diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index f42c62bd6..493559fb3 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -2,7 +2,6 @@ package jadx.core; import jadx.api.IJadxArgs; import jadx.core.codegen.CodeGen; -import jadx.core.dex.visitors.BlockMakerVisitor; import jadx.core.dex.visitors.ClassModifier; import jadx.core.dex.visitors.CodeShrinker; import jadx.core.dex.visitors.ConstInlinerVisitor; @@ -16,6 +15,11 @@ import jadx.core.dex.visitors.ModVisitor; import jadx.core.dex.visitors.PrepareForCodeGen; import jadx.core.dex.visitors.ReSugarCode; import jadx.core.dex.visitors.SimplifyVisitor; +import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler; +import jadx.core.dex.visitors.blocksmaker.BlockFinallyExtract; +import jadx.core.dex.visitors.blocksmaker.BlockFinish; +import jadx.core.dex.visitors.blocksmaker.BlockProcessor; +import jadx.core.dex.visitors.blocksmaker.BlockSplitter; import jadx.core.dex.visitors.regions.CheckRegions; import jadx.core.dex.visitors.regions.IfRegionVisitor; import jadx.core.dex.visitors.regions.LoopRegionVisitor; @@ -55,10 +59,16 @@ public class Jadx { if (args.isFallbackMode()) { passes.add(new FallbackModeVisitor()); } else { - passes.add(new BlockMakerVisitor()); + passes.add(new BlockSplitter()); + passes.add(new BlockProcessor()); + passes.add(new BlockExceptionHandler()); + passes.add(new BlockFinallyExtract()); + passes.add(new BlockFinish()); + passes.add(new SSATransform()); passes.add(new DebugInfoVisitor()); passes.add(new TypeInference()); + if (args.isRawCFGOutput()) { passes.add(DotGraphVisitor.dumpRaw(outDir)); } @@ -72,6 +82,7 @@ public class Jadx { passes.add(new CodeShrinker()); passes.add(new ReSugarCode()); + if (args.isCFGOutput()) { passes.add(DotGraphVisitor.dump(outDir)); } diff --git a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java index 82eb62144..76998b998 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -446,6 +446,8 @@ public class InsnGen { break; case PHI: + assert isFallback(); + code.add("PHI(").add(String.valueOf(insn.getArgsCount())).add(")"); break; /* fallback mode instructions */ diff --git a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java index b3dba91a8..4f17dcef3 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java @@ -28,13 +28,13 @@ import jadx.core.dex.regions.loops.ForLoop; import jadx.core.dex.regions.loops.LoopRegion; import jadx.core.dex.regions.loops.LoopType; import jadx.core.dex.trycatch.ExceptionHandler; -import jadx.core.dex.trycatch.TryCatchBlock; import jadx.core.utils.ErrorsCounter; import jadx.core.utils.RegionUtils; import jadx.core.utils.exceptions.CodegenException; import jadx.core.utils.exceptions.JadxRuntimeException; import java.util.List; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -282,27 +282,28 @@ public class RegionGen extends InsnGen { } private void makeTryCatch(TryCatchRegion region, CodeWriter code) throws CodegenException { - TryCatchBlock tryCatchBlock = region.geTryCatchBlock(); code.startLine("try {"); makeRegionIndent(code, region.getTryRegion()); // TODO: move search of 'allHandler' to 'TryCatchRegion' ExceptionHandler allHandler = null; - for (ExceptionHandler handler : tryCatchBlock.getHandlers()) { - if (!handler.isCatchAll()) { - makeCatchBlock(code, handler); - } else { + for (Map.Entry entry : region.getCatchRegions().entrySet()) { + ExceptionHandler handler = entry.getKey(); + if (handler.isCatchAll()) { if (allHandler != null) { LOG.warn("Several 'all' handlers in try/catch block in {}", mth); } allHandler = handler; + } else { + makeCatchBlock(code, handler); } } if (allHandler != null) { makeCatchBlock(code, allHandler); } - if (tryCatchBlock.getFinalRegion() != null) { + IContainer finallyRegion = region.getFinallyRegion(); + if (finallyRegion != null) { code.startLine("} finally {"); - makeRegionIndent(code, tryCatchBlock.getFinalRegion()); + makeRegionIndent(code, finallyRegion); } code.startLine('}'); } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java index 6c3ac2bcc..6b2e217ff 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java @@ -10,6 +10,7 @@ public enum AFlag { SYNTHETIC, RETURN, // block contains only return instruction + ORIG_RETURN, DECLARE_VAR, DONT_WRAP, @@ -18,6 +19,7 @@ public enum AFlag { DONT_INLINE, DONT_GENERATE, SKIP, + REMOVE, SKIP_FIRST_ARG, ANONYMOUS_CONSTRUCTOR, diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java index 8f75f0f0f..8660aaed6 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java @@ -7,6 +7,7 @@ import jadx.core.dex.attributes.nodes.EnumClassAttr; import jadx.core.dex.attributes.nodes.EnumMapAttr; import jadx.core.dex.attributes.nodes.FieldReplaceAttr; import jadx.core.dex.attributes.nodes.ForceReturnAttr; +import jadx.core.dex.attributes.nodes.IgnoreEdgeAttr; import jadx.core.dex.attributes.nodes.JadxErrorAttr; import jadx.core.dex.attributes.nodes.JumpInfo; import jadx.core.dex.attributes.nodes.LoopInfo; @@ -51,4 +52,5 @@ public class AType { public static final AType SOURCE_FILE = new AType(); public static final AType DECLARE_VARIABLES = new AType(); public static final AType LOOP_LABEL = new AType(); + public static final AType IGNORE_EDGE = new AType(); } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/IgnoreEdgeAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/IgnoreEdgeAttr.java new file mode 100644 index 000000000..4cb147a3b --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/IgnoreEdgeAttr.java @@ -0,0 +1,32 @@ +package jadx.core.dex.attributes.nodes; + +import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.IAttribute; +import jadx.core.dex.nodes.BlockNode; +import jadx.core.utils.Utils; + +import java.util.HashSet; +import java.util.Set; + +public class IgnoreEdgeAttr implements IAttribute { + + private final Set blocks = new HashSet(); + + public Set getBlocks() { + return blocks; + } + + public boolean contains(BlockNode block) { + return blocks.contains(block); + } + + @Override + public AType getType() { + return AType.IGNORE_EDGE; + } + + @Override + public String toString() { + return "IGNORE_EDGES: " + Utils.listToString(blocks); + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/BlockNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/BlockNode.java index 44ed06fbc..e9c619834 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/BlockNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/BlockNode.java @@ -3,6 +3,7 @@ package jadx.core.dex.nodes; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AttrNode; +import jadx.core.dex.attributes.nodes.IgnoreEdgeAttr; import jadx.core.dex.attributes.nodes.LoopInfo; import jadx.core.utils.InsnUtils; @@ -99,6 +100,10 @@ public class BlockNode extends AttrNode implements IBlock { toRemove.add(loop.getStart()); } } + IgnoreEdgeAttr ignoreEdgeAttr = block.get(AType.IGNORE_EDGE); + if (ignoreEdgeAttr != null) { + toRemove.addAll(ignoreEdgeAttr.getBlocks()); + } if (toRemove.isEmpty()) { return sucList; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/DebugInfoParser.java b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/DebugInfoParser.java index 47383c50f..5cfa12c11 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/parser/DebugInfoParser.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/parser/DebugInfoParser.java @@ -237,31 +237,32 @@ public class DebugInfoParser { } private static void merge(InsnArg arg, LocalVar var) { - if (arg != null && arg.isRegister()) { - RegisterArg reg = (RegisterArg) arg; - if (var.getRegNum() == reg.getRegNum()) { - SSAVar ssaVar = reg.getSVar(); + if (arg == null || !arg.isRegister()) { + return; + } + RegisterArg reg = (RegisterArg) arg; + if (var.getRegNum() != reg.getRegNum()) { + return; + } + boolean mergeRequired = false; - boolean mergeRequired = false; + SSAVar ssaVar = reg.getSVar(); + if (ssaVar != null) { + int ssaEnd = ssaVar.getEndAddr(); + int ssaStart = ssaVar.getStartAddr(); + int localStart = var.getStartAddr(); + int localEnd = var.getEndAddr(); - if (ssaVar != null) { - int ssaEnd = ssaVar.getEndAddr(); - int ssaStart = ssaVar.getStartAddr(); - int localStart = var.getStartAddr(); - int localEnd = var.getEndAddr(); - - boolean isIntersected = !((localEnd < ssaStart) || (ssaEnd < localStart)); - if (isIntersected && (ssaEnd <= localEnd)) { - mergeRequired = true; - } - } else { - mergeRequired = true; - } - - if (mergeRequired) { - reg.mergeDebugInfo(var.getType(), var.getName()); - } + boolean isIntersected = !((localEnd < ssaStart) || (ssaEnd < localStart)); + if (isIntersected && (ssaEnd <= localEnd)) { + mergeRequired = true; } + } else { + mergeRequired = true; + } + + if (mergeRequired) { + reg.mergeDebugInfo(var.getType(), var.getName()); } } } diff --git a/jadx-core/src/main/java/jadx/core/dex/regions/TryCatchRegion.java b/jadx-core/src/main/java/jadx/core/dex/regions/TryCatchRegion.java index 0f4699bb1..6fe8ddae6 100644 --- a/jadx-core/src/main/java/jadx/core/dex/regions/TryCatchRegion.java +++ b/jadx-core/src/main/java/jadx/core/dex/regions/TryCatchRegion.java @@ -8,12 +8,15 @@ import jadx.core.utils.Utils; import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; public final class TryCatchRegion extends AbstractRegion { private final IContainer tryRegion; - private List catchRegions = Collections.emptyList(); + private Map catchRegions = Collections.emptyMap(); + private IContainer finallyRegion; private TryCatchBlock tryCatchBlock; public TryCatchRegion(IRegion parent, IContainer tryRegion) { @@ -21,31 +24,50 @@ public final class TryCatchRegion extends AbstractRegion { this.tryRegion = tryRegion; } + public void setTryCatchBlock(TryCatchBlock tryCatchBlock) { + this.tryCatchBlock = tryCatchBlock; + int count = tryCatchBlock.getHandlersCount(); + this.catchRegions = new LinkedHashMap(count); + for (ExceptionHandler handler : tryCatchBlock.getHandlers()) { + IContainer handlerRegion = handler.getHandlerRegion(); + if (handlerRegion != null) { + if (handler.isFinally()) { + finallyRegion = handlerRegion; + } else { + catchRegions.put(handler, handlerRegion); + } + } + } + } + public IContainer getTryRegion() { return tryRegion; } - public List getCatchRegions() { + public Map getCatchRegions() { return catchRegions; } - public TryCatchBlock geTryCatchBlock() { + public TryCatchBlock getTryCatchBlock() { return tryCatchBlock; } - public void setTryCatchBlock(TryCatchBlock tryCatchBlock) { - this.tryCatchBlock = tryCatchBlock; - this.catchRegions = new ArrayList(tryCatchBlock.getHandlersCount()); - for (ExceptionHandler handler : tryCatchBlock.getHandlers()) { - catchRegions.add(handler.getHandlerRegion()); - } + public IContainer getFinallyRegion() { + return finallyRegion; + } + + public void setFinallyRegion(IContainer finallyRegion) { + this.finallyRegion = finallyRegion; } @Override public List getSubBlocks() { - List all = new ArrayList(1 + catchRegions.size()); + List all = new ArrayList(2 + catchRegions.size()); all.add(tryRegion); - all.addAll(catchRegions); + all.addAll(catchRegions.values()); + if (finallyRegion != null) { + all.add(finallyRegion); + } return Collections.unmodifiableList(all); } @@ -57,6 +79,7 @@ public final class TryCatchRegion extends AbstractRegion { @Override public String toString() { return "Try: " + tryRegion - + " catches: " + Utils.listToString(catchRegions); + + " catches: " + Utils.listToString(catchRegions.values()) + + (finallyRegion == null ? "" : " finally: " + finallyRegion); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/regions/conditions/IfRegion.java b/jadx-core/src/main/java/jadx/core/dex/regions/conditions/IfRegion.java index 3dddecf3d..cb3c88a56 100644 --- a/jadx-core/src/main/java/jadx/core/dex/regions/conditions/IfRegion.java +++ b/jadx-core/src/main/java/jadx/core/dex/regions/conditions/IfRegion.java @@ -116,6 +116,6 @@ public final class IfRegion extends AbstractRegion { @Override public String toString() { - return "IF(" + condition + ") then " + thenRegion + " else " + elseRegion; + return "IF " + header + " then " + thenRegion + " else " + elseRegion; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/trycatch/ExcHandlerAttr.java b/jadx-core/src/main/java/jadx/core/dex/trycatch/ExcHandlerAttr.java index fdca842d9..f456ed8fb 100644 --- a/jadx-core/src/main/java/jadx/core/dex/trycatch/ExcHandlerAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/trycatch/ExcHandlerAttr.java @@ -28,8 +28,8 @@ public class ExcHandlerAttr implements IAttribute { @Override public String toString() { - return "ExcHandler: " - + (handler.isCatchAll() ? "all" : handler.getCatchType()) - + " " + handler.getArg(); + return "ExcHandler: " + (handler.isFinally() + ? " FINALLY" + : (handler.isCatchAll() ? "all" : handler.getCatchType()) + " " + handler.getArg()); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/trycatch/ExceptionHandler.java b/jadx-core/src/main/java/jadx/core/dex/trycatch/ExceptionHandler.java index f4139b2c9..c8afd6811 100644 --- a/jadx-core/src/main/java/jadx/core/dex/trycatch/ExceptionHandler.java +++ b/jadx-core/src/main/java/jadx/core/dex/trycatch/ExceptionHandler.java @@ -21,6 +21,7 @@ public class ExceptionHandler { private InsnArg arg; private TryCatchBlock tryBlock; + private boolean isFinally; public ExceptionHandler(int addr, ClassInfo type) { this.handleOffset = addr; @@ -79,6 +80,14 @@ public class ExceptionHandler { return tryBlock; } + public boolean isFinally() { + return isFinally; + } + + public void setFinally(boolean isFinally) { + this.isFinally = isFinally; + } + @Override public int hashCode() { return (catchType == null ? 0 : 31 * catchType.hashCode()) + handleOffset; diff --git a/jadx-core/src/main/java/jadx/core/dex/trycatch/TryCatchBlock.java b/jadx-core/src/main/java/jadx/core/dex/trycatch/TryCatchBlock.java index 8eff2ef26..96caf0476 100644 --- a/jadx-core/src/main/java/jadx/core/dex/trycatch/TryCatchBlock.java +++ b/jadx-core/src/main/java/jadx/core/dex/trycatch/TryCatchBlock.java @@ -1,14 +1,11 @@ package jadx.core.dex.trycatch; +import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.nodes.BlockNode; -import jadx.core.dex.nodes.IBlock; -import jadx.core.dex.nodes.IContainer; -import jadx.core.dex.nodes.InsnContainer; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; -import jadx.core.utils.InstructionRemover; import jadx.core.utils.Utils; import java.util.ArrayList; @@ -19,7 +16,6 @@ import java.util.List; public class TryCatchBlock { private final List handlers; - private IContainer finalRegion; // references for fast remove/modify private final List insns; @@ -55,6 +51,7 @@ public class TryCatchBlock { for (Iterator it = handlers.iterator(); it.hasNext(); ) { ExceptionHandler h = it.next(); if (h == handler) { + unbindHandler(h); it.remove(); break; } @@ -64,29 +61,20 @@ public class TryCatchBlock { } } + private void unbindHandler(ExceptionHandler handler) { + for (BlockNode block : handler.getBlocks()) { + block.add(AFlag.SKIP); + } + } + private void removeWholeBlock(MethodNode mth) { - if (finalRegion != null) { - // search catch attr - for (BlockNode block : mth.getBasicBlocks()) { - CatchAttr cb = block.get(AType.CATCH_BLOCK); - if (cb == attr) { - for (ExceptionHandler eh : mth.getExceptionHandlers()) { - if (eh.getBlocks().contains(block)) { - TryCatchBlock tb = eh.getTryBlock(); - tb.setFinalRegionFromInsns(mth, ((IBlock) finalRegion).getInstructions()); - } - } - } - } - } else { - // self destruction - for (InsnNode insn : insns) { - insn.removeAttr(attr); - } - insns.clear(); - for (BlockNode block : mth.getBasicBlocks()) { - block.removeAttr(attr); - } + // self destruction + for (InsnNode insn : insns) { + insn.removeAttr(attr); + } + insns.clear(); + for (BlockNode block : mth.getBasicBlocks()) { + block.removeAttr(attr); } } @@ -108,36 +96,11 @@ public class TryCatchBlock { return attr; } - public IContainer getFinalRegion() { - return finalRegion; - } - - public void setFinalRegion(IContainer finalRegion) { - this.finalRegion = finalRegion; - } - - public void setFinalRegionFromInsns(MethodNode mth, List insns) { - List finalBlockInsns = new ArrayList(insns); - setFinalRegion(new InsnContainer(finalBlockInsns)); - - InstructionRemover.unbindInsnList(mth, finalBlockInsns); - - // remove these instructions from other handlers - for (ExceptionHandler h : getHandlers()) { - for (BlockNode ehb : h.getBlocks()) { - ehb.getInstructions().removeAll(finalBlockInsns); - } + public boolean merge(MethodNode mth, TryCatchBlock tryBlock) { + if (tryBlock == this) { + return false; } - // remove from blocks with this catch - for (BlockNode b : mth.getBasicBlocks()) { - CatchAttr ca = b.get(AType.CATCH_BLOCK); - if (attr == ca) { - b.getInstructions().removeAll(finalBlockInsns); - } - } - } - public void merge(MethodNode mth, TryCatchBlock tryBlock) { for (InsnNode insn : tryBlock.getInsns()) { this.addInsn(insn); } @@ -148,6 +111,7 @@ public class TryCatchBlock { // clear tryBlock.handlers.clear(); tryBlock.removeWholeBlock(mth); + return true; } @Override diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/CodeShrinker.java b/jadx-core/src/main/java/jadx/core/dex/visitors/CodeShrinker.java index c44364430..686428bad 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/CodeShrinker.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/CodeShrinker.java @@ -208,7 +208,7 @@ public class CodeShrinker extends AbstractVisitor { // } SSAVar sVar = arg.getSVar(); // allow inline only one use arg or 'this' - if (sVar.getVariableUseCount() != 1 && !arg.isThis()) { + if (sVar == null || (sVar.getVariableUseCount() != 1 && !arg.isThis())) { continue; } InsnNode assignInsn = sVar.getAssign().getParentInsn(); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DebugInfoVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DebugInfoVisitor.java index 70670155c..e85bbb337 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DebugInfoVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DebugInfoVisitor.java @@ -6,6 +6,7 @@ import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.parser.DebugInfoParser; +import jadx.core.utils.BlockUtils; import jadx.core.utils.exceptions.JadxException; public class DebugInfoVisitor extends AbstractVisitor { @@ -23,18 +24,21 @@ public class DebugInfoVisitor extends AbstractVisitor { mth.setSourceLine(line - 1); } } - if (!mth.getReturnType().equals(ArgType.VOID) - && mth.getExitBlocks().size() > 1) { + if (!mth.getReturnType().equals(ArgType.VOID)) { // fix debug for splitter 'return' instructions for (BlockNode exit : mth.getExitBlocks()) { - InsnNode ret = exit.getInstructions().get(0); - InsnNode oldRet = insnArr[ret.getOffset()]; - if (oldRet != ret) { - RegisterArg oldArg = (RegisterArg) oldRet.getArg(0); - RegisterArg newArg = (RegisterArg) ret.getArg(0); - newArg.mergeDebugInfo(oldArg.getType(), oldArg.getName()); - ret.setSourceLine(oldRet.getSourceLine()); + InsnNode ret = BlockUtils.getLastInsn(exit); + if (ret == null) { + continue; } + InsnNode oldRet = insnArr[ret.getOffset()]; + if (oldRet == ret) { + continue; + } + RegisterArg oldArg = (RegisterArg) oldRet.getArg(0); + RegisterArg newArg = (RegisterArg) ret.getArg(0); + newArg.mergeDebugInfo(oldArg.getType(), oldArg.getName()); + ret.setSourceLine(oldRet.getSourceLine()); } } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java index 138eb3be8..66b17aac2 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java @@ -28,7 +28,6 @@ import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.utils.InstructionRemover; import java.util.ArrayList; -import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,10 +50,6 @@ public class ModVisitor extends AbstractVisitor { removeStep(mth, remover); checkArgsNames(mth); - - for (BlockNode block : mth.getBasicBlocks()) { - processExceptionHandler(mth, block); - } } private static void replaceStep(MethodNode mth, InstructionRemover remover) { @@ -110,6 +105,10 @@ public class ModVisitor extends AbstractVisitor { } break; + case MOVE_EXCEPTION: + processMoveException(mth, block, insn, remover); + break; + default: break; } @@ -284,70 +283,33 @@ public class ModVisitor extends AbstractVisitor { } } - private static void processExceptionHandler(MethodNode mth, BlockNode block) { - ExcHandlerAttr handlerAttr = block.get(AType.EXC_HANDLER); - if (handlerAttr == null) { + private static void processMoveException(MethodNode mth, BlockNode block, InsnNode insn, + InstructionRemover remover) { + ExcHandlerAttr excHandlerAttr = block.get(AType.EXC_HANDLER); + if (excHandlerAttr == null) { return; } - ExceptionHandler excHandler = handlerAttr.getHandler(); - boolean noExitNode = true; // check if handler has exit edge to block not from this handler - boolean reThrow = false; - for (BlockNode excBlock : excHandler.getBlocks()) { - if (noExitNode) { - noExitNode = excHandler.getBlocks().containsAll(excBlock.getCleanSuccessors()); - } + ExceptionHandler excHandler = excHandlerAttr.getHandler(); - List insns = excBlock.getInstructions(); - int size = insns.size(); - if (excHandler.isCatchAll() - && size > 0 - && insns.get(size - 1).getType() == InsnType.THROW) { - reThrow = true; - InstructionRemover.remove(mth, excBlock, size - 1); - - // move not removed instructions to 'finally' block - if (!insns.isEmpty()) { - // TODO: support instructions from several blocks - // tryBlock.setFinalBlockFromInsns(mth, insns); - // TODO: because of incomplete realization don't extract final block, - // just remove unnecessary instructions - insns.clear(); - } - } + // result arg used both in this insn and exception handler, + RegisterArg resArg = insn.getResult(); + ArgType type = excHandler.isCatchAll() ? ArgType.THROWABLE : excHandler.getCatchType().getType(); + String name = excHandler.isCatchAll() ? "th" : "e"; + if (resArg.getName() == null) { + resArg.setName(name); } - - List blockInsns = block.getInstructions(); - if (!blockInsns.isEmpty()) { - InsnNode insn = blockInsns.get(0); - if (insn.getType() == InsnType.MOVE_EXCEPTION) { - // result arg used both in this insn and exception handler, - RegisterArg resArg = insn.getResult(); - ArgType type = excHandler.isCatchAll() ? ArgType.THROWABLE : excHandler.getCatchType().getType(); - String name = excHandler.isCatchAll() ? "th" : "e"; - if (resArg.getName() == null) { - resArg.setName(name); - } - SSAVar sVar = insn.getResult().getSVar(); - if (sVar.getUseCount() == 0) { - excHandler.setArg(new NamedArg(name, type)); - InstructionRemover.remove(mth, block, 0); - } else if (sVar.isUsedInPhi()) { - // exception var moved to external variable => replace with 'move' insn - InsnNode moveInsn = new InsnNode(InsnType.MOVE, 1); - moveInsn.setResult(insn.getResult()); - NamedArg namedArg = new NamedArg(name, type); - moveInsn.addArg(namedArg); - excHandler.setArg(namedArg); - replaceInsn(block, 0, moveInsn); - } - } - } - int totalSize = 0; - for (BlockNode excBlock : excHandler.getBlocks()) { - totalSize += excBlock.getInstructions().size(); - } - if (totalSize == 0 && noExitNode && reThrow) { - handlerAttr.getTryBlock().removeHandler(mth, excHandler); + SSAVar sVar = insn.getResult().getSVar(); + if (sVar.getUseCount() == 0) { + excHandler.setArg(new NamedArg(name, type)); + remover.add(insn); + } else if (sVar.isUsedInPhi()) { + // exception var moved to external variable => replace with 'move' insn + InsnNode moveInsn = new InsnNode(InsnType.MOVE, 1); + moveInsn.setResult(insn.getResult()); + NamedArg namedArg = new NamedArg(name, type); + moveInsn.addArg(namedArg); + excHandler.setArg(namedArg); + replaceInsn(block, 0, moveInsn); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/BlockProcessingHelper.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockExceptionHandler.java similarity index 76% rename from jadx-core/src/main/java/jadx/core/dex/visitors/BlockProcessingHelper.java rename to jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockExceptionHandler.java index ed9d2fda1..6d7c06579 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/BlockProcessingHelper.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockExceptionHandler.java @@ -1,8 +1,7 @@ -package jadx.core.dex.visitors; +package jadx.core.dex.visitors.blocksmaker; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; -import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; @@ -14,14 +13,16 @@ import jadx.core.dex.trycatch.CatchAttr; import jadx.core.dex.trycatch.ExcHandlerAttr; import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.dex.trycatch.TryCatchBlock; +import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.utils.BlockUtils; import jadx.core.utils.InstructionRemover; -import java.util.List; +import java.util.Iterator; -public class BlockProcessingHelper { +public class BlockExceptionHandler extends AbstractVisitor { - public static void visit(MethodNode mth) { + @Override + public void visit(MethodNode mth) { if (mth.isNoCode()) { return; } @@ -30,7 +31,6 @@ public class BlockProcessingHelper { } for (BlockNode block : mth.getBasicBlocks()) { block.updateCleanSuccessors(); - initBlocksInIfNodes(block); } for (BlockNode block : mth.getBasicBlocks()) { processExceptionHandlers(mth, block); @@ -38,6 +38,16 @@ public class BlockProcessingHelper { for (BlockNode block : mth.getBasicBlocks()) { processTryCatchBlocks(mth, block); } + + for (BlockNode block : mth.getBasicBlocks()) { + Iterator it = block.getInstructions().iterator(); + while (it.hasNext()) { + InsnNode insn = it.next(); + if (insn.getType() == InsnType.NOP) { + it.remove(); + } + } + } } /** @@ -88,24 +98,35 @@ public class BlockProcessingHelper { // if 'throw' in exception handler block have 'catch' - merge these catch blocks for (InsnNode insn : excBlock.getInstructions()) { - if (insn.getType() == InsnType.THROW) { - CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK); - if (catchAttr != null) { - TryCatchBlock handlerBlock = handlerAttr.getTryBlock(); - TryCatchBlock catchBlock = catchAttr.getTryBlock(); - if (handlerBlock != catchBlock) { // TODO: why it can be? - handlerBlock.merge(mth, catchBlock); - catchBlock.removeInsn(insn); - } - } + CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK); + if (catchAttr == null) { + continue; + } + if (insn.getType() == InsnType.THROW + || onlyAllHandler(catchAttr.getTryBlock())) { + TryCatchBlock handlerBlock = handlerAttr.getTryBlock(); + TryCatchBlock catchBlock = catchAttr.getTryBlock(); + handlerBlock.merge(mth, catchBlock); } } } } } + private static boolean onlyAllHandler(TryCatchBlock tryBlock) { + if (tryBlock.getHandlersCount() == 1) { + ExceptionHandler eh = tryBlock.getHandlers().iterator().next(); + if (eh.isCatchAll() || eh.isFinally()) { + return true; + } + } + return false; + } + + /** + * If all instructions in block have same 'catch' attribute mark it as 'TryCatch' block. + */ private static void processTryCatchBlocks(MethodNode mth, BlockNode block) { - // if all instructions in block have same 'catch' attribute mark it as 'TryCatch' block CatchAttr commonCatchAttr = null; for (InsnNode insn : block.getInstructions()) { CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK); @@ -138,17 +159,4 @@ public class BlockProcessingHelper { } } } - - /** - * Init 'then' and 'else' blocks for 'if' instruction. - */ - private static void initBlocksInIfNodes(BlockNode block) { - List instructions = block.getInstructions(); - if (instructions.size() == 1) { - InsnNode insn = instructions.get(0); - if (insn.getType() == InsnType.IF) { - ((IfNode) insn).initBlocks(block); - } - } - } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockFinallyExtract.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockFinallyExtract.java new file mode 100644 index 000000000..b3510fd2e --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockFinallyExtract.java @@ -0,0 +1,527 @@ +package jadx.core.dex.visitors.blocksmaker; + +import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.nodes.IgnoreEdgeAttr; +import jadx.core.dex.instructions.InsnType; +import jadx.core.dex.instructions.args.InsnArg; +import jadx.core.dex.instructions.args.RegisterArg; +import jadx.core.dex.nodes.BlockNode; +import jadx.core.dex.nodes.InsnNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.trycatch.ExcHandlerAttr; +import jadx.core.dex.trycatch.ExceptionHandler; +import jadx.core.dex.trycatch.SplitterBlockAttr; +import jadx.core.dex.trycatch.TryCatchBlock; +import jadx.core.dex.visitors.AbstractVisitor; +import jadx.core.dex.visitors.blocksmaker.helpers.BlocksPair; +import jadx.core.dex.visitors.blocksmaker.helpers.BlocksRemoveInfo; +import jadx.core.dex.visitors.ssa.LiveVarAnalysis; +import jadx.core.utils.BlockUtils; +import jadx.core.utils.InstructionRemover; +import jadx.core.utils.exceptions.JadxRuntimeException; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static jadx.core.dex.visitors.blocksmaker.BlockSplitter.connect; +import static jadx.core.dex.visitors.blocksmaker.BlockSplitter.insertBlockBetween; +import static jadx.core.dex.visitors.blocksmaker.BlockSplitter.removeConnection; + +public class BlockFinallyExtract extends AbstractVisitor { + private static final Logger LOG = LoggerFactory.getLogger(BlockFinallyExtract.class); + + @Override + public void visit(MethodNode mth) { + if (mth.isNoCode() || mth.isNoExceptionHandlers()) { + return; + } + + boolean reloadBlocks = false; + List basicBlocks = mth.getBasicBlocks(); + for (int i = 0; i < basicBlocks.size(); i++) { + BlockNode block = basicBlocks.get(i); + if (processExceptionHandler(mth, block)) { + reloadBlocks = true; + } + } + if (reloadBlocks) { + mergeReturnBlocks(mth); + BlockProcessor.rerun(mth); + } + } + + private static boolean processExceptionHandler(MethodNode mth, BlockNode block) { + ExcHandlerAttr handlerAttr = block.get(AType.EXC_HANDLER); + if (handlerAttr == null) { + return false; + } + ExceptionHandler excHandler = handlerAttr.getHandler(); + + // check if handler has exit edge to block not from this handler + boolean noExitNode = true; + boolean reThrowRemoved = false; + + for (BlockNode excBlock : excHandler.getBlocks()) { + if (noExitNode) { + noExitNode = excHandler.getBlocks().containsAll(excBlock.getCleanSuccessors()); + } + List insns = excBlock.getInstructions(); + int size = insns.size(); + if (excHandler.isCatchAll() + && size != 0 + && insns.get(size - 1).getType() == InsnType.THROW) { + reThrowRemoved = true; + InstructionRemover.remove(mth, excBlock, size - 1); + } + } + if (reThrowRemoved && noExitNode + && extractFinally(mth, block, excHandler)) { + return true; + } + int totalSize = countInstructions(excHandler); + if (totalSize == 0 && reThrowRemoved && noExitNode) { + handlerAttr.getTryBlock().removeHandler(mth, excHandler); + } + return false; + } + + /** + * Search and remove common code from 'catch' and 'handlers'. + */ + private static boolean extractFinally(MethodNode mth, BlockNode handlerBlock, ExceptionHandler handler) { + int count = handler.getBlocks().size(); + BitSet bs = new BitSet(count); + List blocks = new ArrayList(count); + for (BlockNode block : handler.getBlocks()) { + List insns = block.getInstructions(); + if (!insns.isEmpty()) { + if (insns.get(0).getType() != InsnType.MOVE_EXCEPTION) { + blocks.add(block); + } + bs.set(block.getId()); + } + } + if (blocks.isEmpty()) { + // nothing to do + return false; + } + + List removes = new LinkedList(); + Set splitters = new HashSet(); + + // remove 'finally' from handlers + TryCatchBlock tryBlock = handler.getTryBlock(); + if (tryBlock.getHandlersCount() > 1) { + for (ExceptionHandler otherHandler : tryBlock.getHandlers()) { + if (otherHandler == handler) { + continue; + } + for (BlockNode hb : otherHandler.getBlocks()) { + BlocksRemoveInfo removeInfo = removeInsns(mth, hb, blocks, bs); + if (removeInfo != null) { + removes.add(removeInfo); + break; + } + } + } + if (removes.size() != tryBlock.getHandlersCount() - 1) { + return false; + } + } + + for (ExceptionHandler otherHandler : tryBlock.getHandlers()) { + SplitterBlockAttr splitterAttr = otherHandler.getHandlerBlock().get(AType.SPLITTER_BLOCK); + if (splitterAttr != null) { + BlockNode splBlock = splitterAttr.getBlock(); + if (!splBlock.getCleanSuccessors().isEmpty()) { + splitters.add(splBlock); + } + } + } + + // remove 'finally' from 'try' blocks (dominated by splitter block) + boolean removed = false; + for (BlockNode splitter : splitters) { + BlockNode start = splitter.getCleanSuccessors().get(0); + List list = BlockUtils.collectBlocksDominatedBy(splitter, start); + for (BlockNode block : list) { + if (bs.get(block.getId())) { + continue; + } + BlocksRemoveInfo removeInfo = removeInsns(mth, block, blocks, bs); + if (removeInfo != null) { + removes.add(removeInfo); + removed = true; + break; + } + } + } + if (!removed) { + return false; + } + + // 'finally' extract confirmed + for (BlocksRemoveInfo removeInfo : removes) { + if (!applyRemove(mth, removeInfo)) { + return false; + } + } + handler.setFinally(true); + // remove 'move-exception' instruction + if (BlockUtils.checkLastInsnType(handlerBlock, InsnType.MOVE_EXCEPTION)) { + InstructionRemover.remove(mth, handlerBlock, handlerBlock.getInstructions().size() - 1); + handlerBlock.add(AFlag.SKIP); + } + return true; + } + + private static BlocksRemoveInfo removeInsns(MethodNode mth, BlockNode remBlock, List blocks, BitSet bs) { + if (blocks.isEmpty()) { + return null; + } + BlockNode startBlock = blocks.get(0); + BlocksRemoveInfo removeInfo = checkFromFirstBlock(remBlock, startBlock, bs); + if (removeInfo == null) { + return null; + } + if (removeInfo.getOuts().size() != 1) { + LOG.debug("Unexpected finally block outs count: {}", removeInfo.getOuts()); + return null; + } + return removeInfo; + } + + private static BlocksRemoveInfo checkFromFirstBlock(BlockNode remBlock, BlockNode startBlock, BitSet bs) { + BlocksRemoveInfo removeInfo = isStartBlock(remBlock, startBlock); + if (removeInfo == null) { + return null; + } + if (!checkBlocksTree(remBlock, startBlock, removeInfo, bs)) { + return null; + } + return removeInfo; + } + + /** + * 'Finally' instructions can start in the middle of the first block. + */ + private static BlocksRemoveInfo isStartBlock(BlockNode remBlock, BlockNode startBlock) { + List remInsns = remBlock.getInstructions(); + List startInsns = startBlock.getInstructions(); + if (remInsns.size() < startInsns.size()) { + return null; + } + // first - fast check + int delta = remInsns.size() - startInsns.size(); + if (!checkInsns(remInsns, startInsns, delta, null)) { + return null; + } + BlocksRemoveInfo removeInfo = new BlocksRemoveInfo(new BlocksPair(remBlock, startBlock)); + removeInfo.setStartSplitIndex(delta); + // second - run checks again for collect registers mapping + if (!checkInsns(remInsns, startInsns, delta, removeInfo)) { + return null; + } + return removeInfo; + } + + private static boolean checkInsns(List remInsns, List startInsns, int delta, + @Nullable BlocksRemoveInfo removeInfo) { + for (int i = startInsns.size() - 1; i >= 0; i--) { + InsnNode startInsn = startInsns.get(i); + InsnNode remInsn = remInsns.get(delta + i); + if (!sameInsns(remInsn, startInsn, removeInfo)) { + return false; + } + } + return true; + } + + private static boolean checkBlocksTree(BlockNode remBlock, BlockNode startBlock, BlocksRemoveInfo removeInfo, + BitSet bs) { + // skip check on start block + if (!removeInfo.getProcessed().isEmpty() + && !sameBlocks(remBlock, startBlock, removeInfo)) { + return false; + } + removeInfo.getProcessed().add(new BlocksPair(remBlock, startBlock)); + + List baseCS = startBlock.getCleanSuccessors(); + List remCS = remBlock.getCleanSuccessors(); + if (baseCS.size() != remCS.size()) { + removeInfo.getOuts().add(new BlocksPair(remBlock, startBlock)); + return true; + } + for (int i = 0; i < baseCS.size(); i++) { + BlockNode sBlock = baseCS.get(i); + BlockNode rBlock = remCS.get(i); + if (bs.get(sBlock.getId())) { + if (!checkBlocksTree(rBlock, sBlock, removeInfo, bs)) { + return false; + } + } else { + removeInfo.getOuts().add(new BlocksPair(rBlock, sBlock)); + } + } + return true; + } + + private static boolean sameBlocks(BlockNode remBlock, BlockNode startBlock, BlocksRemoveInfo removeInfo) { + List first = remBlock.getInstructions(); + List second = startBlock.getInstructions(); + if (first.size() != second.size()) { + return false; + } + int size = first.size(); + for (int i = 0; i < size; i++) { + if (!sameInsns(first.get(i), second.get(i), removeInfo)) { + return false; + } + } + return true; + } + + private static boolean sameInsns(InsnNode remInsn, InsnNode fInsn, BlocksRemoveInfo removeInfo) { + if (remInsn.getType() != fInsn.getType() + || remInsn.getArgsCount() != fInsn.getArgsCount()) { + return false; + } + for (int i = 0; i < remInsn.getArgsCount(); i++) { + InsnArg remArg = remInsn.getArg(i); + InsnArg fArg = fInsn.getArg(i); + if (remArg.isRegister() != fArg.isRegister()) { + return false; + } + if (removeInfo != null && fArg.isRegister()) { + RegisterArg remReg = (RegisterArg) remArg; + RegisterArg fReg = (RegisterArg) fArg; + if (remReg.getRegNum() != fReg.getRegNum()) { + RegisterArg mapReg = removeInfo.getRegMap().get(remArg); + if (mapReg == null) { + removeInfo.getRegMap().put(remReg, fReg); + } else if (!mapReg.equalRegisterAndType(fReg)) { + return false; + } + } + } + } + return true; + } + + private static boolean applyRemove(MethodNode mth, BlocksRemoveInfo removeInfo) { + BlockNode remBlock = removeInfo.getStart().getFirst(); + BlockNode startBlock = removeInfo.getStart().getSecond(); + + if (remBlock.contains(AFlag.REMOVE)) { + // already processed + return true; + } + if (remBlock.getPredecessors().size() != 1) { + LOG.warn("Finally extract failed: remBlock pred: {}, {}", remBlock, remBlock.getPredecessors()); + return false; + } + BlockNode remBlockPred = remBlock.getPredecessors().get(0); + int splitIndex = removeInfo.getStartSplitIndex(); + if (splitIndex > 0) { + // split start block (remBlock) + BlockNode newBlock = insertBlockBetween(mth, remBlockPred, remBlock); + for (int i = 0; i < splitIndex; i++) { + InsnNode insnNode = remBlock.getInstructions().get(i); + insnNode.add(AFlag.SKIP); + newBlock.getInstructions().add(insnNode); + } + Iterator it = remBlock.getInstructions().iterator(); + while (it.hasNext()) { + InsnNode insnNode = it.next(); + if (insnNode.contains(AFlag.SKIP)) { + it.remove(); + } + } + for (InsnNode insnNode : newBlock.getInstructions()) { + insnNode.remove(AFlag.SKIP); + } + remBlockPred = newBlock; + } + + BlocksPair out = removeInfo.getOuts().iterator().next(); + BlockNode rOut = out.getFirst(); + BlockNode sOut = out.getSecond(); + + // redirect out edges + List filtPreds = filterPredecessors(sOut); + if (filtPreds.size() > 1) { + BlockNode pred = sOut.getPredecessors().get(0); + BlockNode newPred = BlockSplitter.insertBlockBetween(mth, pred, sOut); + for (BlockNode predBlock : new ArrayList(sOut.getPredecessors())) { + if (predBlock != newPred) { + removeConnection(predBlock, sOut); + connect(predBlock, newPred); + } + } + rOut.getPredecessors().clear(); + addIgnoredEdge(newPred, rOut); + connect(newPred, rOut); + } else if (filtPreds.size() == 1) { + BlockNode pred = filtPreds.get(0); + BlockNode repl = removeInfo.getBySecond(pred); + if (repl == null) { + throw new JadxRuntimeException("Block not found by " + pred + + ", in " + removeInfo + ", method: " + mth); + } + removeConnection(pred, rOut); + addIgnoredEdge(repl, rOut); + connect(repl, rOut); + } else { + throw new JadxRuntimeException("Finally extract failed, unexpected preds: " + filtPreds + + " for " + sOut + ", method: " + mth); + } + + // redirect input edges + for (BlockNode pred : new ArrayList(remBlock.getPredecessors())) { + removeConnection(pred, remBlock); + connect(pred, startBlock); + addIgnoredEdge(pred, startBlock); + connect(pred, rOut); + } + + // generate 'move' instruction for mapped register pairs + if (!removeInfo.getRegMap().isEmpty()) { + // TODO: very expensive operation + LiveVarAnalysis la = new LiveVarAnalysis(mth); + la.runAnalysis(); + for (Map.Entry entry : removeInfo.getRegMap().entrySet()) { + RegisterArg from = entry.getKey(); + if (la.isLive(remBlockPred.getId(), from.getRegNum())) { + RegisterArg to = entry.getValue(); + InsnNode move = new InsnNode(InsnType.MOVE, 1); + move.setResult(to); + move.addArg(from); + remBlockPred.getInstructions().add(move); + } + } + } + + // mark blocks for remove + markForRemove(remBlock); + for (BlocksPair pair : removeInfo.getProcessed()) { + markForRemove(pair.getFirst()); + BlockNode second = pair.getSecond(); + second.updateCleanSuccessors(); + } + return true; + } + + /** + * Unbind block for removing. + */ + private static void markForRemove(BlockNode block) { + for (BlockNode p : block.getPredecessors()) { + p.getSuccessors().remove(block); + } + for (BlockNode s : block.getSuccessors()) { + s.getPredecessors().remove(block); + } + block.getPredecessors().clear(); + block.getSuccessors().clear(); + block.add(AFlag.REMOVE); + } + + private static void addIgnoredEdge(BlockNode from, BlockNode toBlock) { + IgnoreEdgeAttr edgeAttr = from.get(AType.IGNORE_EDGE); + if (edgeAttr == null) { + edgeAttr = new IgnoreEdgeAttr(); + from.addAttr(edgeAttr); + } + edgeAttr.getBlocks().add(toBlock); + } + + private static List filterPredecessors(BlockNode block) { + List predecessors = block.getPredecessors(); + List list = new ArrayList(predecessors.size()); + for (BlockNode pred : predecessors) { + IgnoreEdgeAttr edgeAttr = pred.get(AType.IGNORE_EDGE); + if (edgeAttr == null || !edgeAttr.contains(block)) { + list.add(pred); + } + } + return list; + } + + private static int countInstructions(ExceptionHandler excHandler) { + int totalSize = 0; + for (BlockNode excBlock : excHandler.getBlocks()) { + List list = excBlock.getInstructions(); + if (!list.isEmpty() && list.get(0).getType() == InsnType.MOVE_EXCEPTION) { + // don't count 'move-exception' it will be removed later + totalSize--; + } + totalSize += list.size(); + } + return totalSize; + } + + /** + * Merge return block with same predecessor. + */ + private static void mergeReturnBlocks(MethodNode mth) { + List exitBlocks = mth.getExitBlocks(); + BlockNode pred = getFinallyOutBlock(exitBlocks); + if (pred == null) { + return; + } + List merge = new LinkedList(); + for (BlockNode blockNode : pred.getSuccessors()) { + if (blockNode.contains(AFlag.RETURN)) { + merge.add(blockNode); + } + } + if (merge.size() < 2) { + return; + } + // select 'original' return block + BlockNode origReturnBlock = null; + for (BlockNode ret : merge) { + if (ret.contains(AFlag.ORIG_RETURN)) { + origReturnBlock = ret; + break; + } + } + if (origReturnBlock == null) { + return; + } + IgnoreEdgeAttr edgeAttr = pred.get(AType.IGNORE_EDGE); + for (BlockNode mb : merge) { + if (mb == origReturnBlock) { + continue; + } + for (BlockNode remPred : mb.getPredecessors()) { + connect(remPred, origReturnBlock); + } + markForRemove(mb); + edgeAttr.getBlocks().remove(mb); + } + } + + private static BlockNode getFinallyOutBlock(List exitBlocks) { + for (BlockNode exitBlock : exitBlocks) { + for (BlockNode exitPred : exitBlock.getPredecessors()) { + IgnoreEdgeAttr edgeAttr = exitPred.get(AType.IGNORE_EDGE); + if (edgeAttr != null && edgeAttr.contains(exitBlock)) { + return exitPred; + } + } + } + return null; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockFinish.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockFinish.java new file mode 100644 index 000000000..6db4ff758 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockFinish.java @@ -0,0 +1,40 @@ +package jadx.core.dex.visitors.blocksmaker; + +import jadx.core.dex.instructions.IfNode; +import jadx.core.dex.instructions.InsnType; +import jadx.core.dex.nodes.BlockNode; +import jadx.core.dex.nodes.InsnNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.visitors.AbstractVisitor; + +import java.util.List; + +public class BlockFinish extends AbstractVisitor { + + @Override + public void visit(MethodNode mth) { + if (mth.isNoCode()) { + return; + } + + for (BlockNode block : mth.getBasicBlocks()) { + block.updateCleanSuccessors(); + initBlocksInIfNodes(block); + } + + mth.finishBasicBlocks(); + } + + /** + * Init 'then' and 'else' blocks for 'if' instruction. + */ + private static void initBlocksInIfNodes(BlockNode block) { + List instructions = block.getInstructions(); + if (instructions.size() == 1) { + InsnNode insn = instructions.get(0); + if (insn.getType() == InsnType.IF) { + ((IfNode) insn).initBlocks(block); + } + } + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/BlockMakerVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java similarity index 62% rename from jadx-core/src/main/java/jadx/core/dex/visitors/BlockMakerVisitor.java rename to jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java index 9056b3c0c..97f75cc10 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/BlockMakerVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java @@ -1,10 +1,8 @@ -package jadx.core.dex.visitors; +package jadx.core.dex.visitors.blocksmaker; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; -import jadx.core.dex.attributes.nodes.JumpInfo; import jadx.core.dex.attributes.nodes.LoopInfo; -import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.RegisterArg; @@ -12,172 +10,37 @@ import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.Edge; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; -import jadx.core.dex.trycatch.CatchAttr; -import jadx.core.dex.trycatch.ExceptionHandler; -import jadx.core.dex.trycatch.SplitterBlockAttr; +import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.utils.BlockUtils; import jadx.core.utils.exceptions.JadxRuntimeException; import java.util.ArrayList; import java.util.BitSet; -import java.util.EnumSet; -import java.util.HashMap; import java.util.Iterator; import java.util.List; -import java.util.Map; -import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static jadx.core.dex.visitors.blocksmaker.BlockSplitter.connect; +import static jadx.core.dex.visitors.blocksmaker.BlockSplitter.removeConnection; import static jadx.core.utils.EmptyBitSet.EMPTY; -public class BlockMakerVisitor extends AbstractVisitor { - - // leave these instructions alone in block node - private static final Set SEPARATE_INSNS = EnumSet.of( - InsnType.RETURN, - InsnType.IF, - InsnType.SWITCH, - InsnType.MONITOR_ENTER, - InsnType.MONITOR_EXIT - ); +public class BlockProcessor extends AbstractVisitor { + private static final Logger LOG = LoggerFactory.getLogger(BlockProcessor.class); @Override public void visit(MethodNode mth) { if (mth.isNoCode()) { return; } - mth.checkInstructions(); - - mth.initBasicBlocks(); - splitBasicBlocks(mth); processBlocksTree(mth); - BlockProcessingHelper.visit(mth); - mth.finishBasicBlocks(); } - private static void splitBasicBlocks(MethodNode mth) { - InsnNode prevInsn = null; - Map blocksMap = new HashMap(); - BlockNode curBlock = startNewBlock(mth, 0); - mth.setEnterBlock(curBlock); - - // split into blocks - for (InsnNode insn : mth.getInstructions()) { - if (insn == null) { - continue; - } - boolean startNew = false; - if (prevInsn != null) { - InsnType type = prevInsn.getType(); - if (type == InsnType.GOTO - || type == InsnType.THROW - || SEPARATE_INSNS.contains(type)) { - - if (type == InsnType.RETURN || type == InsnType.THROW) { - mth.addExitBlock(curBlock); - } - BlockNode block = startNewBlock(mth, insn.getOffset()); - if (type == InsnType.MONITOR_ENTER || type == InsnType.MONITOR_EXIT) { - connect(curBlock, block); - } - curBlock = block; - startNew = true; - } else { - startNew = isSplitByJump(prevInsn, insn) - || SEPARATE_INSNS.contains(insn.getType()) - || isDoWhile(blocksMap, curBlock, insn); - if (startNew) { - BlockNode block = startNewBlock(mth, insn.getOffset()); - connect(curBlock, block); - curBlock = block; - } - } - } - // for try/catch make empty block for connect handlers - if (insn.contains(AFlag.TRY_ENTER)) { - BlockNode block; - if (insn.getOffset() != 0 && !startNew) { - block = startNewBlock(mth, insn.getOffset()); - connect(curBlock, block); - curBlock = block; - } - blocksMap.put(insn.getOffset(), curBlock); - - // add this insn in new block - block = startNewBlock(mth, -1); - curBlock.add(AFlag.SYNTHETIC); - SplitterBlockAttr splitter = new SplitterBlockAttr(curBlock); - block.addAttr(splitter); - curBlock.addAttr(splitter); - connect(curBlock, block); - curBlock = block; - } else { - blocksMap.put(insn.getOffset(), curBlock); - } - curBlock.getInstructions().add(insn); - prevInsn = insn; - } - // setup missing connections - setupConnections(mth, blocksMap); - } - - private static void setupConnections(MethodNode mth, Map blocksMap) { - for (BlockNode block : mth.getBasicBlocks()) { - for (InsnNode insn : block.getInstructions()) { - List jumps = insn.getAll(AType.JUMP); - for (JumpInfo jump : jumps) { - BlockNode srcBlock = getBlock(jump.getSrc(), blocksMap); - BlockNode thisBlock = getBlock(jump.getDest(), blocksMap); - connect(srcBlock, thisBlock); - } - - // connect exception handlers - CatchAttr catches = insn.get(AType.CATCH_BLOCK); - // get synthetic block for handlers - SplitterBlockAttr spl = block.get(AType.SPLITTER_BLOCK); - if (catches != null && spl != null) { - BlockNode splitterBlock = spl.getBlock(); - boolean tryEnd = insn.contains(AFlag.TRY_LEAVE); - for (ExceptionHandler h : catches.getTryBlock().getHandlers()) { - BlockNode handlerBlock = getBlock(h.getHandleOffset(), blocksMap); - // skip self loop in handler - if (splitterBlock != handlerBlock) { - connect(splitterBlock, handlerBlock); - } - if (tryEnd) { - connect(block, handlerBlock); - } - } - } - } - } - } - - private static boolean isSplitByJump(InsnNode prevInsn, InsnNode currentInsn) { - List pJumps = prevInsn.getAll(AType.JUMP); - for (JumpInfo jump : pJumps) { - if (jump.getSrc() == prevInsn.getOffset()) { - return true; - } - } - List cJumps = currentInsn.getAll(AType.JUMP); - for (JumpInfo jump : cJumps) { - if (jump.getDest() == currentInsn.getOffset()) { - return true; - } - } - return false; - } - - private static boolean isDoWhile(Map blocksMap, BlockNode curBlock, InsnNode insn) { - // split 'do-while' block (last instruction: 'if', target this block) - if (insn.getType() == InsnType.IF) { - IfNode ifs = (IfNode) (insn); - BlockNode targetBlock = blocksMap.get(ifs.getTarget()); - if (targetBlock == curBlock) { - return true; - } - } - return false; + public static void rerun(MethodNode mth) { + removeBlocks(mth); + clearBlocksState(mth); + processBlocksTree(mth); } private static void processBlocksTree(MethodNode mth) { @@ -201,32 +64,6 @@ public class BlockMakerVisitor extends AbstractVisitor { processNestedLoops(mth); } - private static BlockNode getBlock(int offset, Map blocksMap) { - BlockNode block = blocksMap.get(offset); - assert block != null; - return block; - } - - private static void connect(BlockNode from, BlockNode to) { - if (!from.getSuccessors().contains(to)) { - from.getSuccessors().add(to); - } - if (!to.getPredecessors().contains(from)) { - to.getPredecessors().add(from); - } - } - - private static void removeConnection(BlockNode from, BlockNode to) { - from.getSuccessors().remove(to); - to.getPredecessors().remove(from); - } - - private static BlockNode startNewBlock(MethodNode mth, int offset) { - BlockNode block = new BlockNode(mth.getBasicBlocks().size(), offset); - mth.getBasicBlocks().add(block); - return block; - } - private static void computeDominators(MethodNode mth) { List basicBlocks = mth.getBasicBlocks(); int nBlocks = basicBlocks.size(); @@ -308,14 +145,18 @@ public class BlockMakerVisitor extends AbstractVisitor { } private static void computeBlockDF(MethodNode mth, BlockNode block) { + if (block.getDomFrontier() != null) { + return; + } for (BlockNode c : block.getDominatesOn()) { computeBlockDF(mth, c); } + List blocks = mth.getBasicBlocks(); BitSet domFrontier = null; for (BlockNode s : block.getSuccessors()) { if (s.getIDom() != block) { if (domFrontier == null) { - domFrontier = new BitSet(); + domFrontier = new BitSet(blocks.size()); } domFrontier.set(s.getId()); } @@ -323,9 +164,9 @@ public class BlockMakerVisitor extends AbstractVisitor { for (BlockNode c : block.getDominatesOn()) { BitSet frontier = c.getDomFrontier(); for (int p = frontier.nextSetBit(0); p >= 0; p = frontier.nextSetBit(p + 1)) { - if (mth.getBasicBlocks().get(p).getIDom() != block) { + if (blocks.get(p).getIDom() != block) { if (domFrontier == null) { - domFrontier = new BitSet(); + domFrontier = new BitSet(blocks.size()); } domFrontier.set(p); } @@ -418,7 +259,7 @@ public class BlockMakerVisitor extends AbstractVisitor { } if (oneHeader) { // several back edges connected to one loop header => make additional block - BlockNode newLoopHeader = startNewBlock(mth, block.getStartOffset()); + BlockNode newLoopHeader = BlockSplitter.startNewBlock(mth, block.getStartOffset()); newLoopHeader.add(AFlag.SYNTHETIC); connect(newLoopHeader, block); for (LoopInfo la : loops) { @@ -438,7 +279,7 @@ public class BlockMakerVisitor extends AbstractVisitor { for (Edge edge : edges) { BlockNode target = edge.getTarget(); if (!target.contains(AFlag.SYNTHETIC)) { - insertBlockBetween(mth, edge.getSource(), target); + BlockSplitter.insertBlockBetween(mth, edge.getSource(), target); change = true; } } @@ -453,7 +294,7 @@ public class BlockMakerVisitor extends AbstractVisitor { List nodes = new ArrayList(loopEnd.getPredecessors()); for (BlockNode pred : nodes) { if (!pred.contains(AFlag.SYNTHETIC)) { - insertBlockBetween(mth, pred, loopEnd); + BlockSplitter.insertBlockBetween(mth, pred, loopEnd); change = true; } } @@ -466,15 +307,6 @@ public class BlockMakerVisitor extends AbstractVisitor { return splitReturn(mth); } - private static BlockNode insertBlockBetween(MethodNode mth, BlockNode source, BlockNode target) { - BlockNode newBlock = startNewBlock(mth, target.getStartOffset()); - newBlock.add(AFlag.SYNTHETIC); - removeConnection(source, target); - connect(source, newBlock); - connect(newBlock, target); - return newBlock; - } - /** * Splice return block if several predecessors presents */ @@ -493,11 +325,12 @@ public class BlockMakerVisitor extends AbstractVisitor { } boolean first = true; for (BlockNode pred : preds) { - BlockNode newRetBlock = startNewBlock(mth, exitBlock.getStartOffset()); + BlockNode newRetBlock = BlockSplitter.startNewBlock(mth, exitBlock.getStartOffset()); newRetBlock.add(AFlag.SYNTHETIC); InsnNode newRetInsn; if (first) { newRetInsn = returnInsn; + newRetBlock.add(AFlag.ORIG_RETURN); first = false; } else { newRetInsn = duplicateReturnInsn(returnInsn); @@ -548,6 +381,21 @@ public class BlockMakerVisitor extends AbstractVisitor { return insn; } + private static void removeBlocks(MethodNode mth) { + Iterator it = mth.getBasicBlocks().iterator(); + while (it.hasNext()) { + BlockNode block = it.next(); + if (block.contains(AFlag.REMOVE)) { + if (!block.getPredecessors().isEmpty() + || !block.getSuccessors().isEmpty()) { + LOG.error("Block {} not deleted, method: {}", block, mth); + } else { + it.remove(); + } + } + } + } + private static void clearBlocksState(MethodNode mth) { for (BlockNode block : mth.getBasicBlocks()) { 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 new file mode 100644 index 000000000..95ccaa0c7 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java @@ -0,0 +1,226 @@ +package jadx.core.dex.visitors.blocksmaker; + +import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.nodes.JumpInfo; +import jadx.core.dex.instructions.IfNode; +import jadx.core.dex.instructions.InsnType; +import jadx.core.dex.nodes.BlockNode; +import jadx.core.dex.nodes.InsnNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.trycatch.CatchAttr; +import jadx.core.dex.trycatch.ExceptionHandler; +import jadx.core.dex.trycatch.SplitterBlockAttr; +import jadx.core.dex.visitors.AbstractVisitor; +import jadx.core.utils.exceptions.JadxRuntimeException; + +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class BlockSplitter extends AbstractVisitor { + + // leave these instructions alone in block node + private static final Set SEPARATE_INSNS = EnumSet.of( + InsnType.RETURN, + InsnType.IF, + InsnType.SWITCH, + InsnType.MONITOR_ENTER, + InsnType.MONITOR_EXIT, + InsnType.THROW + ); + + @Override + public void visit(MethodNode mth) { + if (mth.isNoCode()) { + return; + } + mth.checkInstructions(); + + mth.initBasicBlocks(); + splitBasicBlocks(mth); + removeInsns(mth); + } + + private static void splitBasicBlocks(MethodNode mth) { + InsnNode prevInsn = null; + Map blocksMap = new HashMap(); + BlockNode curBlock = startNewBlock(mth, 0); + mth.setEnterBlock(curBlock); + + // split into blocks + for (InsnNode insn : mth.getInstructions()) { + if (insn == null) { + continue; + } + boolean startNew = false; + if (prevInsn != null) { + InsnType type = prevInsn.getType(); + if (type == InsnType.GOTO + || type == InsnType.THROW + || SEPARATE_INSNS.contains(type)) { + + if (type == InsnType.RETURN || type == InsnType.THROW) { + mth.addExitBlock(curBlock); + } + BlockNode block = startNewBlock(mth, insn.getOffset()); + if (type == InsnType.MONITOR_ENTER || type == InsnType.MONITOR_EXIT) { + connect(curBlock, block); + } + curBlock = block; + startNew = true; + } else { + startNew = isSplitByJump(prevInsn, insn) + || SEPARATE_INSNS.contains(insn.getType()) + || isDoWhile(blocksMap, curBlock, insn) + || prevInsn.contains(AFlag.TRY_LEAVE) + || prevInsn.getType() == InsnType.MOVE_EXCEPTION; + if (startNew) { + BlockNode block = startNewBlock(mth, insn.getOffset()); + connect(curBlock, block); + curBlock = block; + } + } + } + // for try/catch make empty block for connect handlers + if (insn.contains(AFlag.TRY_ENTER)) { + BlockNode block; + if (insn.getOffset() != 0 && !startNew) { + block = startNewBlock(mth, insn.getOffset()); + connect(curBlock, block); + curBlock = block; + } + blocksMap.put(insn.getOffset(), curBlock); + + // add this insn in new block + block = startNewBlock(mth, -1); + curBlock.add(AFlag.SYNTHETIC); + SplitterBlockAttr splitter = new SplitterBlockAttr(curBlock); + block.addAttr(splitter); + curBlock.addAttr(splitter); + connect(curBlock, block); + curBlock = block; + } else { + blocksMap.put(insn.getOffset(), curBlock); + } + curBlock.getInstructions().add(insn); + prevInsn = insn; + } + // setup missing connections + setupConnections(mth, blocksMap); + } + + static BlockNode startNewBlock(MethodNode mth, int offset) { + BlockNode block = new BlockNode(mth.getBasicBlocks().size(), offset); + mth.getBasicBlocks().add(block); + return block; + } + + static void connect(BlockNode from, BlockNode to) { + if (!from.getSuccessors().contains(to)) { + from.getSuccessors().add(to); + } + if (!to.getPredecessors().contains(from)) { + to.getPredecessors().add(from); + } + } + + static void removeConnection(BlockNode from, BlockNode to) { + from.getSuccessors().remove(to); + to.getPredecessors().remove(from); + } + + static BlockNode insertBlockBetween(MethodNode mth, BlockNode source, BlockNode target) { + BlockNode newBlock = startNewBlock(mth, target.getStartOffset()); + newBlock.add(AFlag.SYNTHETIC); + removeConnection(source, target); + connect(source, newBlock); + connect(newBlock, target); + return newBlock; + } + + private static void setupConnections(MethodNode mth, Map blocksMap) { + for (BlockNode block : mth.getBasicBlocks()) { + for (InsnNode insn : block.getInstructions()) { + List jumps = insn.getAll(AType.JUMP); + for (JumpInfo jump : jumps) { + BlockNode srcBlock = getBlock(jump.getSrc(), blocksMap); + BlockNode thisBlock = getBlock(jump.getDest(), blocksMap); + connect(srcBlock, thisBlock); + } + + // connect exception handlers + CatchAttr catches = insn.get(AType.CATCH_BLOCK); + // get synthetic block for handlers + SplitterBlockAttr spl = block.get(AType.SPLITTER_BLOCK); + if (catches != null && spl != null) { + BlockNode splitterBlock = spl.getBlock(); + boolean tryEnd = insn.contains(AFlag.TRY_LEAVE); + for (ExceptionHandler h : catches.getTryBlock().getHandlers()) { + BlockNode handlerBlock = getBlock(h.getHandleOffset(), blocksMap); + // skip self loop in handler + if (splitterBlock != handlerBlock) { + handlerBlock.addAttr(spl); + connect(splitterBlock, handlerBlock); + } + if (tryEnd) { + connect(block, handlerBlock); + } + } + } + } + } + } + + private static boolean isSplitByJump(InsnNode prevInsn, InsnNode currentInsn) { + List pJumps = prevInsn.getAll(AType.JUMP); + for (JumpInfo jump : pJumps) { + if (jump.getSrc() == prevInsn.getOffset()) { + return true; + } + } + List cJumps = currentInsn.getAll(AType.JUMP); + for (JumpInfo jump : cJumps) { + if (jump.getDest() == currentInsn.getOffset()) { + return true; + } + } + return false; + } + + private static boolean isDoWhile(Map blocksMap, BlockNode curBlock, InsnNode insn) { + // split 'do-while' block (last instruction: 'if', target this block) + if (insn.getType() == InsnType.IF) { + IfNode ifs = (IfNode) (insn); + BlockNode targetBlock = blocksMap.get(ifs.getTarget()); + if (targetBlock == curBlock) { + return true; + } + } + return false; + } + + private static BlockNode getBlock(int offset, Map blocksMap) { + BlockNode block = blocksMap.get(offset); + if (block == null) { + throw new JadxRuntimeException("Missing block: " + offset); + } + return block; + } + + private static void removeInsns(MethodNode mth) { + for (BlockNode block : mth.getBasicBlocks()) { + Iterator it = block.getInstructions().iterator(); + while (it.hasNext()) { + InsnType insnType = it.next().getType(); + if (insnType == InsnType.GOTO || insnType == InsnType.NOP) { + it.remove(); + } + } + } + } + +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/helpers/BlocksPair.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/helpers/BlocksPair.java new file mode 100644 index 000000000..0b9970fd0 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/helpers/BlocksPair.java @@ -0,0 +1,43 @@ +package jadx.core.dex.visitors.blocksmaker.helpers; + +import jadx.core.dex.nodes.BlockNode; + +public final class BlocksPair { + private final BlockNode first; + private final BlockNode second; + + public BlocksPair(BlockNode first, BlockNode second) { + this.first = first; + this.second = second; + } + + public BlockNode getFirst() { + return first; + } + + public BlockNode getSecond() { + return second; + } + + @Override + public int hashCode() { + return 31 * first.hashCode() + second.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof BlocksPair)) { + return false; + } + BlocksPair other = (BlocksPair) o; + return first.equals(other.first) && second.equals(other.second); + } + + @Override + public String toString() { + return "(" + first + ", " + second + ")"; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/helpers/BlocksRemoveInfo.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/helpers/BlocksRemoveInfo.java new file mode 100644 index 000000000..78d48bc87 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/helpers/BlocksRemoveInfo.java @@ -0,0 +1,77 @@ +package jadx.core.dex.visitors.blocksmaker.helpers; + +import jadx.core.dex.instructions.args.RegisterArg; +import jadx.core.dex.nodes.BlockNode; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.jetbrains.annotations.Nullable; + +public final class BlocksRemoveInfo { + private final Set processed = new HashSet(); + private final Set outs = new HashSet(); + private final Map regMap = new HashMap(); + private final BlocksPair start; + + private int startSplitIndex; + + public BlocksRemoveInfo(BlocksPair start) { + this.start = start; + } + + public Set getProcessed() { + return processed; + } + + public Set getOuts() { + return outs; + } + + public BlocksPair getStart() { + return start; + } + + public int getStartSplitIndex() { + return startSplitIndex; + } + + public void setStartSplitIndex(int startSplitIndex) { + this.startSplitIndex = startSplitIndex; + } + + public Map getRegMap() { + return regMap; + } + + @Nullable + public BlockNode getByFirst(BlockNode first) { + for (BlocksPair blocksPair : processed) { + if (blocksPair.getFirst() == first) { + return blocksPair.getSecond(); + } + } + return null; + } + + @Nullable + public BlockNode getBySecond(BlockNode second) { + for (BlocksPair blocksPair : processed) { + if (blocksPair.getSecond() == second) { + return blocksPair.getSecond(); + } + } + return null; + } + + @Override + public String toString() { + return "BRI start: " + start + + ", list: " + processed + + ", outs: " + outs + + ", regMap: " + regMap + + ", split: " + startSplitIndex; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/CheckRegions.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/CheckRegions.java index 63b267424..329b73833 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/CheckRegions.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/CheckRegions.java @@ -4,6 +4,7 @@ import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.IBlock; +import jadx.core.dex.nodes.IContainer; import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.regions.loops.LoopRegion; @@ -11,9 +12,8 @@ import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.utils.ErrorsCounter; import jadx.core.utils.exceptions.JadxException; -import java.util.ArrayList; import java.util.HashSet; -import java.util.List; +import java.util.LinkedHashSet; import java.util.Set; import org.slf4j.Logger; @@ -30,9 +30,11 @@ public class CheckRegions extends AbstractVisitor { return; } + // printRegion(mth, mth.getRegion(), "|"); + // check if all blocks included in regions final Set blocksInRegions = new HashSet(); - DepthRegionTraversal.traverseAll(mth, new AbstractRegionVisitor() { + DepthRegionTraversal.traverse(mth, new AbstractRegionVisitor() { @Override public void processBlock(MethodNode mth, IBlock container) { if (!(container instanceof BlockNode)) { @@ -49,7 +51,7 @@ public class CheckRegions extends AbstractVisitor { // TODO // mth.add(AFlag.INCONSISTENT_CODE); LOG.debug(" Duplicated block: {} in {}", block, mth); - // printRegionsWithBlock(mth, block); + printRegionsWithBlock(mth, block); } } }); @@ -79,7 +81,7 @@ public class CheckRegions extends AbstractVisitor { } private static void printRegionsWithBlock(MethodNode mth, final BlockNode block) { - final List regions = new ArrayList(); + final Set regions = new LinkedHashSet(); DepthRegionTraversal.traverseAll(mth, new TracedRegionVisitor() { @Override public void processBlockTraced(MethodNode mth, IBlock container, IRegion currentRegion) { @@ -90,4 +92,15 @@ public class CheckRegions extends AbstractVisitor { }); LOG.debug(" Found block: {} in regions: {}", block, regions); } + + private void printRegion(MethodNode mth, IRegion region, String indent) { + LOG.debug(indent + region); + for (IContainer container : region.getSubBlocks()) { + if (container instanceof IRegion) { + printRegion(mth, (IRegion) container, indent + " "); + } else { + LOG.debug(indent + " " + container); + } + } + } } 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 bf01cf74b..ee13530ed 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 @@ -22,6 +22,7 @@ import jadx.core.dex.regions.conditions.IfRegion; import jadx.core.dex.regions.loops.LoopRegion; import jadx.core.dex.trycatch.ExcHandlerAttr; import jadx.core.dex.trycatch.ExceptionHandler; +import jadx.core.dex.trycatch.SplitterBlockAttr; import jadx.core.dex.trycatch.TryCatchBlock; import jadx.core.utils.BlockUtils; import jadx.core.utils.ErrorsCounter; @@ -316,7 +317,8 @@ public class RegionMaker { Region body = makeRegion(loopStart, stack); BlockNode loopEnd = loop.getEnd(); if (!RegionUtils.isRegionContainsBlock(body, loopEnd) - && !loopEnd.contains(AType.EXC_HANDLER)) { + && !loopEnd.contains(AType.EXC_HANDLER) + && !inExceptionHandlerBlocks(loopEnd)) { body.getSubBlocks().add(loopEnd); } loopRegion.setBody(body); @@ -330,6 +332,18 @@ public class RegionMaker { return loopExit; } + private boolean inExceptionHandlerBlocks(BlockNode loopEnd) { + if (mth.getExceptionHandlersCount() == 0) { + return false; + } + for (ExceptionHandler eh : mth.getExceptionHandlers()) { + if (eh.getBlocks().contains(loopEnd)) { + return true; + } + } + return false; + } + private boolean canInsertBreak(BlockNode exit) { if (exit.contains(AFlag.RETURN) || BlockUtils.checkLastInsnType(exit, InsnType.BREAK)) { @@ -747,23 +761,33 @@ public class RegionMaker { } } + // TODO add blocks common for several handlers to some region private void processExcHandler(ExceptionHandler handler, Set exits) { BlockNode start = handler.getHandlerBlock(); if (start == null) { return; } - - // TODO extract finally part which exists in all handlers from same try block - // TODO add blocks common for several handlers to some region - RegionStack stack = new RegionStack(mth); - stack.addExits(exits); - - BlockNode exit = BlockUtils.traverseWhileDominates(start, start); - if (exit != null && RegionUtils.isRegionContainsBlock(mth.getRegion(), exit)) { - stack.addExit(exit); + BlockNode dom; + if (handler.isFinally()) { + SplitterBlockAttr splitterAttr = start.get(AType.SPLITTER_BLOCK); + if (splitterAttr == null) { + return; + } + dom = splitterAttr.getBlock(); + } else { + dom = start; + stack.addExits(exits); + } + BitSet domFrontier = dom.getDomFrontier(); + List handlerExits = BlockUtils.bitSetToBlocks(mth, domFrontier); + boolean inLoop = mth.getLoopForBlock(start) != null; + for (BlockNode exit : handlerExits) { + if ((!inLoop || BlockUtils.isPathExists(start, exit)) + && RegionUtils.isRegionContainsBlock(mth.getRegion(), exit)) { + stack.addExit(exit); + } } - handler.setHandlerRegion(makeRegion(start, stack)); ExcHandlerAttr excHandlerAttr = start.get(AType.EXC_HANDLER); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ssa/SSATransform.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ssa/SSATransform.java index 0a55c2a49..90d360d14 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ssa/SSATransform.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ssa/SSATransform.java @@ -105,16 +105,17 @@ public class SSATransform extends AbstractVisitor { for (InsnNode insn : block.getInstructions()) { if (insn.getType() != InsnType.PHI) { for (InsnArg arg : insn.getArguments()) { - if (arg.isRegister()) { - RegisterArg reg = (RegisterArg) arg; - int regNum = reg.getRegNum(); - SSAVar var = vars[regNum]; - if (var == null) { - var = mth.makeNewSVar(regNum, vers, null); - vars[regNum] = var; - } - var.use(reg); + if (!arg.isRegister()) { + continue; } + RegisterArg reg = (RegisterArg) arg; + int regNum = reg.getRegNum(); + SSAVar var = vars[regNum]; + if (var == null) { + throw new JadxRuntimeException("Not initialized variable reg: " + regNum + + ", insn: " + insn + ", block:" + block + ", method: " + mth); + } + var.use(reg); } } RegisterArg result = insn.getResult(); @@ -125,24 +126,24 @@ public class SSATransform extends AbstractVisitor { } for (BlockNode s : block.getSuccessors()) { PhiListAttr phiList = s.get(AType.PHI_LIST); - if (phiList != null) { - int j = s.getPredecessors().indexOf(block); - if (j == -1) { - throw new JadxRuntimeException("Can't find predecessor for " + block + " " + s); + if (phiList == null) { + continue; + } + int j = s.getPredecessors().indexOf(block); + if (j == -1) { + throw new JadxRuntimeException("Can't find predecessor for " + block + " " + s); + } + for (PhiInsn phiInsn : phiList.getList()) { + if (j >= phiInsn.getArgsCount()) { + continue; } - for (PhiInsn phiInsn : phiList.getList()) { - if (j >= phiInsn.getArgsCount()) { - continue; - } - int regNum = phiInsn.getResult().getRegNum(); - SSAVar var = vars[regNum]; - if (var == null) { - var = mth.makeNewSVar(regNum, vers, null); - vars[regNum] = var; - } - var.use(phiInsn.getArg(j)); - var.setUsedInPhi(phiInsn); + int regNum = phiInsn.getResult().getRegNum(); + SSAVar var = vars[regNum]; + if (var == null) { + continue; } + var.use(phiInsn.getArg(j)); + var.setUsedInPhi(phiInsn); } } for (BlockNode domOn : block.getDominatesOn()) { @@ -231,7 +232,10 @@ public class SSATransform extends AbstractVisitor { for (PhiInsn phiInsn : insnToRemove) { if (list.remove(phiInsn)) { for (InsnArg arg : phiInsn.getArguments()) { - ((RegisterArg) arg).getSVar().setUsedInPhi(null); + SSAVar sVar = ((RegisterArg) arg).getSVar(); + if (sVar != null) { + sVar.setUsedInPhi(null); + } } InstructionRemover.remove(mth, block, phiInsn); } 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 1dc1dd862..172c793fb 100644 --- a/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java @@ -23,6 +23,8 @@ import java.util.LinkedList; import java.util.List; import java.util.Set; +import org.jetbrains.annotations.Nullable; + public class BlockUtils { private BlockUtils() { @@ -112,12 +114,17 @@ public class BlockUtils { } public static boolean checkLastInsnType(BlockNode block, InsnType expectedType) { + InsnNode insn = getLastInsn(block); + return insn != null && insn.getType() == expectedType; + } + + @Nullable + public static InsnNode getLastInsn(BlockNode block) { List insns = block.getInstructions(); if (insns.isEmpty()) { - return false; + return null; } - InsnNode insn = insns.get(insns.size() - 1); - return insn.getType() == expectedType; + return insns.get(insns.size() - 1); } public static BlockNode getBlockByInsn(MethodNode mth, InsnNode insn) { @@ -135,7 +142,7 @@ public class BlockUtils { return null; } - private static BlockNode searchBlockWithPhi(MethodNode mth, PhiInsn insn) { + public static BlockNode searchBlockWithPhi(MethodNode mth, PhiInsn insn) { for (BlockNode block : mth.getBasicBlocks()) { PhiListAttr phiListAttr = block.get(AType.PHI_LIST); if (phiListAttr != null) { @@ -225,7 +232,11 @@ public class BlockUtils { } public static List bitSetToBlocks(MethodNode mth, BitSet bs) { - List blocks = new ArrayList(bs.cardinality()); + int size = bs.cardinality(); + if (size == 0) { + return Collections.emptyList(); + } + List blocks = new ArrayList(size); for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) { BlockNode block = mth.getBasicBlocks().get(i); blocks.add(block); diff --git a/jadx-core/src/main/java/jadx/core/utils/RegionUtils.java b/jadx-core/src/main/java/jadx/core/utils/RegionUtils.java index 77b9206be..aae65d2f6 100644 --- a/jadx-core/src/main/java/jadx/core/utils/RegionUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/RegionUtils.java @@ -183,10 +183,6 @@ public class RegionUtils { return true; } } - if (tb.getFinalRegion() != null - && isRegionContainsRegion(tb.getFinalRegion(), region)) { - return true; - } } if (isRegionContainsRegion(b, region)) { return true; diff --git a/jadx-core/src/test/java/jadx/tests/integration/loops/TestDoWhileBreak.java b/jadx-core/src/test/java/jadx/tests/integration/loops/TestDoWhileBreak.java new file mode 100644 index 000000000..50200e3d5 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/loops/TestDoWhileBreak.java @@ -0,0 +1,36 @@ +package jadx.tests.integration.loops; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.IntegrationTest; + +import org.junit.Test; + +import static jadx.tests.api.utils.JadxMatchers.containsOne; +import static org.junit.Assert.assertThat; + +public class TestDoWhileBreak extends IntegrationTest { + + public static class TestCls { + + public int test(int k) throws InterruptedException { + int i = 3; + do { + if (k > 9) { + i = 0; + break; + } + i++; + } while (i < 5); + + return i; + } + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("while (")); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/synchronize/TestSynchronized.java b/jadx-core/src/test/java/jadx/tests/integration/synchronize/TestSynchronized.java index bb867574d..1042ca629 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/synchronize/TestSynchronized.java +++ b/jadx-core/src/test/java/jadx/tests/integration/synchronize/TestSynchronized.java @@ -38,5 +38,8 @@ public class TestSynchronized extends IntegrationTest { assertThat(code, containsString("synchronized (this.o) {")); assertThat(code, not(containsString(indent(3) + ";"))); + assertThat(code, not(containsString("try {"))); + assertThat(code, not(containsString("} catch (Throwable th) {"))); + assertThat(code, not(containsString("throw th;"))); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestFinallyExtract.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestFinallyExtract.java new file mode 100644 index 000000000..cc008d4f2 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestFinallyExtract.java @@ -0,0 +1,43 @@ +package jadx.tests.integration.trycatch; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.IntegrationTest; + +import java.io.IOException; + +import org.junit.Test; + +import static jadx.tests.api.utils.JadxMatchers.containsOne; +import static org.junit.Assert.assertThat; + +public class TestFinallyExtract extends IntegrationTest { + + public static class TestCls { + + public String test() throws IOException { + boolean success = false; + try { + String value = test(); + success = true; + return value; + } finally { + if (!success) { + test(); + } + } + } + } + + @Test + public void test() { + setOutputCFG(); + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("success = true;")); + assertThat(code, containsOne("return value;")); + assertThat(code, containsOne("try {")); + assertThat(code, containsOne("} finally {")); + assertThat(code, containsOne("if (!success) {")); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch3.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch3.java index e0c7a1cd3..c8b1e09ad 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch3.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch3.java @@ -6,27 +6,47 @@ import jadx.tests.api.IntegrationTest; import org.junit.Test; import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.not; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; public class TestTryCatch3 extends IntegrationTest { public static class TestCls { - private final static Object obj = new Object(); - private boolean mDiscovering; + private int f = 0; private boolean test(Object obj) { - this.mDiscovering = false; + boolean res; try { - exc(obj); + res = exc(obj); } catch (Exception e) { - e.toString(); + res = false; } finally { - mDiscovering = true; + f++; } - return mDiscovering; + return res; } - private void exc(Object obj) throws Exception { + private boolean exc(Object obj) throws Exception { + if ("r".equals(obj)) { + throw new AssertionError(); + } + return true; + } + + public void check() { + f = 0; + assertTrue(test(null)); + assertEquals(1, f); + + f = 0; + try { + test("r"); + } catch (AssertionError e) { + // pass + } + assertEquals(1, f); } } @@ -38,5 +58,16 @@ public class TestTryCatch3 extends IntegrationTest { assertThat(code, containsString("try {")); assertThat(code, containsString("exc(obj);")); assertThat(code, containsString("} catch (Exception e) {")); + + assertThat(code, not(containsString("throw th;"))); + } + + @Test + public void test2() { + noDebugInfo(); + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + + assertThat(code, not(containsString("throw th;"))); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally.java new file mode 100644 index 000000000..67275e5b2 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally.java @@ -0,0 +1,54 @@ +package jadx.tests.integration.trycatch; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.IntegrationTest; + +import org.junit.Test; + +import static jadx.tests.api.utils.JadxMatchers.containsOne; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +public class TestTryCatchFinally extends IntegrationTest { + + public static class TestCls { + public boolean f; + + private boolean test(Object obj) { + this.f = false; + try { + exc(obj); + } catch (Exception e) { + e.getMessage(); + } finally { + f = true; + } + return f; + } + + private static boolean exc(Object obj) throws Exception { + if (obj == null) { + throw new Exception("test"); + } + return (obj instanceof String); + } + + public void check() { + assertTrue(test("a")); + assertTrue(test(null)); + } + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("exc(obj);")); + assertThat(code, containsOne("} catch (Exception e) {")); + assertThat(code, containsOne("e.getMessage();")); + assertThat(code, containsOne("} finally {")); + assertThat(code, containsOne("f = true;")); + assertThat(code, containsOne("return this.f;")); + } +}