From 7c0671c81b1512bf5b56fc4962f89be72d413f27 Mon Sep 17 00:00:00 2001 From: Skylot Date: Tue, 10 Aug 2021 13:07:05 +0300 Subject: [PATCH] feat: rewrite try-catch processing --- .../src/main/java/jadx/api/JadxArgs.java | 9 + .../java/jadx/api/data/impl/JadxNodeRef.java | 2 +- jadx-core/src/main/java/jadx/core/Consts.java | 1 + jadx-core/src/main/java/jadx/core/Jadx.java | 24 +- .../main/java/jadx/core/codegen/ClassGen.java | 2 +- .../java/jadx/core/codegen/MethodGen.java | 4 +- .../java/jadx/core/codegen/RegionGen.java | 8 +- .../java/jadx/core/dex/attributes/AFlag.java | 4 + .../java/jadx/core/dex/attributes/AType.java | 11 +- .../jadx/core/dex/attributes/AttrList.java | 3 +- .../jadx/core/dex/attributes/AttrNode.java | 5 + .../dex/attributes/nodes/EdgeInsnAttr.java | 7 +- .../dex/attributes/nodes/IgnoreEdgeAttr.java | 32 -- .../core/dex/attributes/nodes/LoopInfo.java | 8 +- .../dex/attributes/nodes/TmpEdgeAttr.java | 29 + .../core/dex/instructions/args/InsnArg.java | 7 + .../dex/instructions/args/RegisterArg.java | 4 + .../java/jadx/core/dex/nodes/BlockNode.java | 26 +- .../java/jadx/core/dex/nodes/InsnNode.java | 1 + .../java/jadx/core/dex/nodes/MethodNode.java | 59 +- .../jadx/core/dex/regions/TryCatchRegion.java | 8 +- .../core/dex/regions/conditions/IfInfo.java | 42 +- .../jadx/core/dex/trycatch/CatchAttr.java | 34 +- .../core/dex/trycatch/ExcHandlerAttr.java | 12 +- .../core/dex/trycatch/ExceptionHandler.java | 43 +- .../core/dex/trycatch/SplitterBlockAttr.java | 28 - .../jadx/core/dex/trycatch/TryCatchBlock.java | 189 ------ .../core/dex/trycatch/TryCatchBlockAttr.java | 177 ++++++ .../dex/visitors/AttachMethodDetails.java | 16 +- .../dex/visitors/AttachTryCatchVisitor.java | 148 ++--- .../jadx/core/dex/visitors/CheckCode.java | 33 ++ .../jadx/core/dex/visitors/ClassModifier.java | 22 +- .../core/dex/visitors/ConstInlineVisitor.java | 35 +- .../core/dex/visitors/ConstructorVisitor.java | 19 +- .../jadx/core/dex/visitors/EnumVisitor.java | 9 +- .../core/dex/visitors/ExtractFieldInit.java | 10 +- .../dex/visitors/FallbackModeVisitor.java | 4 +- .../dex/visitors/MarkMethodsForInline.java | 45 +- .../core/dex/visitors/MoveInlineVisitor.java | 41 +- .../core/dex/visitors/PrepareForCodeGen.java | 2 +- .../visitors/ProcessInstructionsVisitor.java | 10 +- .../core/dex/visitors/SimplifyVisitor.java | 13 +- .../blocks/BlockExceptionHandler.java | 544 ++++++++++++++++++ .../BlockProcessor.java | 290 ++++------ .../BlockSplitter.java | 300 +++++----- .../blocksmaker/BlockExceptionHandler.java | 136 ----- .../dex/visitors/blocksmaker/BlockFinish.java | 72 --- .../debuginfo/DebugInfoApplyVisitor.java | 4 +- .../debuginfo/DebugInfoAttachVisitor.java | 2 +- .../FinallyExtractInfo.java | 2 +- .../helpers => finaly}/InsnsSlice.java | 2 +- .../{ => finaly}/MarkFinallyVisitor.java | 218 ++++--- .../dex/visitors/regions/IfMakerHelper.java | 119 ++-- .../visitors/regions/LoopRegionVisitor.java | 1 + .../regions/ProcessTryCatchRegions.java | 103 +--- .../dex/visitors/regions/RegionMaker.java | 116 ++-- .../visitors/regions/RegionMakerVisitor.java | 4 +- .../dex/visitors/regions/ReturnVisitor.java | 1 - .../core/dex/visitors/regions/TernaryMod.java | 3 +- .../visitors/shrink/CodeShrinkVisitor.java | 11 +- .../core/dex/visitors/ssa/SSATransform.java | 4 +- .../typeinference/TypeInferenceVisitor.java | 2 +- .../visitors/typeinference/TypeUpdate.java | 23 +- .../main/java/jadx/core/utils/BlockUtils.java | 470 ++++++++++++--- .../java/jadx/core/utils/DebugChecks.java | 53 ++ .../main/java/jadx/core/utils/DebugUtils.java | 7 +- .../java/jadx/core/utils/InsnRemover.java | 24 +- .../main/java/jadx/core/utils/InsnUtils.java | 2 +- .../main/java/jadx/core/utils/ListUtils.java | 53 ++ .../java/jadx/core/utils/RegionUtils.java | 51 +- .../api/utils/assertj/JadxCodeAssertions.java | 31 +- .../jadx/tests/external/BaseExternalTest.java | 6 +- .../arrays/TestArrayInitField.java | 14 +- .../integration/conditions/TestTernary4.java | 16 +- .../TestTernaryOneBranchInConstructor.java | 1 + .../loops/TestBreakInComplexIf.java | 14 +- .../loops/TestContinueInLoop2.java | 73 --- .../integration/loops/TestLoopDetection.java | 7 + .../integration/loops/TestLoopRestore.java | 5 +- .../integration/loops/TestNestedLoops3.java | 2 +- .../integration/loops/TestNestedLoops4.java | 11 +- .../loops/TestSynchronizedInEndlessLoop.java | 4 +- .../integration/others/TestDuplicateCast.java | 3 +- .../integration/others/TestFieldInit2.java | 1 - .../others/TestOverridePrivateMethod.java | 12 +- .../synchronize/TestSynchronized3.java | 2 +- .../synchronize/TestSynchronized6.java | 44 ++ .../integration/trycatch/TestEmptyCatch.java | 22 + .../trycatch/TestEmptyFinally.java | 17 +- .../integration/trycatch/TestFinally3.java | 9 - .../trycatch/TestFinallyExtract.java | 2 +- .../trycatch/TestLoopInTryCatch.java | 18 +- .../trycatch/TestNestedTryCatch.java | 6 +- .../integration/trycatch/TestTryCatch7.java | 1 - .../trycatch/TestTryCatchFinally.java | 33 +- .../trycatch/TestTryCatchFinally10.java | 21 +- .../trycatch/TestTryCatchFinally3.java | 2 +- .../trycatch/TestTryCatchFinally5.java | 22 +- .../trycatch/TestTryWithEmptyCatchTriple.java | 16 +- .../integration/types/TestTypeResolver17.java | 42 ++ .../integration/types/TestTypeResolver3.java | 15 +- .../variables/TestVariablesGeneric.java | 17 +- .../smali/synchronize/TestSynchronized6.smali | 70 +++ .../test/smali/trycatch/TestEmptyCatch.smali | 103 ++++ .../test/smali/types/TestTypeResolver17.smali | 112 ++++ .../jadx/gui/device/debugger/smali/Smali.java | 14 +- .../plugins/input/java/JavaFileLoader.java | 6 +- .../input/java/data/code/JavaCodeReader.java | 10 +- .../java/data/code/trycatch/JavaTryData.java | 25 +- .../jadx/api/plugins/input/data/ICatch.java | 4 +- .../jadx/api/plugins/input/data/ITry.java | 4 +- .../plugins/input/data/impl/CatchData.java | 25 +- .../api/plugins/input/data/impl/TryData.java | 21 +- .../java/jadx/api/plugins/utils/Utils.java | 25 +- 114 files changed, 2901 insertions(+), 1810 deletions(-) delete mode 100644 jadx-core/src/main/java/jadx/core/dex/attributes/nodes/IgnoreEdgeAttr.java create mode 100644 jadx-core/src/main/java/jadx/core/dex/attributes/nodes/TmpEdgeAttr.java delete mode 100644 jadx-core/src/main/java/jadx/core/dex/trycatch/SplitterBlockAttr.java delete mode 100644 jadx-core/src/main/java/jadx/core/dex/trycatch/TryCatchBlock.java create mode 100644 jadx-core/src/main/java/jadx/core/dex/trycatch/TryCatchBlockAttr.java create mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockExceptionHandler.java rename jadx-core/src/main/java/jadx/core/dex/visitors/{blocksmaker => blocks}/BlockProcessor.java (73%) rename jadx-core/src/main/java/jadx/core/dex/visitors/{blocksmaker => blocks}/BlockSplitter.java (60%) delete mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockExceptionHandler.java delete mode 100644 jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockFinish.java rename jadx-core/src/main/java/jadx/core/dex/visitors/{blocksmaker/helpers => finaly}/FinallyExtractInfo.java (95%) rename jadx-core/src/main/java/jadx/core/dex/visitors/{blocksmaker/helpers => finaly}/InsnsSlice.java (97%) rename jadx-core/src/main/java/jadx/core/dex/visitors/{ => finaly}/MarkFinallyVisitor.java (67%) create mode 100644 jadx-core/src/main/java/jadx/core/utils/ListUtils.java delete mode 100644 jadx-core/src/test/java/jadx/tests/integration/loops/TestContinueInLoop2.java create mode 100644 jadx-core/src/test/java/jadx/tests/integration/synchronize/TestSynchronized6.java create mode 100644 jadx-core/src/test/java/jadx/tests/integration/trycatch/TestEmptyCatch.java create mode 100644 jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver17.java create mode 100644 jadx-core/src/test/smali/synchronize/TestSynchronized6.smali create mode 100644 jadx-core/src/test/smali/trycatch/TestEmptyCatch.smali create mode 100644 jadx-core/src/test/smali/types/TestTypeResolver17.smali diff --git a/jadx-core/src/main/java/jadx/api/JadxArgs.java b/jadx-core/src/main/java/jadx/api/JadxArgs.java index a37c4bc11..0c01c0465 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgs.java @@ -41,6 +41,7 @@ public class JadxArgs { private boolean useImports = true; private boolean debugInfo = true; private boolean insertDebugLines = false; + private boolean extractFinally = true; private boolean inlineAnonymousClasses = true; private boolean inlineMethods = true; @@ -208,6 +209,14 @@ public class JadxArgs { this.inlineMethods = inlineMethods; } + public boolean isExtractFinally() { + return extractFinally; + } + + public void setExtractFinally(boolean extractFinally) { + this.extractFinally = extractFinally; + } + public boolean isSkipResources() { return skipResources; } diff --git a/jadx-core/src/main/java/jadx/api/data/impl/JadxNodeRef.java b/jadx-core/src/main/java/jadx/api/data/impl/JadxNodeRef.java index 50d4452cd..895541fb8 100644 --- a/jadx-core/src/main/java/jadx/api/data/impl/JadxNodeRef.java +++ b/jadx-core/src/main/java/jadx/api/data/impl/JadxNodeRef.java @@ -12,7 +12,7 @@ import jadx.api.JavaMethod; import jadx.api.JavaNode; import jadx.api.data.IJavaNodeRef; -public class JadxNodeRef implements IJavaNodeRef, Comparable { +public class JadxNodeRef implements IJavaNodeRef { @Nullable public static JadxNodeRef forJavaNode(JavaNode javaNode) { diff --git a/jadx-core/src/main/java/jadx/core/Consts.java b/jadx-core/src/main/java/jadx/core/Consts.java index 779f851db..f129de01b 100644 --- a/jadx-core/src/main/java/jadx/core/Consts.java +++ b/jadx-core/src/main/java/jadx/core/Consts.java @@ -6,6 +6,7 @@ public class Consts { public static final boolean DEBUG_USAGE = false; public static final boolean DEBUG_TYPE_INFERENCE = false; public static final boolean DEBUG_OVERLOADED_CASTS = false; + public static final boolean DEBUG_EXC_HANDLERS = false; public static final String CLASS_OBJECT = "java.lang.Object"; public static final String CLASS_STRING = "java.lang.String"; diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index 2759685b8..16e7ae9fd 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -28,7 +28,6 @@ import jadx.core.dex.visitors.GenericTypesVisitor; import jadx.core.dex.visitors.IDexTreeVisitor; import jadx.core.dex.visitors.InitCodeVariables; import jadx.core.dex.visitors.InlineMethods; -import jadx.core.dex.visitors.MarkFinallyVisitor; import jadx.core.dex.visitors.MarkMethodsForInline; import jadx.core.dex.visitors.MethodInvokeVisitor; import jadx.core.dex.visitors.ModVisitor; @@ -42,12 +41,11 @@ import jadx.core.dex.visitors.RenameVisitor; import jadx.core.dex.visitors.ShadowFieldVisitor; import jadx.core.dex.visitors.SignatureProcessor; import jadx.core.dex.visitors.SimplifyVisitor; -import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler; -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.blocks.BlockProcessor; +import jadx.core.dex.visitors.blocks.BlockSplitter; import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor; import jadx.core.dex.visitors.debuginfo.DebugInfoAttachVisitor; +import jadx.core.dex.visitors.finaly.MarkFinallyVisitor; import jadx.core.dex.visitors.regions.CheckRegions; import jadx.core.dex.visitors.regions.CleanRegions; import jadx.core.dex.visitors.regions.IfRegionVisitor; @@ -95,31 +93,32 @@ public class Jadx { if (args.isFallbackMode()) { return getFallbackPassesList(); } - List passes = new ArrayList<>(); + + // instructions IR passes.add(new CheckCode()); if (args.isDebugInfo()) { passes.add(new DebugInfoAttachVisitor()); } passes.add(new AttachTryCatchVisitor()); passes.add(new AttachCommentsVisitor()); + passes.add(new AttachMethodDetails()); passes.add(new ProcessInstructionsVisitor()); + // blocks IR passes.add(new BlockSplitter()); + passes.add(new BlockProcessor()); if (args.isRawCFGOutput()) { passes.add(DotGraphVisitor.dumpRaw()); } - passes.add(new BlockProcessor()); - passes.add(new BlockExceptionHandler()); - passes.add(new BlockFinish()); - - passes.add(new AttachMethodDetails()); passes.add(new SSATransform()); passes.add(new MoveInlineVisitor()); passes.add(new ConstructorVisitor()); passes.add(new InitCodeVariables()); - passes.add(new MarkFinallyVisitor()); + if (args.isExtractFinally()) { + passes.add(new MarkFinallyVisitor()); + } passes.add(new ConstInlineVisitor()); passes.add(new TypeInferenceVisitor()); if (args.isDebugInfo()) { @@ -138,6 +137,7 @@ public class Jadx { passes.add(DotGraphVisitor.dump()); } + // regions IR passes.add(new RegionMakerVisitor()); passes.add(new IfRegionVisitor()); passes.add(new ReturnVisitor()); diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index c4d62c600..a5f661d30 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -324,12 +324,12 @@ public class ClassGen { public void addMethodCode(ICodeWriter code, MethodNode mth) throws CodegenException { CodeGenUtils.addComments(code, mth); + insertDecompilationProblems(code, mth); if (mth.isNoCode()) { MethodGen mthGen = new MethodGen(this, mth); mthGen.addDefinition(code); code.add(';'); } else { - insertDecompilationProblems(code, mth); boolean badCode = mth.contains(AFlag.INCONSISTENT_CODE); if (badCode && showInconsistentCode) { mth.remove(AFlag.INCONSISTENT_CODE); diff --git a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java index 5e91a83c5..dc33bd17a 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java @@ -143,7 +143,7 @@ public class MethodGen { } else if (args.size() > 2) { args = args.subList(2, args.size()); } else { - mth.addComment("JADX WARN: Incorrect number of args for enum constructor: " + args.size() + " (expected >= 2)"); + mth.addWarnComment("Incorrect number of args for enum constructor: " + args.size() + " (expected >= 2)"); } } else if (mth.contains(AFlag.SKIP_FIRST_ARG)) { args = args.subList(1, args.size()); @@ -382,7 +382,7 @@ public class MethodGen { code.incIndent(); } - CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK); + CatchAttr catchAttr = insn.get(AType.EXC_CATCH); if (catchAttr != null) { code.add(" // " + catchAttr); } 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 b0f21d41f..9fb3e3f5d 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/RegionGen.java @@ -176,11 +176,11 @@ public class RegionGen extends InsnGen { } public void makeLoop(LoopRegion region, ICodeWriter code) throws CodegenException { + code.startLineWithNum(region.getConditionSourceLine()); LoopLabelAttr labelAttr = region.getInfo().getStart().get(AType.LOOP_LABEL); if (labelAttr != null) { - code.startLine(mgen.getNameGen().getLoopLabel(labelAttr)).add(':'); + code.add(mgen.getNameGen().getLoopLabel(labelAttr)).add(": "); } - code.startLineWithNum(region.getConditionSourceLine()); IfCondition condition = region.getCondition(); if (condition == null) { @@ -307,7 +307,7 @@ public class RegionGen extends InsnGen { public void makeTryCatch(TryCatchRegion region, ICodeWriter code) throws CodegenException { code.startLine("try {"); - InsnNode insn = Utils.first(region.getTryCatchBlock().getInsns()); + InsnNode insn = BlockUtils.getFirstInsn(Utils.first(region.getTryCatchBlock().getBlocks())); InsnCodeOffset.attach(code, insn); CodeGenUtils.addCodeComments(code, insn); @@ -387,7 +387,7 @@ public class RegionGen extends InsnGen { } code.add(") {"); - InsnCodeOffset.attach(code, handler.getHandleOffset()); + InsnCodeOffset.attach(code, handler.getHandlerOffset()); CodeGenUtils.addCodeComments(code, handler.getHandlerBlock()); makeRegionIndent(code, region); 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 508705013..d0deb2cb8 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 @@ -2,6 +2,8 @@ package jadx.core.dex.attributes; public enum AFlag { MTH_ENTER_BLOCK, + MTH_EXIT_BLOCK, + TRY_ENTER, TRY_LEAVE, @@ -25,6 +27,8 @@ public enum AFlag { DONT_RENAME, // do not rename during deobfuscation ADDED_TO_REGION, + EXC_TOP_SPLITTER, + EXC_BOTTOM_SPLITTER, FINALLY_INSNS, SKIP_FIRST_ARG, 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 1001e2e39..a8264ed66 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 @@ -10,7 +10,6 @@ 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.GenericInfoAttr; -import jadx.core.dex.attributes.nodes.IgnoreEdgeAttr; import jadx.core.dex.attributes.nodes.JadxError; import jadx.core.dex.attributes.nodes.JumpInfo; import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr; @@ -23,10 +22,11 @@ import jadx.core.dex.attributes.nodes.PhiListAttr; import jadx.core.dex.attributes.nodes.RegDebugInfoAttr; import jadx.core.dex.attributes.nodes.RenameReasonAttr; import jadx.core.dex.attributes.nodes.SkipMethodArgsAttr; +import jadx.core.dex.attributes.nodes.TmpEdgeAttr; import jadx.core.dex.nodes.IMethodDetails; import jadx.core.dex.trycatch.CatchAttr; import jadx.core.dex.trycatch.ExcHandlerAttr; -import jadx.core.dex.trycatch.SplitterBlockAttr; +import jadx.core.dex.trycatch.TryCatchBlockAttr; /** * Attribute types enumeration, @@ -62,21 +62,22 @@ public final class AType implements IJadxAttrType { public static final AType SKIP_MTH_ARGS = new AType<>(); public static final AType METHOD_OVERRIDE = new AType<>(); public static final AType METHOD_TYPE_VARS = new AType<>(); + public static final AType> TRY_BLOCKS_LIST = new AType<>(); // region public static final AType DECLARE_VARIABLES = new AType<>(); // block public static final AType PHI_LIST = new AType<>(); - public static final AType IGNORE_EDGE = new AType<>(); public static final AType FORCE_RETURN = new AType<>(); - public static final AType CATCH_BLOCK = new AType<>(); - public static final AType SPLITTER_BLOCK = new AType<>(); public static final AType> LOOP = new AType<>(); public static final AType> EDGE_INSN = new AType<>(); + public static final AType TMP_EDGE = new AType<>(); + public static final AType TRY_BLOCK = new AType<>(); // block or insn public static final AType EXC_HANDLER = new AType<>(); + public static final AType EXC_CATCH = new AType<>(); // instruction public static final AType LOOP_LABEL = new AType<>(); diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AttrList.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AttrList.java index df5f1bca6..8b976753b 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AttrList.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AttrList.java @@ -3,7 +3,6 @@ package jadx.core.dex.attributes; import java.util.ArrayList; import java.util.List; -import jadx.api.ICodeWriter; import jadx.api.plugins.input.data.attributes.IJadxAttrType; import jadx.api.plugins.input.data.attributes.IJadxAttribute; import jadx.core.utils.Utils; @@ -28,6 +27,6 @@ public class AttrList implements IJadxAttribute { @Override public String toString() { - return Utils.listToString(list, ICodeWriter.NL); + return Utils.listToString(list, ", "); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java index 268c28e67..d1f411d54 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AttrNode.java @@ -32,6 +32,11 @@ public abstract class AttrNode implements IAttributeNode { initStorage().add(type, obj); } + public void addAttr(IJadxAttrType> type, List list) { + AttributeStorage strg = initStorage(); + list.forEach(attr -> strg.add(type, attr)); + } + @Override public void copyAttributesFrom(AttrNode attrNode) { AttributeStorage copyFrom = attrNode.storage; diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/EdgeInsnAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/EdgeInsnAttr.java index f90b66e36..480ce1b98 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/EdgeInsnAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/EdgeInsnAttr.java @@ -6,6 +6,7 @@ import jadx.api.plugins.input.data.attributes.IJadxAttribute; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AttrList; import jadx.core.dex.nodes.BlockNode; +import jadx.core.dex.nodes.Edge; import jadx.core.dex.nodes.InsnNode; public class EdgeInsnAttr implements IJadxAttribute { @@ -14,6 +15,10 @@ public class EdgeInsnAttr implements IJadxAttribute { private final BlockNode end; private final InsnNode insn; + public static void addEdgeInsn(Edge edge, InsnNode insn) { + addEdgeInsn(edge.getSource(), edge.getTarget(), insn); + } + public static void addEdgeInsn(BlockNode start, BlockNode end, InsnNode insn) { EdgeInsnAttr edgeInsnAttr = new EdgeInsnAttr(start, end, insn); if (!start.getAll(AType.EDGE_INSN).contains(edgeInsnAttr)) { @@ -24,7 +29,7 @@ public class EdgeInsnAttr implements IJadxAttribute { } } - public EdgeInsnAttr(BlockNode start, BlockNode end, InsnNode insn) { + private EdgeInsnAttr(BlockNode start, BlockNode end, InsnNode insn) { this.start = start; this.end = end; this.insn = insn; 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 deleted file mode 100644 index 9100becc1..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/IgnoreEdgeAttr.java +++ /dev/null @@ -1,32 +0,0 @@ -package jadx.core.dex.attributes.nodes; - -import java.util.HashSet; -import java.util.Set; - -import jadx.api.plugins.input.data.attributes.IJadxAttribute; -import jadx.core.dex.attributes.AType; -import jadx.core.dex.nodes.BlockNode; -import jadx.core.utils.Utils; - -public class IgnoreEdgeAttr implements IJadxAttribute { - - private final Set blocks = new HashSet<>(3); - - public Set getBlocks() { - return blocks; - } - - public boolean contains(BlockNode block) { - return blocks.contains(block); - } - - @Override - public AType getAttrType() { - 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/attributes/nodes/LoopInfo.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LoopInfo.java index 35935153e..ac473c9c8 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LoopInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/LoopInfo.java @@ -1,8 +1,8 @@ package jadx.core.dex.attributes.nodes; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -60,11 +60,11 @@ public class LoopInfo { * Return loop exit edges. */ public List getExitEdges() { - List edges = new LinkedList<>(); + List edges = new ArrayList<>(); Set blocks = getLoopBlocks(); for (BlockNode block : blocks) { - for (BlockNode s : block.getSuccessors()) { - if (!blocks.contains(s) && !s.contains(AType.EXC_HANDLER)) { + for (BlockNode s : block.getSuccessors()) { // don't use clean successors to include loop back edges + if (!blocks.contains(s) && !BlockUtils.isExceptionHandlerPath(s)) { edges.add(new Edge(block, s)); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/TmpEdgeAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/TmpEdgeAttr.java new file mode 100644 index 000000000..c89fd3001 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/TmpEdgeAttr.java @@ -0,0 +1,29 @@ +package jadx.core.dex.attributes.nodes; + +import jadx.api.plugins.input.data.attributes.IJadxAttrType; +import jadx.api.plugins.input.data.attributes.IJadxAttribute; +import jadx.core.dex.attributes.AType; +import jadx.core.dex.nodes.BlockNode; + +public class TmpEdgeAttr implements IJadxAttribute { + + private final BlockNode block; + + public TmpEdgeAttr(BlockNode block) { + this.block = block; + } + + public BlockNode getBlock() { + return block; + } + + @Override + public IJadxAttrType getAttrType() { + return AType.TMP_EDGE; + } + + @Override + public String toString() { + return "TMP_EDGE: " + block; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java index a5f5c5a19..bf16dd89c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java @@ -236,6 +236,13 @@ public abstract class InsnArg extends Typed { return isLiteral() || (isInsnWrap() && ((InsnWrapArg) this).getWrapInsn().isConstInsn()); } + public boolean isSameConst(InsnArg other) { + if (isConst() && other.isConst()) { + return this.equals(other); + } + return false; + } + protected final T copyCommonParams(T copy) { copy.copyAttributesFrom(this); copy.setParentInsn(parentInsn); diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java index 263417282..380fefba3 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java @@ -189,6 +189,10 @@ public class RegisterArg extends InsnArg implements Named { return this.getSVar().getCodeVar() == arg.getSVar().getCodeVar(); } + public boolean isLinkedToOtherSsaVars() { + return getSVar().getCodeVar().getSsaVars().size() > 1; + } + @Override public int hashCode() { return regNum; 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 8cac2c651..a92b9fdc3 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 @@ -9,7 +9,6 @@ import org.jetbrains.annotations.NotNull; 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.BlockUtils; import jadx.core.utils.EmptyBitSet; @@ -59,7 +58,7 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable getCleanSuccessors() { - return cleanSuccessors; + return this.cleanSuccessors; } public void updateCleanSuccessors() { @@ -67,12 +66,17 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable successorsList = successors; + successors = lockList(successorsList); + cleanSuccessors = successorsList == cleanSuccessors ? this.successors : lockList(cleanSuccessors); + predecessors = lockList(predecessors); + dominatesOn = lockList(dominatesOn); + if (domFrontier == null) { + throw new JadxRuntimeException("Dominance frontier not set for block: " + this); + } + } catch (Exception e) { + throw new JadxRuntimeException("Failed to lock block: " + this, e); } } @@ -86,7 +90,7 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable toRemove = new ArrayList<>(sucList.size()); for (BlockNode b : sucList) { - if (BlockUtils.isBlockMustBeCleared(b)) { + if (BlockUtils.isExceptionHandlerPath(b)) { toRemove.add(b); } } @@ -96,10 +100,6 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable blocks; private BlockNode enterBlock; - private List exitBlocks; + private BlockNode exitBlock; private List sVars; private List exceptionHandlers; private List loops; @@ -157,7 +156,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, instructions = null; blocks = null; enterBlock = null; - exitBlocks = null; + exitBlock = null; region = null; exceptionHandlers = Collections.emptyList(); loops = Collections.emptyList(); @@ -206,27 +205,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, } } - public void checkInstructions() { - List list = new ArrayList<>(); - for (InsnNode insnNode : instructions) { - if (insnNode == null) { - continue; - } - list.clear(); - RegisterArg resultArg = insnNode.getResult(); - if (resultArg != null) { - list.add(resultArg); - } - insnNode.getRegisterArgs(list); - for (RegisterArg arg : list) { - if (arg.getRegNum() >= regsCount) { - throw new JadxRuntimeException("Incorrect register number in instruction: " + insnNode - + ", expected to be less than " + regsCount); - } - } - } - } - public void reload() { unload(); try { @@ -372,12 +350,10 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, public void initBasicBlocks() { blocks = new ArrayList<>(); - exitBlocks = new ArrayList<>(1); } public void finishBasicBlocks() { blocks = lockList(blocks); - exitBlocks = lockList(exitBlocks); loops = lockList(loops); blocks.forEach(BlockNode::lock); } @@ -394,12 +370,16 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, this.enterBlock = enterBlock; } - public List getExitBlocks() { - return exitBlocks; + public BlockNode getExitBlock() { + return exitBlock; } - public void addExitBlock(BlockNode exitBlock) { - this.exitBlocks.add(exitBlock); + public void setExitBlock(BlockNode exitBlock) { + this.exitBlock = exitBlock; + } + + public List getPreExitBlocks() { + return exitBlock.getPredecessors(); } public void registerLoop(LoopInfo loop) { @@ -447,23 +427,6 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, public ExceptionHandler addExceptionHandler(ExceptionHandler handler) { if (exceptionHandlers.isEmpty()) { exceptionHandlers = new ArrayList<>(2); - } else { - for (ExceptionHandler h : exceptionHandlers) { - if (h.equals(handler)) { - return h; - } - if (h.getHandleOffset() == handler.getHandleOffset()) { - if (h.getTryBlock() == handler.getTryBlock()) { - for (ClassInfo catchType : handler.getCatchTypes()) { - h.addCatchType(catchType); - } - } else { - // same handlers from different try blocks - // will merge later - } - return h; - } - } } exceptionHandlers.add(handler); return handler; @@ -550,7 +513,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails, return var; } - public int getNextSVarVersion(int regNum) { + private int getNextSVarVersion(int regNum) { int v = -1; for (SSAVar sVar : sVars) { if (sVar.getRegNum() == regNum) { 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 7bd6aa788..0de29ff1d 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 @@ -12,7 +12,7 @@ import jadx.core.dex.nodes.IBranchRegion; import jadx.core.dex.nodes.IContainer; import jadx.core.dex.nodes.IRegion; import jadx.core.dex.trycatch.ExceptionHandler; -import jadx.core.dex.trycatch.TryCatchBlock; +import jadx.core.dex.trycatch.TryCatchBlockAttr; import jadx.core.utils.Utils; import jadx.core.utils.exceptions.CodegenException; @@ -21,14 +21,14 @@ public final class TryCatchRegion extends AbstractRegion implements IBranchRegio private final IContainer tryRegion; private Map catchRegions = Collections.emptyMap(); private IContainer finallyRegion; - private TryCatchBlock tryCatchBlock; + private TryCatchBlockAttr tryCatchBlock; public TryCatchRegion(IRegion parent, IContainer tryRegion) { super(parent); this.tryRegion = tryRegion; } - public void setTryCatchBlock(TryCatchBlock tryCatchBlock) { + public void setTryCatchBlock(TryCatchBlockAttr tryCatchBlock) { this.tryCatchBlock = tryCatchBlock; int count = tryCatchBlock.getHandlersCount(); this.catchRegions = new LinkedHashMap<>(count); @@ -52,7 +52,7 @@ public final class TryCatchRegion extends AbstractRegion implements IBranchRegio return catchRegions; } - public TryCatchBlock getTryCatchBlock() { + public TryCatchBlockAttr getTryCatchBlock() { return tryCatchBlock; } diff --git a/jadx-core/src/main/java/jadx/core/dex/regions/conditions/IfInfo.java b/jadx-core/src/main/java/jadx/core/dex/regions/conditions/IfInfo.java index 245207266..b7667e71f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/regions/conditions/IfInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/regions/conditions/IfInfo.java @@ -7,29 +7,30 @@ import java.util.Set; import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.InsnNode; +import jadx.core.dex.nodes.MethodNode; public final class IfInfo { + private final MethodNode mth; private final IfCondition condition; - private final Set mergedBlocks; + private final List mergedBlocks; private final BlockNode thenBlock; private final BlockNode elseBlock; private final Set skipBlocks; private final List forceInlineInsns; private BlockNode outBlock; - @Deprecated - private BlockNode ifBlock; - public IfInfo(IfCondition condition, BlockNode thenBlock, BlockNode elseBlock) { - this(condition, thenBlock, elseBlock, new HashSet<>(), new HashSet<>(), new ArrayList<>()); + public IfInfo(MethodNode mth, IfCondition condition, BlockNode thenBlock, BlockNode elseBlock) { + this(mth, condition, thenBlock, elseBlock, new ArrayList<>(), new HashSet<>(), new ArrayList<>()); } public IfInfo(IfInfo info, BlockNode thenBlock, BlockNode elseBlock) { - this(info.getCondition(), thenBlock, elseBlock, + this(info.getMth(), info.getCondition(), thenBlock, elseBlock, info.getMergedBlocks(), info.getSkipBlocks(), info.getForceInlineInsns()); } - private IfInfo(IfCondition condition, BlockNode thenBlock, BlockNode elseBlock, - Set mergedBlocks, Set skipBlocks, List forceInlineInsns) { + private IfInfo(MethodNode mth, IfCondition condition, BlockNode thenBlock, BlockNode elseBlock, + List mergedBlocks, Set skipBlocks, List forceInlineInsns) { + this.mth = mth; this.condition = condition; this.thenBlock = thenBlock; this.elseBlock = elseBlock; @@ -39,12 +40,10 @@ public final class IfInfo { } public static IfInfo invert(IfInfo info) { - IfCondition invertedCondition = IfCondition.invert(info.getCondition()); - IfInfo tmpIf = new IfInfo(invertedCondition, + return new IfInfo(info.getMth(), + IfCondition.invert(info.getCondition()), info.getElseBlock(), info.getThenBlock(), info.getMergedBlocks(), info.getSkipBlocks(), info.getForceInlineInsns()); - tmpIf.setIfBlock(info.getIfBlock()); - return tmpIf; } public void merge(IfInfo... arr) { @@ -55,11 +54,20 @@ public final class IfInfo { } } + @Deprecated + public BlockNode getFirstIfBlock() { + return mergedBlocks.get(0); + } + + public MethodNode getMth() { + return mth; + } + public IfCondition getCondition() { return condition; } - public Set getMergedBlocks() { + public List getMergedBlocks() { return mergedBlocks; } @@ -83,14 +91,6 @@ public final class IfInfo { this.outBlock = outBlock; } - public BlockNode getIfBlock() { - return ifBlock; - } - - public void setIfBlock(BlockNode ifBlock) { - this.ifBlock = ifBlock; - } - public List getForceInlineInsns() { return forceInlineInsns; } diff --git a/jadx-core/src/main/java/jadx/core/dex/trycatch/CatchAttr.java b/jadx-core/src/main/java/jadx/core/dex/trycatch/CatchAttr.java index 49a58e432..6dfc1b4c8 100644 --- a/jadx-core/src/main/java/jadx/core/dex/trycatch/CatchAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/trycatch/CatchAttr.java @@ -1,27 +1,47 @@ package jadx.core.dex.trycatch; +import java.util.List; + import jadx.api.plugins.input.data.attributes.IJadxAttribute; import jadx.core.dex.attributes.AType; +import jadx.core.utils.Utils; public class CatchAttr implements IJadxAttribute { - private final TryCatchBlock tryBlock; + private final List handlers; - public CatchAttr(TryCatchBlock block) { - this.tryBlock = block; + public CatchAttr(List handlers) { + this.handlers = handlers; + } + + public List getHandlers() { + return handlers; } @Override public AType getAttrType() { - return AType.CATCH_BLOCK; + return AType.EXC_CATCH; } - public TryCatchBlock getTryBlock() { - return tryBlock; + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof CatchAttr)) { + return false; + } + CatchAttr catchAttr = (CatchAttr) o; + return getHandlers().equals(catchAttr.getHandlers()); + } + + @Override + public int hashCode() { + return getHandlers().hashCode(); } @Override public String toString() { - return tryBlock.toString(); + return "Catch: " + Utils.listToString(getHandlers()); } } 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 081a6fbd9..668bec461 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 @@ -5,11 +5,9 @@ import jadx.core.dex.attributes.AType; public class ExcHandlerAttr implements IJadxAttribute { - private final TryCatchBlock tryBlock; private final ExceptionHandler handler; - public ExcHandlerAttr(TryCatchBlock block, ExceptionHandler handler) { - this.tryBlock = block; + public ExcHandlerAttr(ExceptionHandler handler) { this.handler = handler; } @@ -18,8 +16,8 @@ public class ExcHandlerAttr implements IJadxAttribute { return AType.EXC_HANDLER; } - public TryCatchBlock getTryBlock() { - return tryBlock; + public TryCatchBlockAttr getTryBlock() { + return handler.getTryBlock(); } public ExceptionHandler getHandler() { @@ -28,8 +26,6 @@ public class ExcHandlerAttr implements IJadxAttribute { @Override public String toString() { - return "ExcHandler: " + (handler.isFinally() - ? " FINALLY" - : handler.catchTypeStr() + ' ' + handler.getArg()); + return "ExcHandler: " + handler; } } 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 1eb086350..619bceb19 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 @@ -4,12 +4,11 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; -import java.util.Set; -import java.util.TreeSet; import org.jetbrains.annotations.Nullable; import jadx.core.Consts; +import jadx.core.dex.attributes.AFlag; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; @@ -21,21 +20,21 @@ import jadx.core.utils.exceptions.JadxRuntimeException; public class ExceptionHandler { - private final Set catchTypes = new TreeSet<>(); - private final int handleOffset; + private final List catchTypes = new ArrayList<>(1); + private final int handlerOffset; private BlockNode handlerBlock; private final List blocks = new ArrayList<>(); private IContainer handlerRegion; private InsnArg arg; - private TryCatchBlock tryBlock; + private TryCatchBlockAttr tryBlock; private boolean isFinally; private boolean removed = false; public ExceptionHandler(int addr, @Nullable ClassInfo type) { - this.handleOffset = addr; + this.handlerOffset = addr; addCatchType(type); } @@ -44,14 +43,17 @@ public class ExceptionHandler { * * @param type - null for 'all' or 'Throwable' handler */ - public void addCatchType(@Nullable ClassInfo type) { + public boolean addCatchType(@Nullable ClassInfo type) { if (type != null) { - this.catchTypes.add(type); - } else { - if (!this.catchTypes.isEmpty()) { - throw new JadxRuntimeException("Null type added to not empty exception handler: " + this); + if (catchTypes.contains(type)) { + return false; } + return catchTypes.add(type); } + if (!this.catchTypes.isEmpty()) { + throw new JadxRuntimeException("Null type added to not empty exception handler: " + this); + } + return false; } public void addCatchTypes(Collection types) { @@ -60,7 +62,7 @@ public class ExceptionHandler { } } - public Set getCatchTypes() { + public List getCatchTypes() { return catchTypes; } @@ -68,7 +70,7 @@ public class ExceptionHandler { if (isCatchAll()) { return ArgType.THROWABLE; } - Set types = getCatchTypes(); + List types = getCatchTypes(); if (types.size() == 1) { return types.iterator().next().getType(); } else { @@ -88,8 +90,8 @@ public class ExceptionHandler { return false; } - public int getHandleOffset() { - return handleOffset; + public int getHandlerOffset() { + return handlerOffset; } public BlockNode getHandlerBlock() { @@ -124,11 +126,11 @@ public class ExceptionHandler { this.arg = arg; } - public void setTryBlock(TryCatchBlock tryBlock) { + public void setTryBlock(TryCatchBlockAttr tryBlock) { this.tryBlock = tryBlock; } - public TryCatchBlock getTryBlock() { + public TryCatchBlockAttr getTryBlock() { return tryBlock; } @@ -146,6 +148,7 @@ public class ExceptionHandler { public void markForRemove() { this.removed = true; + this.blocks.forEach(b -> b.add(AFlag.REMOVE)); } @Override @@ -157,14 +160,14 @@ public class ExceptionHandler { return false; } ExceptionHandler that = (ExceptionHandler) o; - return handleOffset == that.handleOffset + return handlerOffset == that.handlerOffset && catchTypes.equals(that.catchTypes) && Objects.equals(tryBlock, that.tryBlock); } @Override public int hashCode() { - return Objects.hash(catchTypes, handleOffset /* , tryBlock */); + return Objects.hash(catchTypes, handlerOffset /* , tryBlock */); } public String catchTypeStr() { @@ -173,6 +176,6 @@ public class ExceptionHandler { @Override public String toString() { - return catchTypeStr() + " -> " + InsnUtils.formatOffset(handleOffset); + return catchTypeStr() + " -> " + InsnUtils.formatOffset(handlerOffset); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/trycatch/SplitterBlockAttr.java b/jadx-core/src/main/java/jadx/core/dex/trycatch/SplitterBlockAttr.java deleted file mode 100644 index 16ed8f143..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/trycatch/SplitterBlockAttr.java +++ /dev/null @@ -1,28 +0,0 @@ -package jadx.core.dex.trycatch; - -import jadx.api.plugins.input.data.attributes.IJadxAttribute; -import jadx.core.dex.attributes.AType; -import jadx.core.dex.nodes.BlockNode; - -public class SplitterBlockAttr implements IJadxAttribute { - - private final BlockNode block; - - public SplitterBlockAttr(BlockNode block) { - this.block = block; - } - - public BlockNode getBlock() { - return block; - } - - @Override - public AType getAttrType() { - return AType.SPLITTER_BLOCK; - } - - @Override - public String toString() { - return "Splitter:" + block; - } -} 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 deleted file mode 100644 index ccb978dbc..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/trycatch/TryCatchBlock.java +++ /dev/null @@ -1,189 +0,0 @@ -package jadx.core.dex.trycatch; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import org.jetbrains.annotations.Nullable; - -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.InsnNode; -import jadx.core.dex.nodes.MethodNode; -import jadx.core.utils.BlockUtils; -import jadx.core.utils.Utils; - -public class TryCatchBlock { - - private final List handlers; - - // references for fast remove/modify - private final List insns; - private final CatchAttr attr; - - public TryCatchBlock(int handlersCount) { - handlers = new ArrayList<>(handlersCount); - insns = new ArrayList<>(); - attr = new CatchAttr(this); - } - - public Iterable getHandlers() { - return handlers; - } - - public int getHandlersCount() { - return handlers.size(); - } - - public boolean containsAllHandlers(TryCatchBlock tb) { - return handlers.containsAll(tb.handlers); - } - - public ExceptionHandler addHandler(MethodNode mth, int addr, @Nullable ClassInfo type) { - ExceptionHandler handler = new ExceptionHandler(addr, type); - handler.setTryBlock(this); - ExceptionHandler addedHandler = mth.addExceptionHandler(handler); - if (addedHandler == handler || addedHandler.getTryBlock() != this) { - handlers.add(addedHandler); - } - return addedHandler; - } - - /** - * Use only before BlockSplitter - */ - public void removeSameHandlers(TryCatchBlock outerTry) { - for (ExceptionHandler handler : outerTry.getHandlers()) { - if (handlers.remove(handler)) { - handler.setTryBlock(outerTry); - } - } - } - - public void removeHandler(MethodNode mth, ExceptionHandler handler) { - for (Iterator it = handlers.iterator(); it.hasNext();) { - ExceptionHandler h = it.next(); - if (h == handler) { - unbindHandler(h); - it.remove(); - break; - } - } - if (handlers.isEmpty()) { - removeWholeBlock(mth); - } - } - - private void unbindHandler(ExceptionHandler handler) { - for (BlockNode block : handler.getBlocks()) { - // skip synthetic loop exit blocks - BlockUtils.skipPredSyntheticPaths(block); - block.add(AFlag.REMOVE); - ExcHandlerAttr excHandlerAttr = block.get(AType.EXC_HANDLER); - if (excHandlerAttr != null - && excHandlerAttr.getHandler().equals(handler)) { - block.remove(AType.EXC_HANDLER); - } - SplitterBlockAttr splitter = handler.getHandlerBlock().get(AType.SPLITTER_BLOCK); - if (splitter != null) { - BlockNode splitterBlock = splitter.getBlock(); - splitterBlock.remove(AType.SPLITTER_BLOCK); - for (BlockNode successor : splitterBlock.getSuccessors()) { - successor.remove(AType.SPLITTER_BLOCK); - } - } - } - handler.markForRemove(); - } - - private void removeWholeBlock(MethodNode mth) { - // self destruction - for (Iterator it = handlers.iterator(); it.hasNext();) { - ExceptionHandler h = it.next(); - unbindHandler(h); - it.remove(); - } - for (InsnNode insn : insns) { - insn.removeAttr(attr); - } - insns.clear(); - if (mth.getBasicBlocks() != null) { - for (BlockNode block : mth.getBasicBlocks()) { - block.removeAttr(attr); - } - } - } - - public void addInsn(InsnNode insn) { - insns.add(insn); - insn.addAttr(attr); - } - - public void removeInsn(MethodNode mth, InsnNode insn) { - insns.remove(insn); - insn.remove(AType.CATCH_BLOCK); - if (insns.isEmpty()) { - removeWholeBlock(mth); - } - } - - public void removeBlock(MethodNode mth, BlockNode block) { - for (InsnNode insn : block.getInstructions()) { - insns.remove(insn); - insn.remove(AType.CATCH_BLOCK); - } - if (insns.isEmpty()) { - removeWholeBlock(mth); - } - } - - public Iterable getInsns() { - return insns; - } - - public CatchAttr getCatchAttr() { - return attr; - } - - public boolean merge(MethodNode mth, TryCatchBlock tryBlock) { - if (tryBlock == this) { - return false; - } - - for (InsnNode insn : tryBlock.getInsns()) { - this.addInsn(insn); - } - this.handlers.addAll(tryBlock.handlers); - for (ExceptionHandler eh : handlers) { - eh.setTryBlock(this); - } - // clear - tryBlock.handlers.clear(); - tryBlock.removeWholeBlock(mth); - return true; - } - - @Override - public int hashCode() { - return handlers.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - TryCatchBlock other = (TryCatchBlock) obj; - return handlers.equals(other.handlers); - } - - @Override - public String toString() { - return "Catch:{ " + Utils.listToString(handlers) + " }"; - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/trycatch/TryCatchBlockAttr.java b/jadx-core/src/main/java/jadx/core/dex/trycatch/TryCatchBlockAttr.java new file mode 100644 index 000000000..3e394d4d4 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/trycatch/TryCatchBlockAttr.java @@ -0,0 +1,177 @@ +package jadx.core.dex.trycatch; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import jadx.api.plugins.input.data.attributes.IJadxAttrType; +import jadx.api.plugins.input.data.attributes.IJadxAttribute; +import jadx.core.dex.attributes.AType; +import jadx.core.dex.nodes.BlockNode; +import jadx.core.dex.nodes.InsnNode; +import jadx.core.utils.Utils; + +public class TryCatchBlockAttr implements IJadxAttribute { + + private final int id; + private final List handlers; + private List blocks; + + private TryCatchBlockAttr outerTryBlock; + private List innerTryBlocks = Collections.emptyList(); + private boolean merged = false; + + private BlockNode topSplitter; + + public TryCatchBlockAttr(int id, List handlers, List blocks) { + this.id = id; + this.handlers = handlers; + this.blocks = blocks; + + handlers.forEach(h -> h.setTryBlock(this)); + } + + public boolean isAllHandler() { + return handlers.size() == 1 && handlers.get(0).isCatchAll(); + } + + public boolean isThrowOnly() { + boolean throwFound = false; + for (BlockNode block : blocks) { + List insns = block.getInstructions(); + if (insns.size() != 1) { + return false; + } + InsnNode insn = insns.get(0); + switch (insn.getType()) { + case MOVE_EXCEPTION: + case MONITOR_EXIT: + // allowed instructions + break; + + case THROW: + throwFound = true; + break; + + default: + return false; + } + } + return throwFound; + } + + public List getHandlers() { + return handlers; + } + + public int getHandlersCount() { + return handlers.size(); + } + + public List getBlocks() { + return blocks; + } + + public void setBlocks(List blocks) { + this.blocks = blocks; + } + + public void clear() { + blocks.clear(); + handlers.forEach(ExceptionHandler::markForRemove); + handlers.clear(); + } + + public void removeBlock(BlockNode block) { + blocks.remove(block); + } + + public void removeHandler(ExceptionHandler handler) { + handlers.remove(handler); + handler.markForRemove(); + } + + public List getInnerTryBlocks() { + return innerTryBlocks; + } + + public void addInnerTryBlock(TryCatchBlockAttr inner) { + if (this.innerTryBlocks.isEmpty()) { + this.innerTryBlocks = new ArrayList<>(); + } + this.innerTryBlocks.add(inner); + } + + public TryCatchBlockAttr getOuterTryBlock() { + return outerTryBlock; + } + + public void setOuterTryBlock(TryCatchBlockAttr outerTryBlock) { + this.outerTryBlock = outerTryBlock; + } + + public BlockNode getTopSplitter() { + return topSplitter; + } + + public void setTopSplitter(BlockNode topSplitter) { + this.topSplitter = topSplitter; + } + + public boolean isMerged() { + return merged; + } + + public void setMerged(boolean merged) { + this.merged = merged; + } + + public int id() { + return id; + } + + @Override + public IJadxAttrType getAttrType() { + return AType.TRY_BLOCK; + } + + @Override + public int hashCode() { + return handlers.hashCode() + 31 * blocks.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + TryCatchBlockAttr other = (TryCatchBlockAttr) obj; + return id == other.id + && handlers.equals(other.handlers) + && blocks.equals(other.blocks); + } + + @Override + public String toString() { + if (merged) { + return "Merged into " + outerTryBlock; + } + StringBuilder sb = new StringBuilder(); + sb.append("TryCatch #").append(id).append(" {").append(Utils.listToString(handlers)); + sb.append(", blocks: (").append(Utils.listToString(blocks)).append(')'); + if (topSplitter != null) { + sb.append(", top: ").append(topSplitter); + } + if (outerTryBlock != null) { + sb.append(", outer: #").append(outerTryBlock.id); + } + if (!innerTryBlocks.isEmpty()) { + sb.append(", inners: ").append(Utils.listToString(innerTryBlocks, inner -> "#" + inner.id)); + } + sb.append(" }"); + return sb.toString(); + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/AttachMethodDetails.java b/jadx-core/src/main/java/jadx/core/dex/visitors/AttachMethodDetails.java index 6f1a6ca7b..f1bde55e6 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/AttachMethodDetails.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/AttachMethodDetails.java @@ -1,22 +1,19 @@ package jadx.core.dex.visitors; import jadx.core.dex.instructions.BaseInvokeNode; -import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.IMethodDetails; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.utils.MethodUtils; -import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; -import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor; +import jadx.core.dex.visitors.blocks.BlockSplitter; import jadx.core.utils.exceptions.JadxException; @JadxVisitor( name = "Attach Method Details", desc = "Attach method details for invoke instructions", runBefore = { - CodeShrinkVisitor.class, - TypeInferenceVisitor.class, + BlockSplitter.class, MethodInvokeVisitor.class } ) @@ -34,11 +31,9 @@ public class AttachMethodDetails extends AbstractVisitor { if (mth.isNoCode()) { return; } - for (BlockNode blockNode : mth.getBasicBlocks()) { - for (InsnNode insn : blockNode.getInstructions()) { - if (insn instanceof BaseInvokeNode) { - attachMethodDetails((BaseInvokeNode) insn); - } + for (InsnNode insn : mth.getInstructions()) { + if (insn instanceof BaseInvokeNode) { + attachMethodDetails((BaseInvokeNode) insn); } } } @@ -49,5 +44,4 @@ public class AttachMethodDetails extends AbstractVisitor { insn.addAttr(methodDetails); } } - } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/AttachTryCatchVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/AttachTryCatchVisitor.java index fca03fcdb..423e11e44 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/AttachTryCatchVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/AttachTryCatchVisitor.java @@ -1,21 +1,25 @@ package jadx.core.dex.visitors; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; + +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import jadx.api.plugins.input.data.ICatch; -import jadx.api.plugins.input.data.ICodeReader; import jadx.api.plugins.input.data.ITry; +import jadx.api.plugins.utils.Utils; +import jadx.core.Consts; import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.attributes.AType; import jadx.core.dex.info.ClassInfo; import jadx.core.dex.instructions.InsnType; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; +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.utils.exceptions.JadxException; import static jadx.core.dex.visitors.ProcessInstructionsVisitor.getNextInsnOffset; @@ -28,107 +32,111 @@ import static jadx.core.dex.visitors.ProcessInstructionsVisitor.getNextInsnOffse } ) public class AttachTryCatchVisitor extends AbstractVisitor { + private static final Logger LOG = LoggerFactory.getLogger(AttachTryCatchVisitor.class); @Override public void visit(MethodNode mth) throws JadxException { if (mth.isNoCode()) { return; } - initTryCatches(mth, mth.getCodeReader(), mth.getInstructions()); + initTryCatches(mth, mth.getInstructions(), mth.getCodeReader().getTries()); } - private static void initTryCatches(MethodNode mth, ICodeReader codeReader, InsnNode[] insnByOffset) { - List tries = codeReader.getTries(); + private static void initTryCatches(MethodNode mth, InsnNode[] insnByOffset, List tries) { if (tries.isEmpty()) { return; } - int handlersCount = 0; - Set addrs = new HashSet<>(); - List catches = new ArrayList<>(tries.size()); + if (Consts.DEBUG_EXC_HANDLERS) { + LOG.debug("Raw try blocks in {}", mth); + tries.forEach(tryData -> LOG.debug(" - {}", tryData)); + } for (ITry tryData : tries) { - TryCatchBlock catchBlock = processHandlers(mth, addrs, tryData.getCatch()); - catches.add(catchBlock); - handlersCount += catchBlock.getHandlersCount(); - } - - // TODO: run modify in later passes - if (handlersCount > 0 && handlersCount != addrs.size()) { - // resolve nested try blocks: - // inner block contains all handlers from outer block => remove these handlers from inner block - // each handler must be only in one try/catch block - for (TryCatchBlock outerTry : catches) { - for (TryCatchBlock innerTry : catches) { - if (outerTry != innerTry - && innerTry.containsAllHandlers(outerTry)) { - innerTry.removeSameHandlers(outerTry); - } - } - } - } - addrs.clear(); - - for (TryCatchBlock tryCatchBlock : catches) { - if (tryCatchBlock.getHandlersCount() == 0) { + List handlers = attachHandlers(mth, tryData.getCatch(), insnByOffset); + if (handlers.isEmpty()) { continue; } - for (ExceptionHandler handler : tryCatchBlock.getHandlers()) { - int addr = handler.getHandleOffset(); - ExcHandlerAttr ehAttr = new ExcHandlerAttr(tryCatchBlock, handler); - // TODO: don't override existing attribute - insnByOffset[addr].addAttr(ehAttr); - } - } - - int k = 0; - for (ITry tryData : tries) { - TryCatchBlock catchBlock = catches.get(k++); - if (catchBlock.getHandlersCount() != 0) { - markTryBounds(insnByOffset, tryData, catchBlock); - } + markTryBounds(insnByOffset, tryData, new CatchAttr(handlers)); } } - private static void markTryBounds(InsnNode[] insnByOffset, ITry aTry, TryCatchBlock catchBlock) { - int offset = aTry.getStartAddress(); - int end = aTry.getEndAddress(); + private static void markTryBounds(InsnNode[] insnByOffset, ITry aTry, CatchAttr catchAttr) { + int offset = aTry.getStartOffset(); + int end = aTry.getEndOffset(); boolean tryBlockStarted = false; InsnNode insn = null; - while (offset <= end && offset >= 0) { - insn = insnByOffset[offset]; - if (insn != null && insn.getType() != InsnType.NOP) { - if (tryBlockStarted) { - catchBlock.addInsn(insn); - } else if (insn.canThrowException()) { + while (offset <= end) { + InsnNode insnAtOffset = insnByOffset[offset]; + if (insnAtOffset != null) { + insn = insnAtOffset; + insn.addAttr(catchAttr); + if (!tryBlockStarted) { insn.add(AFlag.TRY_ENTER); - catchBlock.addInsn(insn); tryBlockStarted = true; } } offset = getNextInsnOffset(insnByOffset, offset); + if (offset == -1) { + break; + } } - if (tryBlockStarted && insn != null) { + if (tryBlockStarted) { insn.add(AFlag.TRY_LEAVE); + } else { + // no instructions found in range -> add nop at start offset + InsnNode nop = insertNOP(insnByOffset, aTry.getStartOffset()); + nop.add(AFlag.TRY_ENTER); + nop.add(AFlag.TRY_LEAVE); + nop.addAttr(catchAttr); } } - private static TryCatchBlock processHandlers(MethodNode mth, Set addrs, ICatch catchBlock) { - int[] handlerAddrArr = catchBlock.getAddresses(); + private static List attachHandlers(MethodNode mth, ICatch catchBlock, InsnNode[] insnByOffset) { + int[] handlerOffsetArr = catchBlock.getHandlers(); String[] handlerTypes = catchBlock.getTypes(); - int handlersCount = handlerAddrArr.length; - TryCatchBlock tcBlock = new TryCatchBlock(handlersCount); + int handlersCount = handlerOffsetArr.length; + List list = new ArrayList<>(handlersCount); for (int i = 0; i < handlersCount; i++) { - int addr = handlerAddrArr[i]; + int handlerOffset = handlerOffsetArr[i]; ClassInfo type = ClassInfo.fromName(mth.root(), handlerTypes[i]); - tcBlock.addHandler(mth, addr, type); - addrs.add(addr); + Utils.addToList(list, createHandler(mth, insnByOffset, handlerOffset, type)); } - int addr = catchBlock.getCatchAllAddress(); - if (addr >= 0) { - tcBlock.addHandler(mth, addr, null); - addrs.add(addr); + int allHandlerOffset = catchBlock.getCatchAllHandler(); + if (allHandlerOffset >= 0) { + Utils.addToList(list, createHandler(mth, insnByOffset, allHandlerOffset, null)); } - return tcBlock; + return list; + } + + @Nullable + private static ExceptionHandler createHandler(MethodNode mth, InsnNode[] insnByOffset, int handlerOffset, @Nullable ClassInfo type) { + InsnNode insn = insnByOffset[handlerOffset]; + if (insn != null) { + ExcHandlerAttr excHandlerAttr = insn.get(AType.EXC_HANDLER); + if (excHandlerAttr != null) { + ExceptionHandler handler = excHandlerAttr.getHandler(); + if (handler.addCatchType(type)) { + // exist handler updated (assume from same try block) - don't add again + return null; + } + // same handler (can be used in different try blocks) + return handler; + } + } else { + insn = insertNOP(insnByOffset, handlerOffset); + } + ExceptionHandler handler = new ExceptionHandler(handlerOffset, type); + mth.addExceptionHandler(handler); + insn.addAttr(new ExcHandlerAttr(handler)); + return handler; + } + + private static InsnNode insertNOP(InsnNode[] insnByOffset, int offset) { + InsnNode nop = new InsnNode(InsnType.NOP, 0); + nop.setOffset(offset); + nop.add(AFlag.SYNTHETIC); + insnByOffset[offset] = nop; + return nop; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/CheckCode.java b/jadx-core/src/main/java/jadx/core/dex/visitors/CheckCode.java index 3691e68c6..e0894ebb3 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/CheckCode.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/CheckCode.java @@ -1,10 +1,17 @@ package jadx.core.dex.visitors; +import java.util.ArrayList; +import java.util.List; + import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.InsnType; +import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.utils.exceptions.JadxException; +import jadx.core.utils.exceptions.JadxRuntimeException; + +import static jadx.core.utils.Utils.isEmpty; @JadxVisitor( name = "CheckCode", @@ -23,6 +30,7 @@ public class CheckCode extends AbstractVisitor { // TODO: convert args to array } } + checkInstructions(mth); } private boolean canRemoveMethod(MethodNode mth) { @@ -45,4 +53,29 @@ public class CheckCode extends AbstractVisitor { } return true; } + + public void checkInstructions(MethodNode mth) { + if (isEmpty(mth.getInstructions())) { + return; + } + int regsCount = mth.getRegsCount(); + List list = new ArrayList<>(); + for (InsnNode insnNode : mth.getInstructions()) { + if (insnNode == null) { + continue; + } + list.clear(); + RegisterArg resultArg = insnNode.getResult(); + if (resultArg != null) { + list.add(resultArg); + } + insnNode.getRegisterArgs(list); + for (RegisterArg arg : list) { + if (arg.getRegNum() >= regsCount) { + throw new JadxRuntimeException("Incorrect register number in instruction: " + insnNode + + ", expected to be less than " + regsCount); + } + } + } + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java index 859d8299b..b419e57e0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java @@ -82,7 +82,7 @@ public class ClassModifier extends AbstractVisitor { ClassNode fieldsCls = cls.root().resolveClass(clsInfo); ClassInfo parentClass = cls.getClassInfo().getParentClass(); if (fieldsCls != null - && (inline || parentClass.equals(fieldsCls.getClassInfo()))) { + && (inline || Objects.equals(parentClass, fieldsCls.getClassInfo()))) { int found = 0; for (MethodNode mth : cls.getMethods()) { if (removeFieldUsageFromConstructor(mth, field, fieldsCls)) { @@ -111,7 +111,7 @@ public class ClassModifier extends AbstractVisitor { if (!arg.getType().equals(fieldsCls.getClassInfo().getType())) { return false; } - BlockNode block = mth.getBasicBlocks().get(0); + BlockNode block = mth.getEnterBlock().getCleanSuccessors().get(0); List instructions = block.getInstructions(); if (instructions.isEmpty()) { return false; @@ -156,10 +156,13 @@ public class ClassModifier extends AbstractVisitor { return; } // remove synthetic constructor for inner classes - if (af.isConstructor() && mth.getBasicBlocks().size() == 2) { - List args = mth.getArgRegs(); - if (isRemovedClassInArgs(cls, args)) { - modifySyntheticMethod(cls, mth, args); + if (af.isConstructor()) { + InsnNode insn = BlockUtils.getOnlyOneInsnFromMth(mth); + if (insn != null) { + List args = mth.getArgRegs(); + if (isRemovedClassInArgs(cls, args)) { + modifySyntheticMethod(cls, mth, insn, args); + } } } } @@ -190,10 +193,9 @@ public class ClassModifier extends AbstractVisitor { /** * Remove synthetic constructor and redirect calls to existing constructor */ - private static void modifySyntheticMethod(ClassNode cls, MethodNode mth, List args) { - List insns = mth.getBasicBlocks().get(0).getInstructions(); - if (insns.size() == 1 && insns.get(0).getType() == InsnType.CONSTRUCTOR) { - ConstructorInsn constr = (ConstructorInsn) insns.get(0); + private static void modifySyntheticMethod(ClassNode cls, MethodNode mth, InsnNode insn, List args) { + if (insn.getType() == InsnType.CONSTRUCTOR) { + ConstructorInsn constr = (ConstructorInsn) insn; if (constr.isThis() && !args.isEmpty()) { // remove first arg for non-static class (references to outer class) RegisterArg firstArg = args.get(0); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java index e568c5dbc..ba85dbb17 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java @@ -20,6 +20,7 @@ import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.visitors.finaly.MarkFinallyVisitor; import jadx.core.dex.visitors.ssa.SSATransform; import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor; import jadx.core.utils.InsnRemover; @@ -101,7 +102,9 @@ public class ConstInlineVisitor extends AbstractVisitor { } // all check passed, run replace - replaceConst(mth, insn, constArg, toRemove); + if (replaceConst(mth, insn, constArg)) { + toRemove.add(insn); + } } /** @@ -147,11 +150,10 @@ public class ConstInlineVisitor extends AbstractVisitor { return true; } - private static void replaceConst(MethodNode mth, InsnNode constInsn, InsnArg constArg, List toRemove) { + private static boolean replaceConst(MethodNode mth, InsnNode constInsn, InsnArg constArg) { SSAVar ssaVar = constInsn.getResult().getSVar(); if (ssaVar.getUseCount() == 0) { - toRemove.add(constInsn); - return; + return true; } List useList = new ArrayList<>(ssaVar.getUseList()); int replaceCount = 0; @@ -161,10 +163,27 @@ public class ConstInlineVisitor extends AbstractVisitor { } } if (replaceCount == useList.size()) { - toRemove.add(constInsn); + return true; } + // hide insn if used only in not generated insns + if (ssaVar.getUseList().stream().allMatch(ConstInlineVisitor::canIgnoreInsn)) { + constInsn.add(AFlag.DONT_GENERATE); + } + return false; } + private static boolean canIgnoreInsn(RegisterArg reg) { + InsnNode parentInsn = reg.getParentInsn(); + if (parentInsn == null || parentInsn.getType() == InsnType.PHI) { + return false; + } + if (reg.isLinkedToOtherSsaVars()) { + return false; + } + return parentInsn.contains(AFlag.DONT_GENERATE); + } + + @SuppressWarnings("RedundantIfStatement") private static boolean canInline(RegisterArg arg) { if (arg.contains(AFlag.DONT_INLINE_CONST)) { return false; @@ -173,7 +192,11 @@ public class ConstInlineVisitor extends AbstractVisitor { if (parentInsn == null) { return false; } - if (parentInsn.contains(AFlag.DONT_GENERATE) || parentInsn.contains(AFlag.FINALLY_INSNS)) { + if (parentInsn.contains(AFlag.DONT_GENERATE)) { + return false; + } + if (arg.isLinkedToOtherSsaVars() && !arg.getSVar().isUsedInPhi()) { + // don't inline vars used in finally block return false; } return true; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ConstructorVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstructorVisitor.java index c1c8a72cc..7af5d59cb 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ConstructorVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstructorVisitor.java @@ -33,13 +33,16 @@ public class ConstructorVisitor extends AbstractVisitor { if (mth.isNoCode()) { return; } - replaceInvoke(mth); + if (replaceInvoke(mth)) { + MoveInlineVisitor.moveInline(mth); + } if (mth.contains(AFlag.RERUN_SSA_TRANSFORM)) { SSATransform.rerun(mth); } } - private static void replaceInvoke(MethodNode mth) { + private static boolean replaceInvoke(MethodNode mth) { + boolean replaced = false; InsnRemover remover = new InsnRemover(mth); for (BlockNode block : mth.getBasicBlocks()) { remover.setBlock(block); @@ -47,22 +50,23 @@ public class ConstructorVisitor extends AbstractVisitor { for (int i = 0; i < size; i++) { InsnNode insn = block.getInstructions().get(i); if (insn.getType() == InsnType.INVOKE) { - processInvoke(mth, block, i, remover); + replaced |= processInvoke(mth, block, i, remover); } } remover.perform(); } + return replaced; } - private static void processInvoke(MethodNode mth, BlockNode block, int indexInBlock, InsnRemover remover) { + private static boolean processInvoke(MethodNode mth, BlockNode block, int indexInBlock, InsnRemover remover) { InvokeNode inv = (InvokeNode) block.getInstructions().get(indexInBlock); if (!inv.getCallMth().isConstructor()) { - return; + return false; } ConstructorInsn co = new ConstructorInsn(mth, inv); if (canRemoveConstructor(mth, co)) { remover.addAndUnbind(inv); - return; + return false; } co.inheritMetadata(inv); @@ -99,8 +103,8 @@ public class ConstructorVisitor extends AbstractVisitor { parentInsn.replaceArg(useArg, resultArg.duplicate()); } } - co.inheritMetadata(newInstInsn); } + co.inheritMetadata(newInstInsn); } ConstructorInsn replace = processConstructor(mth, co); if (replace != null) { @@ -109,6 +113,7 @@ public class ConstructorVisitor extends AbstractVisitor { } else { BlockUtils.replaceInsn(mth, block, indexInBlock, co); } + return true; } private static boolean canRemoveConstructor(MethodNode mth, ConstructorInsn co) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java index 1007e4691..d63862c4b 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java @@ -160,7 +160,7 @@ public class EnumVisitor extends AbstractVisitor { fieldNode.getFieldInfo().setAlias(name); } fieldNode.add(AFlag.DONT_GENERATE); - processConstructorInsn(cls, enumField, classInitMth, staticBlock); + processConstructorInsn(cls, enumField, classInitMth, staticBlock, toRemove); } valuesField.add(AFlag.DONT_GENERATE); InsnRemover.removeAllAndUnbind(classInitMth, staticBlock, toRemove); @@ -173,7 +173,8 @@ public class EnumVisitor extends AbstractVisitor { return true; } - private void processConstructorInsn(ClassNode cls, EnumField enumField, MethodNode classInitMth, BlockNode staticBlock) { + private void processConstructorInsn(ClassNode cls, EnumField enumField, MethodNode classInitMth, + BlockNode staticBlock, List toRemove) { ConstructorInsn co = enumField.getConstrInsn(); ClassInfo enumClsInfo = co.getClassType(); if (!enumClsInfo.equals(cls.getClassInfo())) { @@ -194,7 +195,7 @@ public class EnumVisitor extends AbstractVisitor { } RegisterArg coResArg = co.getResult(); if (coResArg == null || coResArg.getSVar().getUseList().size() <= 2) { - InsnRemover.removeWithoutUnbind(classInitMth, staticBlock, co); + toRemove.add(co); } else { // constructor result used in other places -> replace constructor with enum field get (SGET) IndexInsnNode enumGet = new IndexInsnNode(InsnType.SGET, enumField.getField().getFieldInfo(), 0); @@ -234,7 +235,7 @@ public class EnumVisitor extends AbstractVisitor { if (valuesMth == null || valuesMth.isVoidReturn()) { return null; } - BlockNode returnBlock = Utils.getOne(valuesMth.getExitBlocks()); + BlockNode returnBlock = Utils.getOne(valuesMth.getPreExitBlocks()); InsnNode returnInsn = BlockUtils.getLastInsn(returnBlock); InsnNode wrappedInsn = getWrappedInsn(getSingleArg(returnInsn)); if (wrappedInsn == null) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java index 0b1ab60f8..1f22362ab 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ExtractFieldInit.java @@ -137,13 +137,17 @@ public class ExtractFieldInit extends AbstractVisitor { } List infoList = new ArrayList<>(constrList.size()); for (MethodNode constrMth : constrList) { - if (constrMth.isNoCode() || constrMth.getBasicBlocks().isEmpty()) { + if (constrMth.isNoCode()) { + return; + } + List enterBlocks = constrMth.getEnterBlock().getCleanSuccessors(); + if (enterBlocks.isEmpty()) { return; } InitInfo info = new InitInfo(constrMth); infoList.add(info); // TODO: check not only first block - BlockNode blockNode = constrMth.getBasicBlocks().get(0); + BlockNode blockNode = enterBlocks.get(0); for (InsnNode insn : blockNode.getInstructions()) { if (insn.getType() == InsnType.IPUT && checkInsn(cls, insn)) { info.getPutInsns().add(insn); @@ -226,7 +230,7 @@ public class ExtractFieldInit extends AbstractVisitor { InsnArg arg = insn.getArg(0); if (arg.isInsnWrap()) { InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn(); - if (!wrapInsn.canReorderRecursive() && insn.contains(AType.CATCH_BLOCK)) { + if (!wrapInsn.canReorderRecursive() && insn.contains(AType.EXC_CATCH)) { return false; } } else { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/FallbackModeVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/FallbackModeVisitor.java index 86689cacc..ae96d91b6 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/FallbackModeVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/FallbackModeVisitor.java @@ -27,7 +27,7 @@ public class FallbackModeVisitor extends AbstractVisitor { continue; } // remove 'exception catch' for instruction which don't throw any exceptions - CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK); + CatchAttr catchAttr = insn.get(AType.EXC_CATCH); if (catchAttr != null) { switch (insn.getType()) { case RETURN: @@ -42,7 +42,7 @@ public class FallbackModeVisitor extends AbstractVisitor { case CONST_CLASS: case CMP_L: case CMP_G: - catchAttr.getTryBlock().removeInsn(mth, insn); + insn.remove(AType.EXC_CATCH); break; default: diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/MarkMethodsForInline.java b/jadx-core/src/main/java/jadx/core/dex/visitors/MarkMethodsForInline.java index b7e726a11..c6c1d1295 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/MarkMethodsForInline.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/MarkMethodsForInline.java @@ -18,9 +18,9 @@ import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnWrapArg; 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.utils.BlockUtils; import jadx.core.utils.exceptions.JadxException; @JadxVisitor( @@ -48,18 +48,12 @@ public class MarkMethodsForInline extends AbstractVisitor { return mia; } if (canInline(mth)) { - List blocks = mth.getBasicBlocks(); - if (blocks == null) { + if (mth.getBasicBlocks() == null) { return null; } - if (blocks.size() == 2) { - BlockNode returnBlock = blocks.get(1); - if (returnBlock.contains(AFlag.RETURN) || returnBlock.getInstructions().isEmpty()) { - MethodInlineAttr inlined = inlineMth(mth, blocks.get(0), returnBlock); - if (inlined != null) { - return inlined; - } - } + MethodInlineAttr inlined = inlineMth(mth); + if (inlined != null) { + return inlined; } } return MethodInlineAttr.inlineNotNeeded(mth); @@ -74,18 +68,25 @@ public class MarkMethodsForInline extends AbstractVisitor { } @Nullable - private static MethodInlineAttr inlineMth(MethodNode mth, BlockNode firstBlock, BlockNode returnBlock) { - List insnList = firstBlock.getInstructions(); - if (insnList.isEmpty()) { - // synthetic field getter - BlockNode block = mth.getBasicBlocks().get(1); - InsnNode insn = block.getInstructions().get(0); - // set arg from 'return' instruction - return addInlineAttr(mth, InsnNode.wrapArg(insn.getArg(0))); + private static MethodInlineAttr inlineMth(MethodNode mth) { + List insns = BlockUtils.collectInsnsWithLimit(mth.getBasicBlocks(), 2); + int insnsCount = insns.size(); + if (insnsCount == 0) { + return null; } - // synthetic field setter or method invoke - if (insnList.size() == 1) { - return addInlineAttr(mth, insnList.get(0)); + if (insnsCount == 1) { + InsnNode insn = insns.get(0); + if (insn.getType() == InsnType.RETURN) { + // synthetic field getter + // set arg from 'return' instruction + return addInlineAttr(mth, InsnNode.wrapArg(insn.getArg(0))); + } + // method invoke + return addInlineAttr(mth, insn); + } + if (insnsCount == 2 && insns.get(1).getType() == InsnType.RETURN) { + // synthetic field setter + return addInlineAttr(mth, insns.get(0)); } // TODO: inline field arithmetics. Disabled tests: TestAnonymousClass3a and TestAnonymousClass5 return null; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/MoveInlineVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/MoveInlineVisitor.java index 41e52a7a9..8c597927c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/MoveInlineVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/MoveInlineVisitor.java @@ -30,13 +30,15 @@ public class MoveInlineVisitor extends AbstractVisitor { moveInline(mth); } - private static void moveInline(MethodNode mth) { + public static void moveInline(MethodNode mth) { InsnRemover remover = new InsnRemover(mth); for (BlockNode block : mth.getBasicBlocks()) { remover.setBlock(block); for (InsnNode insn : block.getInstructions()) { - if (insn.getType() == InsnType.MOVE - && processMove(mth, insn)) { + if (insn.getType() != InsnType.MOVE) { + continue; + } + if (processMove(mth, insn)) { remover.addAndUnbind(insn); } } @@ -52,7 +54,7 @@ public class MoveInlineVisitor extends AbstractVisitor { } SSAVar ssaVar = resultArg.getSVar(); if (ssaVar.isUsedInPhi()) { - return false; + return deleteMove(mth, move); } RegDebugInfoAttr debugInfo = moveArg.get(AType.REG_DEBUG_INFO); for (RegisterArg useArg : ssaVar.getUseList()) { @@ -91,4 +93,35 @@ public class MoveInlineVisitor extends AbstractVisitor { } return true; } + + private static boolean deleteMove(MethodNode mth, InsnNode move) { + InsnArg moveArg = move.getArg(0); + if (!moveArg.isRegister()) { + return false; + } + RegisterArg moveReg = (RegisterArg) moveArg; + SSAVar ssaVar = moveReg.getSVar(); + if (ssaVar.getUseCount() != 1 || ssaVar.isUsedInPhi()) { + return false; + } + RegisterArg assignArg = ssaVar.getAssign(); + InsnNode parentInsn = assignArg.getParentInsn(); + if (parentInsn == null) { + return false; + } + if (parentInsn.getSourceLine() != move.getSourceLine() + || moveArg.contains(AType.REG_DEBUG_INFO)) { + // preserve debug info + return false; + } + // set move result into parent insn result + InsnRemover.unbindAllArgs(mth, move); + InsnRemover.unbindResult(mth, parentInsn); + + RegisterArg resArg = parentInsn.getResult(); + RegisterArg newResArg = move.getResult().duplicate(resArg.getInitType()); + newResArg.copyAttributesFrom(resArg); + parentInsn.setResult(newResArg); + return true; + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java b/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java index ba2865f25..e39f7320f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java @@ -235,7 +235,7 @@ public class PrepareForCodeGen extends AbstractVisitor { if (!regArgs.isEmpty()) { mth.addWarn("Illegal instructions before constructor call"); } else { - mth.addComment("JADX INFO: " + callType + " call moved to the top of the method (can break code semantics)"); + mth.addComment("JADX INFO: '" + callType + "' call moved to the top of the method (can break code semantics)"); } } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessInstructionsVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessInstructionsVisitor.java index d0a62a6df..edec27c28 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessInstructionsVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessInstructionsVisitor.java @@ -17,7 +17,7 @@ import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; -import jadx.core.dex.visitors.blocksmaker.BlockSplitter; +import jadx.core.dex.visitors.blocks.BlockSplitter; import jadx.core.utils.InsnUtils; import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -91,6 +91,7 @@ public class ProcessInstructionsVisitor extends AbstractVisitor { InsnNode arrDataInsn = getInsnAtOffset(insnByOffset, target); if (arrDataInsn != null && arrDataInsn.getType() == InsnType.FILL_ARRAY_DATA) { fillArrayInsn.setArrayData((FillArrayData) arrDataInsn); + removeInsn(insnByOffset, arrDataInsn); } else { throw new JadxRuntimeException("Payload for fill-array not found at " + InsnUtils.formatOffset(target)); } @@ -110,6 +111,7 @@ public class ProcessInstructionsVisitor extends AbstractVisitor { SwitchData data = (SwitchData) switchDataInsn; data.fixTargets(offset); sw.attachSwitchData(data, nextInsnOffset); + removeInsn(insnByOffset, switchDataInsn); } else { throw new JadxRuntimeException("Payload for switch not found at " + InsnUtils.formatOffset(dataTarget)); } @@ -127,7 +129,7 @@ public class ProcessInstructionsVisitor extends AbstractVisitor { RegisterArg moveRes = nextInsn.getResult(); insn.setResult(moveRes.duplicate(resType)); insn.copyAttributesFrom(nextInsn); - insnByOffset[nextInsnOffset] = null; + removeInsn(insnByOffset, nextInsn); } private static void addJump(MethodNode mth, InsnNode[] insnByOffset, int offset, int target) { @@ -160,4 +162,8 @@ public class ProcessInstructionsVisitor extends AbstractVisitor { } return null; } + + private static void removeInsn(InsnNode[] insnByOffset, InsnNode insn) { + insnByOffset[insn.getOffset()] = null; + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java index c50065f24..a5db38952 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java @@ -133,10 +133,10 @@ public class SimplifyVisitor extends AbstractVisitor { return simplifyArith((ArithNode) insn); case IF: - simplifyIf((IfNode) insn); + simplifyIf(mth, (IfNode) insn); break; case TERNARY: - simplifyTernary((TernaryInsn) insn); + simplifyTernary(mth, (TernaryInsn) insn); break; case INVOKE: @@ -257,13 +257,14 @@ public class SimplifyVisitor extends AbstractVisitor { /** * Simplify 'cmp' instruction in if condition */ - private static void simplifyIf(IfNode insn) { + private static void simplifyIf(MethodNode mth, IfNode insn) { InsnArg f = insn.getArg(0); if (f.isInsnWrap()) { InsnNode wi = ((InsnWrapArg) f).getWrapInsn(); if (wi.getType() == InsnType.CMP_L || wi.getType() == InsnType.CMP_G) { if (insn.getArg(1).isZeroLiteral()) { - insn.changeCondition(insn.getOp(), wi.getArg(0), wi.getArg(1)); + insn.changeCondition(insn.getOp(), wi.getArg(0).duplicate(), wi.getArg(1).duplicate()); + InsnRemover.unbindInsn(mth, wi); } else { LOG.warn("TODO: cmp {}", insn); } @@ -274,10 +275,10 @@ public class SimplifyVisitor extends AbstractVisitor { /** * Simplify condition in ternary operation */ - private static void simplifyTernary(TernaryInsn insn) { + private static void simplifyTernary(MethodNode mth, TernaryInsn insn) { IfCondition condition = insn.getCondition(); if (condition.isCompare()) { - simplifyIf(condition.getCompare().getInsn()); + simplifyIf(mth, condition.getCompare().getInsn()); } else { insn.simplifyCondition(); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockExceptionHandler.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockExceptionHandler.java new file mode 100644 index 000000000..359e50752 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockExceptionHandler.java @@ -0,0 +1,544 @@ +package jadx.core.dex.visitors.blocks; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.plugins.utils.Utils; +import jadx.core.Consts; +import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.nodes.TmpEdgeAttr; +import jadx.core.dex.info.ClassInfo; +import jadx.core.dex.instructions.InsnType; +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.instructions.args.InsnArg; +import jadx.core.dex.instructions.args.NamedArg; +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.CatchAttr; +import jadx.core.dex.trycatch.ExcHandlerAttr; +import jadx.core.dex.trycatch.ExceptionHandler; +import jadx.core.dex.trycatch.TryCatchBlockAttr; +import jadx.core.dex.visitors.typeinference.TypeCompare; +import jadx.core.utils.BlockUtils; +import jadx.core.utils.InsnRemover; +import jadx.core.utils.ListUtils; +import jadx.core.utils.exceptions.JadxRuntimeException; + +public class BlockExceptionHandler { + private static final Logger LOG = LoggerFactory.getLogger(BlockExceptionHandler.class); + + public static boolean process(MethodNode mth) { + if (mth.isNoExceptionHandlers()) { + return false; + } + BlockProcessor.computeDominanceFrontier(mth); + + processCatchAttr(mth); + initExcHandlers(mth); + + List tryBlocks = prepareTryBlocks(mth); + connectExcHandlers(mth, tryBlocks); + mth.addAttr(AType.TRY_BLOCKS_LIST, tryBlocks); + mth.getBasicBlocks().forEach(BlockNode::updateCleanSuccessors); + + for (ExceptionHandler eh : mth.getExceptionHandlers()) { + removeMonitorExitFromExcHandler(mth, eh); + } + BlockProcessor.removeMarkedBlocks(mth); + return true; + } + + /** + * Wrap try blocks with top/bottom splitter and connect them to handler block. + * Sometimes try block can be handler block itself and should be connected before wrapping. + * Use queue for postpone try blocks not ready for wrap. + */ + private static void connectExcHandlers(MethodNode mth, List tryBlocks) { + if (tryBlocks.isEmpty()) { + return; + } + int limit = tryBlocks.size() * 3; + int count = 0; + Deque queue = new ArrayDeque<>(tryBlocks); + while (!queue.isEmpty()) { + TryCatchBlockAttr tryBlock = queue.removeFirst(); + boolean complete = wrapBlocksWithTryCatch(mth, tryBlock); + if (!complete) { + queue.addLast(tryBlock); // return to queue at the end + } + if (count++ > limit) { + throw new JadxRuntimeException("Try blocks wrapping queue limit reached! Please report as an issue!"); + } + } + } + + private static void processCatchAttr(MethodNode mth) { + for (BlockNode block : mth.getBasicBlocks()) { + for (InsnNode insn : block.getInstructions()) { + if (insn.contains(AType.EXC_CATCH) && !insn.canThrowException()) { + insn.remove(AType.EXC_CATCH); + } + } + } + // if all instructions in block have same 'catch' attribute -> add this attribute for whole block. + for (BlockNode block : mth.getBasicBlocks()) { + CatchAttr commonCatchAttr = getCommonCatchAttr(block); + if (commonCatchAttr != null) { + block.addAttr(commonCatchAttr); + for (InsnNode insn : block.getInstructions()) { + if (insn.contains(AFlag.TRY_ENTER)) { + block.add(AFlag.TRY_ENTER); + } + if (insn.contains(AFlag.TRY_LEAVE)) { + block.add(AFlag.TRY_LEAVE); + } + } + } + } + } + + @Nullable + private static CatchAttr getCommonCatchAttr(BlockNode block) { + CatchAttr commonCatchAttr = null; + for (InsnNode insn : block.getInstructions()) { + CatchAttr catchAttr = insn.get(AType.EXC_CATCH); + if (catchAttr != null) { + if (commonCatchAttr == null) { + commonCatchAttr = catchAttr; + continue; + } + if (commonCatchAttr != catchAttr) { + return null; + } + } + } + return commonCatchAttr; + } + + @SuppressWarnings("ForLoopReplaceableByForEach") + private static void initExcHandlers(MethodNode mth) { + List blocks = mth.getBasicBlocks(); + int blocksCount = blocks.size(); + for (int i = 0; i < blocksCount; i++) { // will add new blocks to list end + BlockNode block = blocks.get(i); + InsnNode firstInsn = BlockUtils.getFirstInsn(block); + if (firstInsn == null) { + continue; + } + ExcHandlerAttr excHandlerAttr = firstInsn.get(AType.EXC_HANDLER); + if (excHandlerAttr == null) { + continue; + } + firstInsn.remove(AType.EXC_HANDLER); + TmpEdgeAttr tmpEdgeAttr = block.get(AType.TMP_EDGE); + if (tmpEdgeAttr != null) { + // remove temp connection + BlockSplitter.removeConnection(tmpEdgeAttr.getBlock(), block); + block.remove(AType.TMP_EDGE); + } + + ExceptionHandler excHandler = excHandlerAttr.getHandler(); + if (block.getPredecessors().isEmpty()) { + excHandler.setHandlerBlock(block); + block.addAttr(excHandlerAttr); + excHandler.addBlock(block); + BlockUtils.collectBlocksDominatedByWithExcHandlers(mth, block, block) + .forEach(excHandler::addBlock); + } else { + // ignore already connected handlers -> make catch empty + BlockNode emptyHandlerBlock = BlockSplitter.startNewBlock(mth, block.getStartOffset()); + emptyHandlerBlock.add(AFlag.SYNTHETIC); + emptyHandlerBlock.addAttr(excHandlerAttr); + BlockSplitter.connect(emptyHandlerBlock, block); + excHandler.setHandlerBlock(emptyHandlerBlock); + excHandler.addBlock(emptyHandlerBlock); + } + fixMoveExceptionInsn(block, excHandlerAttr); + } + } + + private static List prepareTryBlocks(MethodNode mth) { + Map> blocksByHandler = new HashMap<>(); + for (BlockNode block : mth.getBasicBlocks()) { + CatchAttr catchAttr = block.get(AType.EXC_CATCH); + if (catchAttr != null) { + for (ExceptionHandler eh : catchAttr.getHandlers()) { + blocksByHandler + .computeIfAbsent(eh, c -> new ArrayList<>()) + .add(block); + } + } + } + if (Consts.DEBUG_EXC_HANDLERS) { + LOG.debug("Input exception handlers:"); + blocksByHandler.forEach((eh, blocks) -> LOG.debug(" {}, throw blocks: {}, handler blocks: {}", eh, blocks, eh.getBlocks())); + } + if (blocksByHandler.isEmpty()) { + // no catch blocks -> remove all handlers + mth.getExceptionHandlers().forEach(eh -> removeExcHandler(mth, eh)); + } else { + // remove handlers without blocks in catch attribute + blocksByHandler.forEach((eh, blocks) -> { + if (blocks.isEmpty()) { + removeExcHandler(mth, eh); + } + }); + } + BlockSplitter.detachMarkedBlocks(mth); + mth.clearExceptionHandlers(); + if (mth.isNoExceptionHandlers()) { + return Collections.emptyList(); + } + + blocksByHandler.forEach((eh, blocks) -> { + // remove catches from same handler + blocks.removeAll(eh.getBlocks()); + }); + + List tryBlocks = new ArrayList<>(); + blocksByHandler.forEach((eh, blocks) -> { + List handlers = new ArrayList<>(1); + handlers.add(eh); + tryBlocks.add(new TryCatchBlockAttr(tryBlocks.size(), handlers, blocks)); + }); + if (tryBlocks.size() > 1) { + // merge or mark as outer/inner + while (true) { + boolean restart = combineTryCatchBlocks(tryBlocks); + if (!restart) { + break; + } + } + } + checkForMultiCatch(mth, tryBlocks); + clearTryBlocks(mth, tryBlocks); + sortHandlers(mth, tryBlocks); + + if (Consts.DEBUG_EXC_HANDLERS) { + LOG.debug("Result try-catch blocks:"); + tryBlocks.forEach(tryBlock -> LOG.debug(" {}", tryBlock)); + } + return tryBlocks; + } + + private static void clearTryBlocks(MethodNode mth, List tryBlocks) { + tryBlocks.forEach(tc -> tc.getBlocks().removeIf(b -> b.contains(AFlag.REMOVE))); + tryBlocks.removeIf(tb -> tb.getBlocks().isEmpty() || tb.getHandlers().isEmpty()); + mth.clearExceptionHandlers(); + BlockSplitter.detachMarkedBlocks(mth); + } + + private static boolean combineTryCatchBlocks(List tryBlocks) { + for (TryCatchBlockAttr outerTryBlock : tryBlocks) { + for (TryCatchBlockAttr innerTryBlock : tryBlocks) { + if (outerTryBlock == innerTryBlock || innerTryBlock.getOuterTryBlock() != null) { + continue; + } + if (checkTryCatchRelation(tryBlocks, outerTryBlock, innerTryBlock)) { + return true; + } + } + } + return false; + } + + private static boolean checkTryCatchRelation(List tryBlocks, + TryCatchBlockAttr outerTryBlock, TryCatchBlockAttr innerTryBlock) { + if (outerTryBlock.getBlocks().equals(innerTryBlock.getBlocks())) { + // same try blocks -> merge handlers + List handlers = Utils.concatDistinct(outerTryBlock.getHandlers(), innerTryBlock.getHandlers()); + tryBlocks.add(new TryCatchBlockAttr(tryBlocks.size(), handlers, outerTryBlock.getBlocks())); + tryBlocks.remove(outerTryBlock); + tryBlocks.remove(innerTryBlock); + return true; + } + + Set handlerBlocks = innerTryBlock.getHandlers().stream() + .flatMap(eh -> eh.getBlocks().stream()) + .collect(Collectors.toSet()); + boolean catchInHandler = handlerBlocks.stream().anyMatch(isHandlersIntersects(outerTryBlock)); + boolean catchInTry = innerTryBlock.getBlocks().stream().anyMatch(isHandlersIntersects(outerTryBlock)); + boolean blocksOutsideHandler = outerTryBlock.getBlocks().stream().anyMatch(b -> !handlerBlocks.contains(b)); + + boolean makeInner = catchInHandler && (catchInTry || blocksOutsideHandler); + if (makeInner && innerTryBlock.isAllHandler()) { + // inner try block can't have catch-all handler + outerTryBlock.setBlocks(Utils.concatDistinct(outerTryBlock.getBlocks(), innerTryBlock.getBlocks())); + innerTryBlock.clear(); + return false; + } + if (makeInner) { + // convert to inner + List mergedBlocks = Utils.concatDistinct(outerTryBlock.getBlocks(), innerTryBlock.getBlocks()); + innerTryBlock.getHandlers().removeAll(outerTryBlock.getHandlers()); + innerTryBlock.setOuterTryBlock(outerTryBlock); + outerTryBlock.addInnerTryBlock(innerTryBlock); + outerTryBlock.setBlocks(mergedBlocks); + return false; + } + if (innerTryBlock.getHandlers().containsAll(outerTryBlock.getHandlers())) { + // merge + List mergedBlocks = Utils.concatDistinct(outerTryBlock.getBlocks(), innerTryBlock.getBlocks()); + List handlers = Utils.concatDistinct(outerTryBlock.getHandlers(), innerTryBlock.getHandlers()); + tryBlocks.add(new TryCatchBlockAttr(tryBlocks.size(), handlers, mergedBlocks)); + tryBlocks.remove(outerTryBlock); + tryBlocks.remove(innerTryBlock); + return true; + } + return false; + } + + @NotNull + private static Predicate isHandlersIntersects(TryCatchBlockAttr outerTryBlock) { + return block -> { + CatchAttr catchAttr = block.get(AType.EXC_CATCH); + return catchAttr != null && Objects.equals(catchAttr.getHandlers(), outerTryBlock.getHandlers()); + }; + } + + private static void removeExcHandler(MethodNode mth, ExceptionHandler excHandler) { + excHandler.markForRemove(); + BlockSplitter.removeConnection(mth.getEnterBlock(), excHandler.getHandlerBlock()); + } + + private static boolean wrapBlocksWithTryCatch(MethodNode mth, TryCatchBlockAttr tryCatchBlock) { + List blocks = tryCatchBlock.getBlocks(); + BlockNode top = searchTopBlock(mth, blocks); + if (top.getPredecessors().isEmpty()) { + return false; + } + BlockNode bottom = searchBottomBlock(mth, blocks); + + if (Consts.DEBUG_EXC_HANDLERS) { + LOG.debug("TryCatch #{} split: top {}, bottom: {}", tryCatchBlock.id(), top, bottom); + } + + BlockNode topSplitterBlock; + if (top == mth.getEnterBlock()) { + BlockNode fixedTop = mth.getEnterBlock().getSuccessors().get(0); + topSplitterBlock = BlockSplitter.blockSplitTop(mth, fixedTop); + } else { + BlockNode existTopSplitter = BlockUtils.getBlockWithFlag(top.getPredecessors(), AFlag.EXC_TOP_SPLITTER); + topSplitterBlock = existTopSplitter != null ? existTopSplitter : BlockSplitter.blockSplitTop(mth, top); + } + topSplitterBlock.add(AFlag.EXC_TOP_SPLITTER); + topSplitterBlock.add(AFlag.SYNTHETIC); + + int totalHandlerBlocks = tryCatchBlock.getHandlers().stream().mapToInt(eh -> eh.getBlocks().size()).sum(); + + BlockNode bottomSplitterBlock; + if (bottom == null || totalHandlerBlocks == 0) { + bottomSplitterBlock = null; + } else { + BlockNode existBottomSplitter = BlockUtils.getBlockWithFlag(bottom.getSuccessors(), AFlag.EXC_BOTTOM_SPLITTER); + bottomSplitterBlock = existBottomSplitter != null ? existBottomSplitter : BlockSplitter.startNewBlock(mth, -1); + bottomSplitterBlock.add(AFlag.EXC_BOTTOM_SPLITTER); + bottomSplitterBlock.add(AFlag.SYNTHETIC); + BlockSplitter.connect(bottom, bottomSplitterBlock); + } + + connectSplittersAndHandlers(tryCatchBlock, topSplitterBlock, bottomSplitterBlock); + + for (BlockNode block : blocks) { + TryCatchBlockAttr currentTCBAttr = block.get(AType.TRY_BLOCK); + if (currentTCBAttr == null || currentTCBAttr.getInnerTryBlocks().contains(tryCatchBlock)) { + block.addAttr(tryCatchBlock); + } + } + tryCatchBlock.setTopSplitter(topSplitterBlock); + + topSplitterBlock.updateCleanSuccessors(); + if (bottomSplitterBlock != null) { + bottomSplitterBlock.updateCleanSuccessors(); + } + return true; + } + + private static BlockNode searchTopBlock(MethodNode mth, List blocks) { + BlockNode top = BlockUtils.getTopBlock(blocks); + if (top != null) { + return top; + } + BlockNode topDom = BlockUtils.getCommonDominator(mth, blocks); + if (topDom != null) { + return topDom; + } + throw new JadxRuntimeException("Failed to find top block for try-catch from: " + blocks); + } + + @Nullable + private static BlockNode searchBottomBlock(MethodNode mth, List blocks) { + // search common post-dominator block inside input set + BlockNode bottom = BlockUtils.getBottomBlock(blocks); + if (bottom != null) { + return bottom; + } + // not found -> blocks don't have same dominator + // try to search common cross block outside input set + // NOTE: bottom block not needed for exit nodes (no data flow from them) + return BlockUtils.getPathCross(mth, blocks); + } + + private static void connectSplittersAndHandlers(TryCatchBlockAttr tryCatchBlock, BlockNode topSplitterBlock, + @Nullable BlockNode bottomSplitterBlock) { + for (ExceptionHandler handler : tryCatchBlock.getHandlers()) { + BlockNode handlerBlock = handler.getHandlerBlock(); + if (handlerBlock == null) { + System.out.println(); + } + BlockSplitter.connect(topSplitterBlock, handlerBlock); + if (bottomSplitterBlock != null) { + BlockSplitter.connect(bottomSplitterBlock, handlerBlock); + } + } + TryCatchBlockAttr outerTryBlock = tryCatchBlock.getOuterTryBlock(); + if (outerTryBlock != null) { + connectSplittersAndHandlers(outerTryBlock, topSplitterBlock, bottomSplitterBlock); + } + } + + private static void fixMoveExceptionInsn(BlockNode block, ExcHandlerAttr excHandlerAttr) { + ExceptionHandler excHandler = excHandlerAttr.getHandler(); + ArgType argType = excHandler.getArgType(); + InsnNode me = BlockUtils.getLastInsn(block); + if (me != null && me.getType() == InsnType.MOVE_EXCEPTION) { + // set correct type for 'move-exception' operation + RegisterArg resArg = InsnArg.reg(me.getResult().getRegNum(), argType); + resArg.copyAttributesFrom(me); + me.setResult(resArg); + me.add(AFlag.DONT_INLINE); + resArg.add(AFlag.CUSTOM_DECLARE); + excHandler.setArg(resArg); + me.addAttr(excHandlerAttr); + return; + } + // handler arguments not used + excHandler.setArg(new NamedArg("unused", argType)); + } + + private static void removeMonitorExitFromExcHandler(MethodNode mth, ExceptionHandler excHandler) { + for (BlockNode excBlock : excHandler.getBlocks()) { + InsnRemover remover = new InsnRemover(mth, excBlock); + for (InsnNode insn : excBlock.getInstructions()) { + if (insn.getType() == InsnType.MONITOR_ENTER) { + break; + } + if (insn.getType() == InsnType.MONITOR_EXIT) { + remover.addAndUnbind(insn); + } + } + remover.perform(); + } + } + + private static void checkForMultiCatch(MethodNode mth, List tryBlocks) { + boolean merged = false; + for (TryCatchBlockAttr tryBlock : tryBlocks) { + if (mergeMultiCatch(mth, tryBlock)) { + merged = true; + } + } + if (merged) { + BlockSplitter.detachMarkedBlocks(mth); + mth.clearExceptionHandlers(); + } + } + + private static boolean mergeMultiCatch(MethodNode mth, TryCatchBlockAttr tryCatch) { + if (tryCatch.getHandlers().size() < 2) { + return false; + } + for (ExceptionHandler handler : tryCatch.getHandlers()) { + if (handler.getBlocks().size() != 1) { + return false; + } + BlockNode block = handler.getHandlerBlock(); + if (block.getInstructions().size() != 1 + || !BlockUtils.checkLastInsnType(block, InsnType.MOVE_EXCEPTION)) { + return false; + } + } + List handlerBlocks = ListUtils.map(tryCatch.getHandlers(), ExceptionHandler::getHandlerBlock); + List successorBlocks = handlerBlocks.stream() + .flatMap(h -> h.getSuccessors().stream()) + .distinct() + .collect(Collectors.toList()); + if (successorBlocks.size() != 1) { + return false; + } + BlockNode successorBlock = successorBlocks.get(0); + if (!ListUtils.unorderedEquals(successorBlock.getPredecessors(), handlerBlocks)) { + return false; + } + List regs = tryCatch.getHandlers().stream() + .map(h -> Objects.requireNonNull(BlockUtils.getLastInsn(h.getHandlerBlock())).getResult()) + .distinct() + .collect(Collectors.toList()); + if (regs.size() != 1) { + return false; + } + + // merge confirm, leave only first handler, remove others + ExceptionHandler resultHandler = tryCatch.getHandlers().get(0); + tryCatch.getHandlers().removeIf(handler -> { + if (handler == resultHandler) { + return false; + } + resultHandler.addCatchTypes(handler.getCatchTypes()); + handler.markForRemove(); + return true; + }); + return true; + } + + private static void sortHandlers(MethodNode mth, List tryBlocks) { + TypeCompare typeCompare = mth.root().getTypeCompare(); + Comparator comparator = typeCompare.getReversedComparator(); + for (TryCatchBlockAttr tryBlock : tryBlocks) { + for (ExceptionHandler handler : tryBlock.getHandlers()) { + handler.getCatchTypes().sort((first, second) -> compareByTypeAndName(comparator, first, second)); + } + tryBlock.getHandlers().sort((first, second) -> { + if (first.equals(second)) { + throw new JadxRuntimeException("Same handlers in try block: " + tryBlock); + } + if (first.isCatchAll()) { + return 1; + } + if (second.isCatchAll()) { + return -1; + } + return compareByTypeAndName(comparator, + ListUtils.first(first.getCatchTypes()), ListUtils.first(second.getCatchTypes())); + }); + } + } + + @SuppressWarnings("ComparatorResultComparison") + private static int compareByTypeAndName(Comparator comparator, ClassInfo first, ClassInfo second) { + int r = comparator.compare(first.getType(), second.getType()); + if (r == -2) { + // on conflict sort by name + return first.compareTo(second); + } + return r; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockProcessor.java similarity index 73% rename from jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java rename to jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockProcessor.java index d7ba0884e..33568ac6e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockProcessor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockProcessor.java @@ -1,12 +1,13 @@ -package jadx.core.dex.visitors.blocksmaker; +package jadx.core.dex.visitors.blocks; import java.util.ArrayList; import java.util.BitSet; import java.util.Collections; import java.util.Deque; -import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; +import java.util.Set; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -23,16 +24,13 @@ 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.ExcHandlerAttr; -import jadx.core.dex.trycatch.ExceptionHandler; -import jadx.core.dex.trycatch.TryCatchBlock; +import jadx.core.dex.trycatch.TryCatchBlockAttr; import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.utils.BlockUtils; import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; -import static jadx.core.dex.visitors.blocksmaker.BlockSplitter.connect; +import static jadx.core.dex.visitors.blocks.BlockSplitter.connect; import static jadx.core.utils.EmptyBitSet.EMPTY; public class BlockProcessor extends AbstractVisitor { @@ -46,19 +44,16 @@ public class BlockProcessor extends AbstractVisitor { processBlocksTree(mth); } - public static void rerun(MethodNode mth) { - removeMarkedBlocks(mth); - clearBlocksState(mth); - processBlocksTree(mth); - } - private static void processBlocksTree(MethodNode mth) { + removeUnreachableBlocks(mth); + computeDominators(mth); if (independentBlockTreeMod(mth)) { + checkForUnreachableBlocks(mth); clearBlocksState(mth); computeDominators(mth); } - updateExitBlocks(mth); + updateCleanSuccessors(mth); int i = 0; while (modifyBlocksTree(mth)) { @@ -66,7 +61,6 @@ public class BlockProcessor extends AbstractVisitor { clearBlocksState(mth); // recalculate dominators tree computeDominators(mth); - updateExitBlocks(mth); if (i++ > 100) { throw new JadxRuntimeException("CFG modification limit reached, blocks count: " + mth.getBasicBlocks().size()); @@ -77,14 +71,21 @@ public class BlockProcessor extends AbstractVisitor { computeDominanceFrontier(mth); registerLoops(mth); processNestedLoops(mth); + + updateCleanSuccessors(mth); + mth.finishBasicBlocks(); + } + + private static void updateCleanSuccessors(MethodNode mth) { + mth.getBasicBlocks().forEach(BlockNode::updateCleanSuccessors); } private static void checkForUnreachableBlocks(MethodNode mth) { - mth.getBasicBlocks().forEach(block -> { + for (BlockNode block : mth.getBasicBlocks()) { if (block.getPredecessors().isEmpty() && block != mth.getEnterBlock()) { throw new JadxRuntimeException("Unreachable block: " + block); } - }); + } } private static boolean deduplicateBlockInsns(BlockNode block) { @@ -97,6 +98,9 @@ public class BlockProcessor extends AbstractVisitor { if (lastInsn != null && lastInsn.getType() == InsnType.IF) { return false; } + if (BlockUtils.checkFirstInsn(block, insn -> insn.contains(AType.EXC_HANDLER))) { + return false; + } // TODO: implement insn extraction into separate block for partial predecessors int sameInsnCount = getSameLastInsnCount(predecessors); if (sameInsnCount > 0) { @@ -220,7 +224,7 @@ public class BlockProcessor extends AbstractVisitor { } }); - calcImmediateDominators(basicBlocks, entryBlock); + calcImmediateDominators(mth, basicBlocks, entryBlock); } private static void calcDominators(List basicBlocks, BlockNode entryBlock) { @@ -251,7 +255,7 @@ public class BlockProcessor extends AbstractVisitor { } while (changed); } - private static void calcImmediateDominators(List basicBlocks, BlockNode entryBlock) { + private static void calcImmediateDominators(MethodNode mth, List basicBlocks, BlockNode entryBlock) { for (BlockNode block : basicBlocks) { if (block == entryBlock) { continue; @@ -278,10 +282,8 @@ public class BlockProcessor extends AbstractVisitor { } } - private static void computeDominanceFrontier(MethodNode mth) { - for (BlockNode exit : mth.getExitBlocks()) { - exit.setDomFrontier(EMPTY); - } + static void computeDominanceFrontier(MethodNode mth) { + mth.getExitBlock().setDomFrontier(EMPTY); List domSortedBlocks = new ArrayList<>(mth.getBasicBlocks().size()); Deque stack = new LinkedList<>(); stack.push(mth.getEnterBlock()); @@ -336,37 +338,6 @@ public class BlockProcessor extends AbstractVisitor { block.setDomFrontier(domFrontier); } - private static void updateExitBlocks(MethodNode mth) { - mth.getExitBlocks().clear(); - mth.getBasicBlocks().forEach(block -> { - boolean noSuccessors = block.getSuccessors().isEmpty(); - boolean exitBlock = false; - InsnNode lastInsn = BlockUtils.getLastInsn(block); - if (lastInsn != null) { - InsnType insnType = lastInsn.getType(); - if (insnType == InsnType.RETURN) { - block.add(AFlag.RETURN); - exitBlock = true; - if (!noSuccessors) { - throw new JadxRuntimeException("Found a block after RETURN instruction: " + lastInsn + " in block: " + block); - } - } else if (insnType == InsnType.THROW) { - if (noSuccessors) { - exitBlock = true; - } - } - } - if (exitBlock) { - mth.addExitBlock(block); - } else if (noSuccessors - && !mth.isVoidReturn() - && !mth.isConstructor()) { - mth.addComment("JADX INFO: Unexpected exit block: " + block - + ". Expect last instruction to be RETURN or THROW, got: " + lastInsn); - } - }); - } - private static void markLoops(MethodNode mth) { mth.getBasicBlocks().forEach(block -> { // Every successor that dominates its predecessor is a header of a loop, @@ -419,17 +390,7 @@ public class BlockProcessor extends AbstractVisitor { } private static boolean modifyBlocksTree(MethodNode mth) { - List basicBlocks = mth.getBasicBlocks(); - for (BlockNode block : basicBlocks) { - if (block.getPredecessors().isEmpty() && block != mth.getEnterBlock()) { - throw new JadxRuntimeException("Unreachable block: " + block); - } - } - if (mergeExceptionHandlers(mth)) { - removeMarkedBlocks(mth); - return true; - } - for (BlockNode block : basicBlocks) { + for (BlockNode block : mth.getBasicBlocks()) { if (checkLoops(mth, block)) { return true; } @@ -444,19 +405,18 @@ public class BlockProcessor extends AbstractVisitor { if (mth.isVoidReturn()) { return false; } - boolean changed = false; - for (BlockNode exitBlock : new ArrayList<>(mth.getExitBlocks())) { - BlockNode pred = Utils.getOne(exitBlock.getPredecessors()); + for (BlockNode retBlock : new ArrayList<>(mth.getPreExitBlocks())) { + BlockNode pred = Utils.getOne(retBlock.getPredecessors()); if (pred != null) { InsnNode constInsn = Utils.getOne(pred.getInstructions()); if (constInsn != null && constInsn.isConstInsn()) { RegisterArg constArg = constInsn.getResult(); - InsnNode returnInsn = BlockUtils.getLastInsn(exitBlock); - if (returnInsn != null) { + InsnNode returnInsn = BlockUtils.getLastInsn(retBlock); + if (returnInsn != null && returnInsn.getType() == InsnType.RETURN) { InsnArg retArg = returnInsn.getArg(0); if (constArg.sameReg(retArg)) { - mergeConstAndReturnBlocks(mth, exitBlock, pred); + mergeConstAndReturnBlocks(mth, retBlock, pred); changed = true; } } @@ -465,27 +425,33 @@ public class BlockProcessor extends AbstractVisitor { } if (changed) { removeMarkedBlocks(mth); - cleanExitNodes(mth); } return changed; } - private static void mergeConstAndReturnBlocks(MethodNode mth, BlockNode exitBlock, BlockNode pred) { - pred.getInstructions().addAll(exitBlock.getInstructions()); - pred.copyAttributesFrom(exitBlock); - BlockSplitter.removeConnection(pred, exitBlock); - exitBlock.getInstructions().clear(); - exitBlock.add(AFlag.REMOVE); + private static void mergeConstAndReturnBlocks(MethodNode mth, BlockNode retBlock, BlockNode pred) { + pred.getInstructions().addAll(retBlock.getInstructions()); + pred.copyAttributesFrom(retBlock); + BlockSplitter.removeConnection(pred, retBlock); + retBlock.getInstructions().clear(); + retBlock.add(AFlag.REMOVE); + BlockNode exitBlock = mth.getExitBlock(); + BlockSplitter.removeConnection(retBlock, exitBlock); + BlockSplitter.connect(pred, exitBlock); + pred.updateCleanSuccessors(); } private static boolean independentBlockTreeMod(MethodNode mth) { - List basicBlocks = mth.getBasicBlocks(); boolean changed = false; + List basicBlocks = mth.getBasicBlocks(); for (BlockNode basicBlock : basicBlocks) { if (deduplicateBlockInsns(basicBlock)) { changed = true; } } + if (BlockExceptionHandler.process(mth)) { + changed = true; + } for (BlockNode basicBlock : basicBlocks) { if (BlockSplitter.removeEmptyBlock(basicBlock)) { changed = true; @@ -630,138 +596,70 @@ public class BlockProcessor extends AbstractVisitor { return false; } - /** - * Merge handlers for multi-exception catch - */ - private static boolean mergeExceptionHandlers(MethodNode mth) { - for (BlockNode block : mth.getBasicBlocks()) { - ExcHandlerAttr excHandlerAttr = block.get(AType.EXC_HANDLER); - if (excHandlerAttr != null) { - List blocksForMerge = collectExcHandlerBlocks(block, excHandlerAttr); - if (mergeHandlers(mth, blocksForMerge, excHandlerAttr)) { - return true; - } - } - } - return false; - } - - private static List collectExcHandlerBlocks(BlockNode block, ExcHandlerAttr excHandlerAttr) { - List successors = block.getSuccessors(); - if (successors.size() != 1) { - return Collections.emptyList(); - } - RegisterArg reg = getMoveExceptionRegister(block); - if (reg == null) { - return Collections.emptyList(); - } - TryCatchBlock tryBlock = excHandlerAttr.getTryBlock(); - List blocksForMerge = new ArrayList<>(); - BlockNode nextBlock = successors.get(0); - for (BlockNode predBlock : nextBlock.getPredecessors()) { - if (predBlock != block - && checkOtherExcHandler(predBlock, tryBlock, reg)) { - blocksForMerge.add(predBlock); - } - } - return blocksForMerge; - } - - private static boolean checkOtherExcHandler(BlockNode predBlock, TryCatchBlock tryBlock, RegisterArg reg) { - ExcHandlerAttr otherExcHandlerAttr = predBlock.get(AType.EXC_HANDLER); - if (otherExcHandlerAttr == null) { - return false; - } - TryCatchBlock otherTryBlock = otherExcHandlerAttr.getTryBlock(); - if (tryBlock != otherTryBlock) { - return false; - } - RegisterArg otherReg = getMoveExceptionRegister(predBlock); - if (otherReg == null || reg.getRegNum() != otherReg.getRegNum()) { - return false; - } - return true; - } - - private static RegisterArg getMoveExceptionRegister(BlockNode block) { - InsnNode insn = BlockUtils.getLastInsn(block); - if (insn == null || insn.getType() != InsnType.MOVE_EXCEPTION) { - return null; - } - return insn.getResult(); - } - - private static boolean mergeHandlers(MethodNode mth, List blocksForMerge, ExcHandlerAttr excHandlerAttr) { - if (blocksForMerge.isEmpty()) { - return false; - } - TryCatchBlock tryBlock = excHandlerAttr.getTryBlock(); - for (BlockNode block : blocksForMerge) { - ExcHandlerAttr otherExcHandlerAttr = block.get(AType.EXC_HANDLER); - ExceptionHandler excHandler = otherExcHandlerAttr.getHandler(); - excHandlerAttr.getHandler().addCatchTypes(excHandler.getCatchTypes()); - tryBlock.removeHandler(mth, excHandler); - BlockSplitter.detachBlock(block); - } - return true; - } - private static boolean splitReturnBlocks(MethodNode mth) { boolean changed = false; - for (BlockNode exitBlock : mth.getExitBlocks()) { - if (splitReturn(mth, exitBlock)) { + for (BlockNode preExitBlock : mth.getPreExitBlocks()) { + if (splitReturn(mth, preExitBlock)) { changed = true; } } if (changed) { - cleanExitNodes(mth); + updateExitBlockConnections(mth); } return changed; } + private static void updateExitBlockConnections(MethodNode mth) { + BlockNode exitBlock = mth.getExitBlock(); + BlockSplitter.removePredecessors(exitBlock); + for (BlockNode block : mth.getBasicBlocks()) { + if (block != exitBlock + && block.getSuccessors().isEmpty() + && !block.contains(AFlag.REMOVE)) { + BlockSplitter.connect(block, exitBlock); + } + } + } + /** * Splice return block if several predecessors presents */ - private static boolean splitReturn(MethodNode mth, BlockNode exitBlock) { - if (exitBlock.contains(AFlag.SYNTHETIC) - || exitBlock.contains(AFlag.ORIG_RETURN) - || exitBlock.contains(AType.SPLITTER_BLOCK)) { + private static boolean splitReturn(MethodNode mth, BlockNode returnBlock) { + if (returnBlock.contains(AFlag.SYNTHETIC) + || returnBlock.contains(AFlag.ORIG_RETURN) + || returnBlock.contains(AType.EXC_HANDLER)) { return false; } - List preds = exitBlock.getPredecessors(); + List preds = returnBlock.getPredecessors(); if (preds.size() < 2) { return false; } - preds = BlockUtils.filterPredecessors(exitBlock); - if (preds.size() < 2) { - return false; - } - InsnNode returnInsn = BlockUtils.getLastInsn(exitBlock); + InsnNode returnInsn = BlockUtils.getLastInsn(returnBlock); if (returnInsn == null) { return false; } if (returnInsn.getArgsCount() == 1 - && exitBlock.getInstructions().size() == 1 + && returnBlock.getInstructions().size() == 1 && !isReturnArgAssignInPred(preds, returnInsn)) { return false; } boolean first = true; - for (BlockNode pred : preds) { - BlockNode newRetBlock = BlockSplitter.startNewBlock(mth, -1); - newRetBlock.add(AFlag.SYNTHETIC); + for (BlockNode pred : new ArrayList<>(preds)) { if (first) { - newRetBlock.add(AFlag.ORIG_RETURN); - newRetBlock.getInstructions().addAll(exitBlock.getInstructions()); + returnBlock.add(AFlag.ORIG_RETURN); first = false; } else { - for (InsnNode oldInsn : exitBlock.getInstructions()) { + BlockNode newRetBlock = BlockSplitter.startNewBlock(mth, -1); + newRetBlock.add(AFlag.SYNTHETIC); + newRetBlock.add(AFlag.RETURN); + for (InsnNode oldInsn : returnBlock.getInstructions()) { InsnNode copyInsn = oldInsn.copyWithoutSsa(); copyInsn.add(AFlag.SYNTHETIC); newRetBlock.getInstructions().add(copyInsn); } + BlockSplitter.replaceConnection(pred, returnBlock, newRetBlock); } - BlockSplitter.replaceConnection(pred, exitBlock, newRetBlock); } return true; } @@ -783,26 +681,15 @@ public class BlockProcessor extends AbstractVisitor { return false; } - private static void cleanExitNodes(MethodNode mth) { - Iterator iterator = mth.getExitBlocks().iterator(); - while (iterator.hasNext()) { - BlockNode exitBlock = iterator.next(); - if (exitBlock.getPredecessors().isEmpty()) { - mth.getBasicBlocks().remove(exitBlock); - iterator.remove(); - } - } - } - - private static void removeMarkedBlocks(MethodNode mth) { + static void removeMarkedBlocks(MethodNode mth) { mth.getBasicBlocks().removeIf(block -> { if (block.contains(AFlag.REMOVE)) { if (!block.getPredecessors().isEmpty() || !block.getSuccessors().isEmpty()) { LOG.warn("Block {} not deleted, method: {}", block, mth); } else { - CatchAttr catchAttr = block.get(AType.CATCH_BLOCK); - if (catchAttr != null) { - catchAttr.getTryBlock().removeBlock(mth, block); + TryCatchBlockAttr tryBlockAttr = block.get(AType.TRY_BLOCK); + if (tryBlockAttr != null) { + tryBlockAttr.removeBlock(block); } return true; } @@ -811,6 +698,27 @@ public class BlockProcessor extends AbstractVisitor { }); } + private static void removeUnreachableBlocks(MethodNode mth) { + Set toRemove = null; + for (BlockNode block : mth.getBasicBlocks()) { + if (block.getPredecessors().isEmpty() && block != mth.getEnterBlock()) { + toRemove = new LinkedHashSet<>(); + BlockSplitter.collectSuccessors(block, mth.getEnterBlock(), toRemove); + } + } + if (toRemove == null || toRemove.isEmpty()) { + return; + } + + toRemove.forEach(BlockSplitter::detachBlock); + mth.getBasicBlocks().removeAll(toRemove); + long notEmptyBlocks = toRemove.stream().filter(block -> !block.getInstructions().isEmpty()).count(); + if (notEmptyBlocks != 0) { + int insnsCount = toRemove.stream().mapToInt(block -> block.getInstructions().size()).sum(); + mth.addWarnComment("Unreachable blocks removed: " + notEmptyBlocks + ", instructions: " + insnsCount); + } + } + private static void clearBlocksState(MethodNode mth) { mth.getBasicBlocks().forEach(block -> { block.remove(AType.LOOP); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockSplitter.java similarity index 60% rename from jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java rename to jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockSplitter.java index f9498f029..41273c192 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocks/BlockSplitter.java @@ -1,10 +1,10 @@ -package jadx.core.dex.visitors.blocksmaker; +package jadx.core.dex.visitors.blocks; import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.Deque; import java.util.EnumSet; import java.util.HashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -12,6 +12,7 @@ import java.util.Set; 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.TmpEdgeAttr; import jadx.core.dex.instructions.IfNode; import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.TargetInsnNode; @@ -20,9 +21,7 @@ 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.ExcHandlerAttr; 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; @@ -36,7 +35,8 @@ public class BlockSplitter extends AbstractVisitor { InsnType.SWITCH, InsnType.MONITOR_ENTER, InsnType.MONITOR_EXIT, - InsnType.THROW); + InsnType.THROW, + InsnType.MOVE_EXCEPTION); public static boolean isSeparate(InsnType insnType) { return SEPARATE_INSNS.contains(insnType); @@ -47,22 +47,77 @@ public class BlockSplitter extends AbstractVisitor { if (mth.isNoCode()) { return; } - mth.checkInstructions(); - mth.initBasicBlocks(); - splitBasicBlocks(mth); + Map blocksMap = splitBasicBlocks(mth); + setupConnectionsFromJumps(mth, blocksMap); initBlocksInTargetNodes(mth); + addTempConnectionsForExcHandlers(mth, blocksMap); expandMoveMulti(mth); removeJumpAttr(mth); removeInsns(mth); removeEmptyDetachedBlocks(mth); - removeUnreachableBlocks(mth); mth.getBasicBlocks().removeIf(BlockSplitter::removeEmptyBlock); + setupExitConnections(mth); mth.unloadInsnArr(); } + private static Map splitBasicBlocks(MethodNode mth) { + BlockNode enterBlock = startNewBlock(mth, -1); + enterBlock.add(AFlag.MTH_ENTER_BLOCK); + mth.setEnterBlock(enterBlock); + + BlockNode exitBlock = startNewBlock(mth, -1); + exitBlock.add(AFlag.MTH_EXIT_BLOCK); + mth.setExitBlock(exitBlock); + + Map blocksMap = new HashMap<>(); + BlockNode curBlock = enterBlock; + InsnNode prevInsn = null; + for (InsnNode insn : mth.getInstructions()) { + if (insn == null) { + continue; + } + if (insn.getType() == InsnType.NOP && insn.isAttrStorageEmpty()) { + continue; + } + int insnOffset = insn.getOffset(); + if (prevInsn == null) { + // first block after method enter block + curBlock = connectNewBlock(mth, curBlock, insnOffset); + } else { + InsnType prevType = prevInsn.getType(); + switch (prevType) { + case RETURN: + case THROW: + case GOTO: + case IF: + case SWITCH: + // split without connect to next block + curBlock = startNewBlock(mth, insnOffset); + break; + + default: + if (isSeparate(prevType) + || isSeparate(insn.getType()) + || insn.contains(AFlag.TRY_ENTER) + || prevInsn.contains(AFlag.TRY_LEAVE) + || insn.contains(AType.EXC_HANDLER) + || isSplitByJump(prevInsn, insn) + || isDoWhile(blocksMap, curBlock, insn)) { + curBlock = connectNewBlock(mth, curBlock, insnOffset); + } + break; + } + } + blocksMap.put(insnOffset, curBlock); + curBlock.getInstructions().add(insn); + prevInsn = insn; + } + return blocksMap; + } + /** * Init 'then' and 'else' blocks for 'if' instruction. */ @@ -75,114 +130,12 @@ public class BlockSplitter extends AbstractVisitor { }); } - private static void splitBasicBlocks(MethodNode mth) { - InsnNode prevInsn = null; - Map blocksMap = new HashMap<>(); - BlockNode curBlock = startNewBlock(mth, 0); - curBlock.add(AFlag.MTH_ENTER_BLOCK); - 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 - || isSeparate(type)) { - - if (type == InsnType.RETURN || type == InsnType.THROW) { - mth.addExitBlock(curBlock); - } - BlockNode newBlock = startNewBlock(mth, insn.getOffset()); - if (type == InsnType.MONITOR_ENTER || type == InsnType.MONITOR_EXIT) { - connect(curBlock, newBlock); - } - curBlock = newBlock; - startNew = true; - } else { - startNew = isSplitByJump(prevInsn, insn) - || isSeparate(insn.getType()) - || isDoWhile(blocksMap, curBlock, insn) - || insn.contains(AType.EXC_HANDLER) - || prevInsn.contains(AFlag.TRY_LEAVE) - || prevInsn.getType() == InsnType.MOVE_EXCEPTION; - if (startNew) { - curBlock = connectNewBlock(mth, curBlock, insn.getOffset()); - } - } - } - if (insn.contains(AType.EXC_HANDLER)) { - processExceptionHandler(mth, curBlock, insn); - } - if (insn.contains(AFlag.TRY_ENTER)) { - curBlock = insertSplitterBlock(mth, blocksMap, curBlock, insn, startNew); - } else { - blocksMap.put(insn.getOffset(), curBlock); - curBlock.getInstructions().add(insn); - } - prevInsn = insn; - } - // setup missing connections - setupConnections(mth, blocksMap); - } - - /** - * Make separate block for exception handler. New block already added if MOVE_EXCEPTION insn exists. - * Also link ExceptionHandler with current block. - */ - private static void processExceptionHandler(MethodNode mth, BlockNode curBlock, InsnNode insn) { - ExcHandlerAttr excHandlerAttr = insn.get(AType.EXC_HANDLER); - insn.remove(AType.EXC_HANDLER); - - BlockNode excHandlerBlock; - if (insn.getType() == InsnType.MOVE_EXCEPTION) { - excHandlerBlock = curBlock; - } else { - BlockNode newBlock = startNewBlock(mth, -1); - newBlock.add(AFlag.SYNTHETIC); - connect(newBlock, curBlock); - - excHandlerBlock = newBlock; - } - excHandlerBlock.addAttr(excHandlerAttr); - excHandlerAttr.getHandler().setHandlerBlock(excHandlerBlock); - } - - /** - * For try/catch make empty (splitter) block for connect handlers - */ - private static BlockNode insertSplitterBlock(MethodNode mth, Map blocksMap, - BlockNode curBlock, InsnNode insn, boolean startNew) { - BlockNode splitterBlock; - if (insn.getOffset() == 0 || startNew) { - splitterBlock = curBlock; - } else { - splitterBlock = connectNewBlock(mth, curBlock, insn.getOffset()); - } - blocksMap.put(insn.getOffset(), splitterBlock); - - SplitterBlockAttr splitterAttr = new SplitterBlockAttr(splitterBlock); - splitterBlock.add(AFlag.SYNTHETIC); - splitterBlock.addAttr(splitterAttr); - - // add this insn in new block - BlockNode newBlock = startNewBlock(mth, -1); - newBlock.getInstructions().add(insn); - newBlock.addAttr(splitterAttr); - connect(splitterBlock, newBlock); + static BlockNode connectNewBlock(MethodNode mth, BlockNode block, int offset) { + BlockNode newBlock = startNewBlock(mth, offset); + connect(block, newBlock); return newBlock; } - private static BlockNode connectNewBlock(MethodNode mth, BlockNode curBlock, int offset) { - BlockNode block = startNewBlock(mth, offset); - connect(curBlock, block); - return block; - } - static BlockNode startNewBlock(MethodNode mth, int offset) { BlockNode block = new BlockNode(mth.getBasicBlocks().size(), offset); mth.getBasicBlocks().add(block); @@ -203,6 +156,13 @@ public class BlockSplitter extends AbstractVisitor { to.getPredecessors().remove(from); } + static void removePredecessors(BlockNode block) { + for (BlockNode pred : block.getPredecessors()) { + pred.getSuccessors().remove(block); + } + block.getPredecessors().clear(); + } + static void replaceConnection(BlockNode source, BlockNode oldDest, BlockNode newDest) { removeConnection(source, oldDest); connect(source, newDest); @@ -221,6 +181,17 @@ public class BlockSplitter extends AbstractVisitor { return newBlock; } + static BlockNode blockSplitTop(MethodNode mth, BlockNode block) { + BlockNode newBlock = startNewBlock(mth, block.getStartOffset()); + for (BlockNode pred : new ArrayList<>(block.getPredecessors())) { + replaceConnection(pred, block, newBlock); + pred.updateCleanSuccessors(); + } + connect(newBlock, block); + newBlock.updateCleanSuccessors(); + return newBlock; + } + static void replaceTarget(BlockNode source, BlockNode oldTarget, BlockNode newTarget) { InsnNode lastInsn = BlockUtils.getLastInsn(source); if (lastInsn instanceof TargetInsnNode) { @@ -228,7 +199,7 @@ public class BlockSplitter extends AbstractVisitor { } } - private static void setupConnections(MethodNode mth, Map blocksMap) { + private static void setupConnectionsFromJumps(MethodNode mth, Map blocksMap) { for (BlockNode block : mth.getBasicBlocks()) { for (InsnNode insn : block.getInstructions()) { List jumps = insn.getAll(AType.JUMP); @@ -237,43 +208,50 @@ public class BlockSplitter extends AbstractVisitor { BlockNode thisBlock = getBlock(jump.getDest(), blocksMap); connect(srcBlock, thisBlock); } - connectExceptionHandlers(block, insn, blocksMap); } } } - private static void connectExceptionHandlers(BlockNode block, InsnNode insn, - Map blocksMap) { - CatchAttr catches = insn.get(AType.CATCH_BLOCK); - SplitterBlockAttr spl = block.get(AType.SPLITTER_BLOCK); - if (catches == null || spl == null) { - return; - } - BlockNode splitterBlock = spl.getBlock(); - boolean tryEnd = insn.contains(AFlag.TRY_LEAVE); - for (ExceptionHandler h : catches.getTryBlock().getHandlers()) { - BlockNode handlerBlock = initHandlerBlock(h, blocksMap); - // skip self loop in handler - if (splitterBlock != handlerBlock) { - if (!handlerBlock.contains(AType.SPLITTER_BLOCK)) { - handlerBlock.addAttr(spl); + /** + * Connect exception handlers to the throw block. + * This temporary connection needed to build close to final dominators tree. + * Will be used and removed in {@code jadx.core.dex.visitors.blocks.BlockExceptionHandler} + */ + private static void addTempConnectionsForExcHandlers(MethodNode mth, Map blocksMap) { + for (BlockNode block : mth.getBasicBlocks()) { + for (InsnNode insn : block.getInstructions()) { + CatchAttr catchAttr = insn.get(AType.EXC_CATCH); + if (catchAttr == null) { + continue; + } + for (ExceptionHandler handler : catchAttr.getHandlers()) { + BlockNode handlerBlock = getBlock(handler.getHandlerOffset(), blocksMap); + if (!handlerBlock.contains(AType.TMP_EDGE)) { + List preds = block.getPredecessors(); + if (preds.isEmpty()) { + throw new JadxRuntimeException("Unexpected missing predecessor for block: " + block); + } + BlockNode start = preds.size() == 1 ? preds.get(0) : block; + if (!start.getSuccessors().contains(handlerBlock)) { + connect(start, handlerBlock); + handlerBlock.addAttr(new TmpEdgeAttr(start)); + } + } } - connect(splitterBlock, handlerBlock); - } - if (tryEnd) { - connect(block, handlerBlock); } } } - private static BlockNode initHandlerBlock(ExceptionHandler excHandler, Map blocksMap) { - BlockNode handlerBlock = excHandler.getHandlerBlock(); - if (handlerBlock != null) { - return handlerBlock; + private static void setupExitConnections(MethodNode mth) { + BlockNode exitBlock = mth.getExitBlock(); + for (BlockNode block : mth.getBasicBlocks()) { + if (block.getSuccessors().isEmpty() && block != exitBlock) { + connect(block, exitBlock); + if (BlockUtils.checkLastInsnType(block, InsnType.RETURN)) { + block.add(AFlag.RETURN); + } + } } - BlockNode blockByOffset = getBlock(excHandler.getHandleOffset(), blocksMap); - excHandler.setHandlerBlock(blockByOffset); - return blockByOffset; } private static boolean isSplitByJump(InsnNode prevInsn, InsnNode currentInsn) { @@ -358,33 +336,20 @@ public class BlockSplitter extends AbstractVisitor { } } + public static void detachMarkedBlocks(MethodNode mth) { + for (BlockNode block : mth.getBasicBlocks()) { + if (block.contains(AFlag.REMOVE)) { + detachBlock(block); + } + } + } + static boolean removeEmptyDetachedBlocks(MethodNode mth) { return mth.getBasicBlocks().removeIf(block -> block.getInstructions().isEmpty() && block.getPredecessors().isEmpty() && block.getSuccessors().isEmpty() - && !block.contains(AFlag.MTH_ENTER_BLOCK)); - } - - private static boolean removeUnreachableBlocks(MethodNode mth) { - Set toRemove = new LinkedHashSet<>(); - for (BlockNode block : mth.getBasicBlocks()) { - if (block.getPredecessors().isEmpty() && block != mth.getEnterBlock()) { - collectSuccessors(block, mth.getEnterBlock(), toRemove); - } - } - if (toRemove.isEmpty()) { - return false; - } - - toRemove.forEach(BlockSplitter::detachBlock); - mth.getBasicBlocks().removeAll(toRemove); - long notEmptyBlocks = toRemove.stream().filter(block -> !block.getInstructions().isEmpty()).count(); - if (notEmptyBlocks != 0) { - int insnsCount = toRemove.stream().mapToInt(block -> block.getInstructions().size()).sum(); - mth.addAttr(AType.COMMENTS, "JADX INFO: unreachable blocks removed: " + notEmptyBlocks - + ", instructions: " + insnsCount); - } - return true; + && !block.contains(AFlag.MTH_ENTER_BLOCK) + && !block.contains(AFlag.MTH_EXIT_BLOCK)); } static boolean removeEmptyBlock(BlockNode block) { @@ -417,10 +382,11 @@ public class BlockSplitter extends AbstractVisitor { && block.isAttrStorageEmpty() && block.getSuccessors().size() <= 1 && !block.getPredecessors().isEmpty() - && !block.contains(AFlag.MTH_ENTER_BLOCK); + && !block.contains(AFlag.MTH_ENTER_BLOCK) + && !block.contains(AFlag.MTH_EXIT_BLOCK); } - private static void collectSuccessors(BlockNode startBlock, BlockNode methodEnterBlock, Set toRemove) { + static void collectSuccessors(BlockNode startBlock, BlockNode methodEnterBlock, Set toRemove) { Deque stack = new ArrayDeque<>(); stack.add(startBlock); while (!stack.isEmpty()) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockExceptionHandler.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockExceptionHandler.java deleted file mode 100644 index eb99d9f36..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockExceptionHandler.java +++ /dev/null @@ -1,136 +0,0 @@ -package jadx.core.dex.visitors.blocksmaker; - -import jadx.core.dex.attributes.AFlag; -import jadx.core.dex.attributes.AType; -import jadx.core.dex.instructions.InsnType; -import jadx.core.dex.instructions.args.ArgType; -import jadx.core.dex.instructions.args.InsnArg; -import jadx.core.dex.instructions.args.NamedArg; -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.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.InsnRemover; - -public class BlockExceptionHandler extends AbstractVisitor { - - @Override - public void visit(MethodNode mth) { - if (mth.isNoCode()) { - return; - } - for (BlockNode block : mth.getBasicBlocks()) { - markExceptionHandlers(block); - } - for (BlockNode block : mth.getBasicBlocks()) { - block.updateCleanSuccessors(); - } - for (BlockNode block : mth.getBasicBlocks()) { - processExceptionHandlers(mth, block); - } - for (BlockNode block : mth.getBasicBlocks()) { - processTryCatchBlocks(block); - } - } - - /** - * Set exception handler attribute for whole block - */ - private static void markExceptionHandlers(BlockNode block) { - ExcHandlerAttr handlerAttr = block.get(AType.EXC_HANDLER); - if (handlerAttr == null) { - return; - } - ExceptionHandler excHandler = handlerAttr.getHandler(); - ArgType argType = excHandler.getArgType(); - InsnNode me = BlockUtils.getLastInsn(block); - if (me != null && me.getType() == InsnType.MOVE_EXCEPTION) { - // set correct type for 'move-exception' operation - RegisterArg resArg = InsnArg.reg(me.getResult().getRegNum(), argType); - resArg.copyAttributesFrom(me); - me.setResult(resArg); - me.add(AFlag.DONT_INLINE); - resArg.add(AFlag.CUSTOM_DECLARE); - excHandler.setArg(resArg); - me.addAttr(handlerAttr); - return; - } - // handler arguments not used - excHandler.setArg(new NamedArg("unused", argType)); - } - - private static void processExceptionHandlers(MethodNode mth, BlockNode block) { - ExcHandlerAttr handlerAttr = block.get(AType.EXC_HANDLER); - if (handlerAttr == null) { - return; - } - ExceptionHandler excHandler = handlerAttr.getHandler(); - excHandler.addBlock(block); - for (BlockNode node : BlockUtils.collectBlocksDominatedBy(block, block)) { - excHandler.addBlock(node); - } - for (BlockNode excBlock : excHandler.getBlocks()) { - // remove 'monitor-exit' from exception handler blocks - InsnRemover remover = new InsnRemover(mth, excBlock); - for (InsnNode insn : excBlock.getInstructions()) { - if (insn.getType() == InsnType.MONITOR_ENTER) { - break; - } - if (insn.getType() == InsnType.MONITOR_EXIT) { - remover.addAndUnbind(insn); - } - } - remover.perform(); - - // if 'throw' in exception handler block have 'catch' - merge these catch blocks - for (InsnNode insn : excBlock.getInstructions()) { - 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(); - return eh.isCatchAll() || eh.isFinally(); - } - return false; - } - - /** - * If all instructions in block have same 'catch' attribute mark it as 'TryCatch' block. - */ - private static void processTryCatchBlocks(BlockNode block) { - CatchAttr commonCatchAttr = null; - for (InsnNode insn : block.getInstructions()) { - CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK); - if (catchAttr == null) { - continue; - } - if (commonCatchAttr == null) { - commonCatchAttr = catchAttr; - } else if (commonCatchAttr != catchAttr) { - commonCatchAttr = null; - break; - } - } - if (commonCatchAttr != null) { - block.addAttr(commonCatchAttr); - } - } -} 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 deleted file mode 100644 index 0650ff7ff..000000000 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockFinish.java +++ /dev/null @@ -1,72 +0,0 @@ -package jadx.core.dex.visitors.blocksmaker; - -import java.util.HashMap; -import java.util.Map; - -import jadx.core.dex.attributes.AType; -import jadx.core.dex.nodes.BlockNode; -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.utils.BlockUtils; - -public class BlockFinish extends AbstractVisitor { - - @Override - public void visit(MethodNode mth) { - if (mth.isNoCode()) { - return; - } - - for (BlockNode block : mth.getBasicBlocks()) { - block.updateCleanSuccessors(); - fixSplitterBlock(mth, block); - } - - mth.finishBasicBlocks(); - } - - /** - * For every exception handler must be only one splitter block, - * select correct one and remove others if necessary. - */ - private static void fixSplitterBlock(MethodNode mth, 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 splitters = new HashMap<>(); - 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) { - mth.addWarn("Unknown top exception splitter block from list: " + splitters); - return; - } - for (Map.Entry 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); - } - } - } - } -} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java index 6a46b2f14..4e65a90da 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java @@ -173,8 +173,8 @@ public class DebugInfoApplyVisitor extends AbstractVisitor { return; } InsnNode origReturn = null; - List newReturns = new ArrayList<>(mth.getExitBlocks().size()); - for (BlockNode exit : mth.getExitBlocks()) { + List newReturns = new ArrayList<>(mth.getPreExitBlocks().size()); + for (BlockNode exit : mth.getPreExitBlocks()) { InsnNode ret = BlockUtils.getLastInsn(exit); if (ret != null) { if (ret.contains(AFlag.ORIG_RETURN)) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoAttachVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoAttachVisitor.java index 523cbb766..f211fa9ff 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoAttachVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoAttachVisitor.java @@ -19,7 +19,7 @@ import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.parser.SignatureParser; import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.JadxVisitor; -import jadx.core.dex.visitors.blocksmaker.BlockSplitter; +import jadx.core.dex.visitors.blocks.BlockSplitter; import jadx.core.dex.visitors.ssa.SSATransform; import jadx.core.utils.ErrorsCounter; import jadx.core.utils.Utils; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/helpers/FinallyExtractInfo.java b/jadx-core/src/main/java/jadx/core/dex/visitors/finaly/FinallyExtractInfo.java similarity index 95% rename from jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/helpers/FinallyExtractInfo.java rename to jadx-core/src/main/java/jadx/core/dex/visitors/finaly/FinallyExtractInfo.java index 84015459e..ca5e0beaa 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/helpers/FinallyExtractInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/finaly/FinallyExtractInfo.java @@ -1,4 +1,4 @@ -package jadx.core.dex.visitors.blocksmaker.helpers; +package jadx.core.dex.visitors.finaly; import java.util.ArrayList; import java.util.HashSet; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/helpers/InsnsSlice.java b/jadx-core/src/main/java/jadx/core/dex/visitors/finaly/InsnsSlice.java similarity index 97% rename from jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/helpers/InsnsSlice.java rename to jadx-core/src/main/java/jadx/core/dex/visitors/finaly/InsnsSlice.java index 98d8d2870..c00320c46 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/helpers/InsnsSlice.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/finaly/InsnsSlice.java @@ -1,4 +1,4 @@ -package jadx.core.dex.visitors.blocksmaker.helpers; +package jadx.core.dex.visitors.finaly; import java.util.ArrayList; import java.util.IdentityHashMap; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/MarkFinallyVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/finaly/MarkFinallyVisitor.java similarity index 67% rename from jadx-core/src/main/java/jadx/core/dex/visitors/MarkFinallyVisitor.java rename to jadx-core/src/main/java/jadx/core/dex/visitors/finaly/MarkFinallyVisitor.java index f264581b5..51d294891 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/MarkFinallyVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/finaly/MarkFinallyVisitor.java @@ -1,16 +1,14 @@ -package jadx.core.dex.visitors; +package jadx.core.dex.visitors.finaly; import java.util.ArrayList; -import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import jadx.core.Jadx; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.instructions.InsnType; @@ -21,12 +19,16 @@ import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.trycatch.ExceptionHandler; -import jadx.core.dex.trycatch.SplitterBlockAttr; -import jadx.core.dex.trycatch.TryCatchBlock; -import jadx.core.dex.visitors.blocksmaker.helpers.FinallyExtractInfo; -import jadx.core.dex.visitors.blocksmaker.helpers.InsnsSlice; +import jadx.core.dex.trycatch.TryCatchBlockAttr; +import jadx.core.dex.visitors.AbstractVisitor; +import jadx.core.dex.visitors.ConstInlineVisitor; +import jadx.core.dex.visitors.DepthTraversal; +import jadx.core.dex.visitors.IDexTreeVisitor; +import jadx.core.dex.visitors.JadxVisitor; import jadx.core.dex.visitors.ssa.SSATransform; import jadx.core.utils.BlockUtils; +import jadx.core.utils.ListUtils; +import jadx.core.utils.Utils; @JadxVisitor( name = "MarkFinallyVisitor", @@ -43,56 +45,48 @@ public class MarkFinallyVisitor extends AbstractVisitor { return; } try { - mth.clearExceptionHandlers(); - - for (ExceptionHandler excHandler : mth.getExceptionHandlers()) { - processExceptionHandler(mth, excHandler); + boolean applied = false; + List tryBlocks = mth.getAll(AType.TRY_BLOCKS_LIST); + for (TryCatchBlockAttr tryBlock : tryBlocks) { + applied |= processTryBlock(mth, tryBlock); + } + if (applied) { + mth.clearExceptionHandlers(); + // remove merged or empty try blocks from list in method attribute + List clearedTryBlocks = new ArrayList<>(tryBlocks); + if (clearedTryBlocks.removeIf(tb -> tb.isMerged() || tb.getHandlers().isEmpty())) { + mth.remove(AType.TRY_BLOCKS_LIST); + mth.addAttr(AType.TRY_BLOCKS_LIST, clearedTryBlocks); + } } - mth.clearExceptionHandlers(); } catch (Exception e) { LOG.warn("Undo finally extract visitor, mth: {}", mth, e); - try { - // reload method without applying this visitor - // TODO: make more common and less hacky - mth.unload(); - mth.load(); - List passes = Jadx.getPassesList(mth.root().getArgs()); - for (IDexTreeVisitor visitor : passes) { - if (visitor instanceof MarkFinallyVisitor) { - break; - } - visitor.init(mth.root()); - DepthTraversal.visit(visitor, mth); - } - } catch (Exception excInner) { - LOG.error("Undo finally extract failed, mth: {}", mth, excInner); - } + undoFinallyVisitor(mth); } } - private static boolean processExceptionHandler(MethodNode mth, ExceptionHandler excHandler) { - // check if handler has exit edge to block not from this handler - boolean noExitNode = true; + private static boolean processTryBlock(MethodNode mth, TryCatchBlockAttr tryBlock) { + if (tryBlock.isMerged()) { + return false; + } + ExceptionHandler allHandler = null; InsnNode reThrowInsn = null; - - 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) { - reThrowInsn = insns.get(size - 1); + for (ExceptionHandler excHandler : tryBlock.getHandlers()) { + if (excHandler.isCatchAll()) { + allHandler = excHandler; + for (BlockNode excBlock : excHandler.getBlocks()) { + InsnNode lastInsn = BlockUtils.getLastInsn(excBlock); + if (lastInsn != null && lastInsn.getType() == InsnType.THROW) { + reThrowInsn = BlockUtils.getLastInsn(excBlock); + } + } } } - if (noExitNode && reThrowInsn != null) { - boolean extracted = extractFinally(mth, excHandler); - if (extracted) { + if (allHandler != null && reThrowInsn != null) { + if (extractFinally(mth, tryBlock, allHandler)) { reThrowInsn.add(AFlag.DONT_GENERATE); + return true; } - return extracted; } return false; } @@ -100,30 +94,27 @@ public class MarkFinallyVisitor extends AbstractVisitor { /** * Search and mark common code from 'try' block and 'handlers'. */ - private static boolean extractFinally(MethodNode mth, ExceptionHandler allHandler) { - List handlerBlocks = new ArrayList<>(); + private static boolean extractFinally(MethodNode mth, TryCatchBlockAttr tryBlock, ExceptionHandler allHandler) { BlockNode handlerBlock = allHandler.getHandlerBlock(); - - for (BlockNode block : BlockUtils.collectBlocksDominatedByWithExcHandlers(handlerBlock, handlerBlock)) { - InsnNode lastInsn = BlockUtils.getLastInsn(block); - if (lastInsn != null && lastInsn.getType() == InsnType.THROW) { - break; - } - handlerBlocks.add(block); - } + List handlerBlocks = + new ArrayList<>(BlockUtils.collectBlocksDominatedByWithExcHandlers(mth, handlerBlock, handlerBlock)); + handlerBlocks.remove(handlerBlock); // exclude block with 'move-exception' + handlerBlocks.removeIf(b -> BlockUtils.checkLastInsnType(b, InsnType.THROW)); if (handlerBlocks.isEmpty() || BlockUtils.isAllBlocksEmpty(handlerBlocks)) { // remove empty catch - allHandler.getTryBlock().removeHandler(mth, allHandler); + allHandler.getTryBlock().removeHandler(allHandler); return true; } - - BlockNode startBlock = handlerBlocks.get(0); + BlockNode startBlock = Utils.getOne(handlerBlock.getCleanSuccessors()); FinallyExtractInfo extractInfo = new FinallyExtractInfo(allHandler, startBlock, handlerBlocks); - // remove 'finally' from 'catch' handlers - TryCatchBlock tryBlock = allHandler.getTryBlock(); - if (tryBlock.getHandlersCount() > 1) { - for (ExceptionHandler otherHandler : tryBlock.getHandlers()) { + // collect handlers from this and all inner blocks + List handlers = new ArrayList<>(); + collectAllHandlers(tryBlock, handlers); + + // search 'finally' instructions in other handlers + if (!handlers.isEmpty()) { + for (ExceptionHandler otherHandler : handlers) { if (otherHandler == allHandler) { continue; } @@ -135,31 +126,26 @@ public class MarkFinallyVisitor extends AbstractVisitor { } } } - if (extractInfo.getDuplicateSlices().size() != tryBlock.getHandlersCount() - 1) { + if (extractInfo.getDuplicateSlices().size() != handlers.size() - 1) { return false; } } - Set splitters = new HashSet<>(); - 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, check all up paths on each exit (connected with finally exit) + List tryBlocks = allHandler.getTryBlock().getBlocks(); + BlockNode bottomFinallyBlock = BlockUtils.followEmptyPath(BlockUtils.getBottomBlock(allHandler.getBlocks())); + BlockNode bottom = BlockUtils.getNextBlock(bottomFinallyBlock); + if (bottom == null) { + return false; } - - // remove 'finally' from 'try' blocks (dominated by splitter block) boolean found = false; - for (BlockNode splitter : splitters) { - BlockNode start = splitter.getCleanSuccessors().get(0); - List list = new ArrayList<>(); - list.add(start); - list.addAll(BlockUtils.collectBlocksDominatedByWithExcHandlers(start, start)); - Set checkSet = new LinkedHashSet<>(list); - for (BlockNode block : checkSet) { + List pathBlocks = getPathStarts(mth, bottom, bottomFinallyBlock); + for (BlockNode pred : pathBlocks) { + List upPath = BlockUtils.collectPredecessors(mth, pred, tryBlocks); + if (upPath.size() < handlerBlocks.size()) { + continue; + } + for (BlockNode block : upPath) { if (searchDuplicateInsns(block, extractInfo)) { found = true; break; @@ -179,9 +165,36 @@ public class MarkFinallyVisitor extends AbstractVisitor { // 'finally' extract confirmed, apply apply(extractInfo); allHandler.setFinally(true); + + // merge inner try blocks + List innerTryBlocks = tryBlock.getInnerTryBlocks(); + if (!innerTryBlocks.isEmpty()) { + for (TryCatchBlockAttr innerTryBlock : innerTryBlocks) { + tryBlock.getHandlers().addAll(innerTryBlock.getHandlers()); + tryBlock.getBlocks().addAll(innerTryBlock.getBlocks()); + innerTryBlock.setMerged(true); + } + tryBlock.setBlocks(ListUtils.distinctList(tryBlock.getBlocks())); + innerTryBlocks.clear(); + } return true; } + private static List getPathStarts(MethodNode mth, BlockNode bottom, BlockNode bottomFinallyBlock) { + Stream preds = bottom.getPredecessors().stream().filter(b -> b != bottomFinallyBlock); + if (bottom == mth.getExitBlock()) { + preds = preds.flatMap(r -> r.getPredecessors().stream()); + } + return preds.collect(Collectors.toList()); + } + + private static void collectAllHandlers(TryCatchBlockAttr tryBlock, List handlers) { + handlers.addAll(tryBlock.getHandlers()); + for (TryCatchBlockAttr innerTryBlock : tryBlock.getInnerTryBlocks()) { + collectAllHandlers(innerTryBlock, handlers); + } + } + private static boolean checkSlices(FinallyExtractInfo extractInfo) { InsnsSlice finallySlice = extractInfo.getFinallyInsnsSlice(); List finallyInsnsList = finallySlice.getInsnsList(); @@ -398,8 +411,8 @@ public class MarkFinallyVisitor extends AbstractVisitor { InsnsSlice dupSlice, FinallyExtractInfo extractInfo) { InsnsSlice finallySlice = extractInfo.getFinallyInsnsSlice(); - List finallyCS = finallyBlock.getCleanSuccessors(); - List dupCS = dupBlock.getCleanSuccessors(); + List finallyCS = getSuccessorsWithoutLoop(finallyBlock); + List dupCS = getSuccessorsWithoutLoop(dupBlock); if (finallyCS.size() == dupCS.size()) { for (int i = 0; i < finallyCS.size(); i++) { BlockNode finSBlock = finallyCS.get(i); @@ -421,6 +434,13 @@ public class MarkFinallyVisitor extends AbstractVisitor { return true; } + private static List getSuccessorsWithoutLoop(BlockNode block) { + if (block.contains(AFlag.LOOP_END)) { + return block.getCleanSuccessors(); + } + return block.getSuccessors(); + } + private static boolean compareBlocks(BlockNode dupBlock, BlockNode finallyBlock, InsnsSlice dupSlice, FinallyExtractInfo extractInfo) { List dupInsns = dupBlock.getInstructions(); List finallyInsns = finallyBlock.getInstructions(); @@ -459,7 +479,33 @@ public class MarkFinallyVisitor extends AbstractVisitor { if (remArg.isRegister() != fArg.isRegister()) { return false; } + boolean remConst = remArg.isConst(); + if (remConst != fArg.isConst()) { + return false; + } + if (remConst && !remArg.isSameConst(fArg)) { + return false; + } } return true; } + + /** + * Reload method without applying this visitor + */ + private static void undoFinallyVisitor(MethodNode mth) { + try { + // TODO: make more common and less hacky + mth.unload(); + mth.load(); + for (IDexTreeVisitor visitor : mth.root().getPasses()) { + if (visitor instanceof MarkFinallyVisitor) { + break; + } + DepthTraversal.visit(visitor, mth); + } + } catch (Exception e) { + LOG.error("Undo finally extract failed, mth: {}", mth, e); + } + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/IfMakerHelper.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/IfMakerHelper.java index 413ca6c8a..ccfdb1b09 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/IfMakerHelper.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/IfMakerHelper.java @@ -1,7 +1,6 @@ package jadx.core.dex.visitors.regions; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.Set; @@ -28,7 +27,6 @@ import jadx.core.utils.exceptions.JadxRuntimeException; import static jadx.core.dex.visitors.regions.RegionMaker.isEqualPaths; import static jadx.core.dex.visitors.regions.RegionMaker.isEqualReturnBlocks; -import static jadx.core.utils.BlockUtils.getNextBlock; import static jadx.core.utils.BlockUtils.isPathExists; public class IfMakerHelper { @@ -38,15 +36,14 @@ public class IfMakerHelper { } @Nullable - static IfInfo makeIfInfo(BlockNode ifBlock) { + static IfInfo makeIfInfo(MethodNode mth, BlockNode ifBlock) { InsnNode lastInsn = BlockUtils.getLastInsn(ifBlock); if (lastInsn == null || lastInsn.getType() != InsnType.IF) { return null; } IfNode ifNode = (IfNode) lastInsn; IfCondition condition = IfCondition.fromIfNode(ifNode); - IfInfo info = new IfInfo(condition, ifNode.getThenBlock(), ifNode.getElseBlock()); - info.setIfBlock(ifBlock); + IfInfo info = new IfInfo(mth, condition, ifNode.getThenBlock(), ifNode.getElseBlock()); info.getMergedBlocks().add(ifBlock); return info; } @@ -77,7 +74,7 @@ public class IfMakerHelper { boolean badThen = isBadBranchBlock(info, thenBlock); boolean badElse = isBadBranchBlock(info, elseBlock); if (badThen && badElse) { - LOG.debug("Stop processing blocks after 'if': {}, method: {}", info.getIfBlock(), mth); + LOG.debug("Stop processing blocks after 'if': {}, method: {}", info.getMergedBlocks(), mth); return null; } if (badElse) { @@ -88,24 +85,7 @@ public class IfMakerHelper { info = new IfInfo(info, elseBlock, null); info.setOutBlock(thenBlock); } else { - List thenSC = thenBlock.getCleanSuccessors(); - List elseSC = elseBlock.getCleanSuccessors(); - if (thenSC.size() == 1 && sameElements(thenSC, elseSC)) { - info.setOutBlock(thenSC.get(0)); - } else if (info.getMergedBlocks().size() == 1 - && block.getDominatesOn().size() == 2) { - info.setOutBlock(BlockUtils.getPathCross(mth, thenBlock, elseBlock)); - } - } - if (info.getOutBlock() == null) { - for (BlockNode d : block.getDominatesOn()) { - if (d != thenBlock && d != elseBlock - && !info.getMergedBlocks().contains(d) - && isPathExists(thenBlock, d)) { - info.setOutBlock(d); - break; - } - } + info.setOutBlock(BlockUtils.getPathCross(mth, thenBlock, elseBlock)); } if (BlockUtils.isBackEdge(block, info.getOutBlock())) { info.setOutBlock(null); @@ -135,20 +115,16 @@ public class IfMakerHelper { private static boolean allPathsFromIf(BlockNode block, IfInfo info) { List preds = block.getPredecessors(); - Set ifBlocks = info.getMergedBlocks(); + List ifBlocks = info.getMergedBlocks(); for (BlockNode pred : preds) { - pred = BlockUtils.skipSyntheticPredecessor(pred); - if (!ifBlocks.contains(pred) && !pred.contains(AFlag.LOOP_END)) { + BlockNode top = BlockUtils.skipSyntheticPredecessor(pred); + if (!ifBlocks.contains(top) && !top.contains(AFlag.LOOP_END)) { return false; } } return true; } - private static boolean sameElements(Collection c1, Collection c2) { - return c1.size() == c2.size() && c1.containsAll(c2); - } - static IfInfo mergeNestedIfNodes(IfInfo currentIf) { BlockNode curThen = currentIf.getThenBlock(); BlockNode curElse = currentIf.getElseBlock(); @@ -195,8 +171,8 @@ public class IfMakerHelper { return null; } BlockNode otherBranchBlock = followThenBranch ? curElse : curThen; - otherBranchBlock = BlockUtils.skipSyntheticSuccessor(otherBranchBlock); - if (!isPathExists(nextIf.getIfBlock(), otherBranchBlock)) { + otherBranchBlock = BlockUtils.followEmptyPath(otherBranchBlock); + if (!isPathExists(nextIf.getFirstIfBlock(), otherBranchBlock)) { return checkForTernaryInCondition(currentIf); } @@ -236,7 +212,7 @@ public class IfMakerHelper { if (nextThen == null || nextElse == null) { return null; } - if (!nextThen.getIfBlock().getDomFrontier().equals(nextElse.getIfBlock().getDomFrontier())) { + if (!nextThen.getFirstIfBlock().getDomFrontier().equals(nextElse.getFirstIfBlock().getDomFrontier())) { return null; } nextThen = searchNestedIf(nextThen); @@ -256,8 +232,7 @@ public class IfMakerHelper { private static IfInfo mergeTernaryConditions(IfInfo currentIf, IfInfo nextThen, IfInfo nextElse) { IfCondition newCondition = IfCondition.ternary(currentIf.getCondition(), nextThen.getCondition(), nextElse.getCondition()); - IfInfo result = new IfInfo(newCondition, nextThen.getThenBlock(), nextThen.getElseBlock()); - result.setIfBlock(currentIf.getIfBlock()); + IfInfo result = new IfInfo(currentIf.getMth(), newCondition, nextThen.getThenBlock(), nextThen.getElseBlock()); result.merge(currentIf, nextThen, nextElse); confirmMerge(result); return result; @@ -281,62 +256,55 @@ public class IfMakerHelper { } private static IfInfo mergeIfInfo(IfInfo first, IfInfo second, boolean followThenBranch) { - Mode mergeOperation = followThenBranch ? Mode.AND : Mode.OR; - - IfCondition condition = IfCondition.merge(mergeOperation, first.getCondition(), second.getCondition()); - // skip synthetic successor if both parts leads to same block + MethodNode mth = first.getMth(); + Set skipBlocks = first.getSkipBlocks(); BlockNode thenBlock; BlockNode elseBlock; if (followThenBranch) { thenBlock = second.getThenBlock(); - elseBlock = getCrossBlock(first.getElseBlock(), second.getElseBlock()); + elseBlock = getBranchBlock(first.getElseBlock(), second.getElseBlock(), skipBlocks, mth); } else { - thenBlock = getCrossBlock(first.getThenBlock(), second.getThenBlock()); + thenBlock = getBranchBlock(first.getThenBlock(), second.getThenBlock(), skipBlocks, mth); elseBlock = second.getElseBlock(); } - IfInfo result = new IfInfo(condition, thenBlock, elseBlock); - result.setIfBlock(first.getIfBlock()); + Mode mergeOperation = followThenBranch ? Mode.AND : Mode.OR; + IfCondition condition = IfCondition.merge(mergeOperation, first.getCondition(), second.getCondition()); + IfInfo result = new IfInfo(mth, condition, thenBlock, elseBlock); result.merge(first, second); - - BlockNode otherPathBlock; - if (followThenBranch) { - otherPathBlock = first.getElseBlock(); - if (!otherPathBlock.equals(result.getElseBlock())) { - result.getSkipBlocks().add(otherPathBlock); - } - } else { - otherPathBlock = first.getThenBlock(); - if (!otherPathBlock.equals(result.getThenBlock())) { - result.getSkipBlocks().add(otherPathBlock); - } - } - skipSimplePath(otherPathBlock, result.getSkipBlocks()); return result; } - private static BlockNode getCrossBlock(BlockNode first, BlockNode second) { - if (isSameBlocks(first, second)) { + private static BlockNode getBranchBlock(BlockNode first, BlockNode second, Set skipBlocks, MethodNode mth) { + if (first == second) { return second; } - BlockNode firstSkip = BlockUtils.skipSyntheticSuccessor(first); - if (isSameBlocks(firstSkip, second)) { + if (isEqualReturnBlocks(first, second)) { + skipBlocks.add(first); return second; } - BlockNode secondSkip = BlockUtils.skipSyntheticSuccessor(second); - if (isSameBlocks(firstSkip, secondSkip) || isSameBlocks(first, secondSkip)) { + BlockNode cross = BlockUtils.getPathCross(mth, first, second); + if (cross != null) { + BlockUtils.visitBlocksOnPath(mth, first, cross, skipBlocks::add); + BlockUtils.visitBlocksOnPath(mth, second, cross, skipBlocks::add); + skipBlocks.remove(cross); + return cross; + } + BlockNode firstSkip = BlockUtils.followEmptyPath(first); + BlockNode secondSkip = BlockUtils.followEmptyPath(second); + if (firstSkip.equals(secondSkip) || isEqualReturnBlocks(firstSkip, secondSkip)) { + skipBlocks.add(first); + skipBlocks.add(second); + BlockUtils.visitBlocksOnEmptyPath(first, skipBlocks::add); + BlockUtils.visitBlocksOnEmptyPath(second, skipBlocks::add); return secondSkip; } throw new JadxRuntimeException("Unexpected merge pattern"); } - private static boolean isSameBlocks(BlockNode first, BlockNode second) { - return first == second || isEqualReturnBlocks(first, second); - } - static void confirmMerge(IfInfo info) { if (info.getMergedBlocks().size() > 1) { for (BlockNode block : info.getMergedBlocks()) { - if (block != info.getIfBlock()) { + if (block != info.getFirstIfBlock()) { block.add(AFlag.ADDED_TO_REGION); } } @@ -372,7 +340,7 @@ public class IfMakerHelper { } InsnNode lastInsn = BlockUtils.getLastInsn(block); if (lastInsn != null && lastInsn.getType() == InsnType.IF) { - return makeIfInfo(block); + return makeIfInfo(info.getMth(), block); } // skip this block and search in successors chain List successors = block.getSuccessors(); @@ -420,20 +388,11 @@ public class IfMakerHelper { if (!pass) { return null; } - IfInfo nextInfo = makeIfInfo(next); + IfInfo nextInfo = makeIfInfo(info.getMth(), next); if (nextInfo == null) { return getNextIfNodeInfo(info, next); } nextInfo.addInsnsForForcedInline(forceInlineInsns); return nextInfo; } - - private static void skipSimplePath(BlockNode block, Set skipped) { - while (block != null - && block.getCleanSuccessors().size() < 2 - && block.getPredecessors().size() == 1) { - skipped.add(block); - block = getNextBlock(block); - } - } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java index c6c771dac..327e56062 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java @@ -298,6 +298,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor if (iterVar == null) { return false; } + iterVar.remove(AFlag.REMOVE); // restore variable from inlined insn nextCall.add(AFlag.DONT_GENERATE); if (!fixIterableType(mth, iterableArg, iterVar)) { return false; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java index 6fb6a5dec..154d94259 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java @@ -1,11 +1,8 @@ package jadx.core.dex.visitors.regions; -import java.util.BitSet; -import java.util.HashMap; -import java.util.HashSet; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.Set; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; @@ -18,11 +15,8 @@ import jadx.core.dex.regions.AbstractRegion; import jadx.core.dex.regions.Region; import jadx.core.dex.regions.TryCatchRegion; import jadx.core.dex.regions.loops.LoopRegion; -import jadx.core.dex.trycatch.CatchAttr; 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.dex.trycatch.TryCatchBlockAttr; import jadx.core.utils.RegionUtils; /** @@ -34,76 +28,35 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor { if (mth.isNoCode() || mth.isNoExceptionHandlers()) { return; } - - Map tryBlocksMap = new HashMap<>(2); - searchTryCatchDominators(mth, tryBlocksMap); - - IRegionIterativeVisitor visitor = (regionMth, region) -> { - boolean changed = checkAndWrap(regionMth, tryBlocksMap, region); - return changed && !tryBlocksMap.isEmpty(); - }; - DepthRegionTraversal.traverseIncludingExcHandlers(mth, visitor); + List tryBlocks = collectTryCatchBlocks(mth); + if (tryBlocks.isEmpty()) { + return; + } + DepthRegionTraversal.traverseIncludingExcHandlers(mth, (regionMth, region) -> { + boolean changed = checkAndWrap(regionMth, tryBlocks, region); + return changed && !tryBlocks.isEmpty(); + }); } - private static void searchTryCatchDominators(MethodNode mth, Map tryBlocksMap) { - Set tryBlocks = new HashSet<>(); - // collect all try/catch blocks - for (BlockNode block : mth.getBasicBlocks()) { - CatchAttr c = block.get(AType.CATCH_BLOCK); - if (c != null) { - tryBlocks.add(c.getTryBlock()); - } - } - - // for each try block search nearest dominator block - for (TryCatchBlock tb : tryBlocks) { - if (tb.getHandlersCount() == 0) { - // mth.addWarn("No exception handlers in catch block: " + tb); - continue; - } - processTryCatchBlock(mth, tb, tryBlocksMap); + private static List collectTryCatchBlocks(MethodNode mth) { + List list = mth.getAll(AType.TRY_BLOCKS_LIST); + if (list.isEmpty()) { + return Collections.emptyList(); } + List tryBlocks = new ArrayList<>(list); + tryBlocks.sort((a, b) -> a == b ? 0 : a.getOuterTryBlock() == b ? 1 : -1); // move parent try block to top + return tryBlocks; } - private static void processTryCatchBlock(MethodNode mth, TryCatchBlock tb, Map tryBlocksMap) { - BitSet bs = new BitSet(mth.getBasicBlocks().size()); - for (ExceptionHandler excHandler : tb.getHandlers()) { - BlockNode handlerBlock = excHandler.getHandlerBlock(); - if (handlerBlock != null) { - SplitterBlockAttr splitter = handlerBlock.get(AType.SPLITTER_BLOCK); - if (splitter != null) { - BlockNode block = splitter.getBlock(); - bs.set(block.getId()); - } - } - } - List domBlocks = BlockUtils.bitSetToBlocks(mth, bs); - BlockNode domBlock; - if (domBlocks.size() != 1) { - domBlock = BlockUtils.getTopBlock(domBlocks); - if (domBlock == null) { - mth.addWarn("Exception block dominator not found, dom blocks: " + domBlocks); - return; - } - } else { - domBlock = domBlocks.get(0); - } - TryCatchBlock prevTB = tryBlocksMap.put(domBlock, tb); - if (prevTB != null) { - mth.addWarn("Failed to process nested try/catch"); - } - } - - private static boolean checkAndWrap(MethodNode mth, Map tryBlocksMap, IRegion region) { - // search dominator blocks in this region (don't need to go deeper) - for (Map.Entry entry : tryBlocksMap.entrySet()) { - BlockNode dominator = entry.getKey(); - if (region.getSubBlocks().contains(dominator)) { - TryCatchBlock tb = tryBlocksMap.get(dominator); - if (!wrapBlocks(region, tb, dominator)) { + private static boolean checkAndWrap(MethodNode mth, List tryBlocks, IRegion region) { + // search top splitter block in this region (don't need to go deeper) + for (TryCatchBlockAttr tb : tryBlocks) { + BlockNode topSplitter = tb.getTopSplitter(); + if (region.getSubBlocks().contains(topSplitter)) { + if (!wrapBlocks(region, tb, topSplitter)) { mth.addWarn("Can't wrap try/catch for region: " + region); } - tryBlocksMap.remove(dominator); + tryBlocks.remove(tb); return true; } } @@ -113,7 +66,7 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor { /** * Extract all block dominated by 'dominator' to separate region and mark as try/catch block */ - private static boolean wrapBlocks(IRegion replaceRegion, TryCatchBlock tb, BlockNode dominator) { + private static boolean wrapBlocks(IRegion replaceRegion, TryCatchBlockAttr tb, BlockNode dominator) { if (replaceRegion == null) { return false; } @@ -141,7 +94,7 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor { TryCatchRegion tryCatchRegion = new TryCatchRegion(replaceRegion, tryRegion); tryRegion.setParent(tryCatchRegion); - tryCatchRegion.setTryCatchBlock(tb.getCatchAttr().getTryBlock()); + tryCatchRegion.setTryCatchBlock(tb); // replace first node by region IContainer firstNode = tryRegion.getSubBlocks().get(0); @@ -160,7 +113,7 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor { return true; } - private static boolean isHandlerPath(TryCatchBlock tb, IContainer cont) { + private static boolean isHandlerPath(TryCatchBlockAttr tb, IContainer cont) { for (ExceptionHandler h : tb.getHandlers()) { BlockNode handlerBlock = h.getHandlerBlock(); if (handlerBlock != null 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 0110311b9..47897e3ae 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 @@ -5,6 +5,7 @@ import java.util.BitSet; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -41,11 +42,11 @@ 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.dex.trycatch.TryCatchBlockAttr; import jadx.core.utils.BlockUtils; import jadx.core.utils.ErrorsCounter; import jadx.core.utils.RegionUtils; +import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxOverflowException; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -53,9 +54,9 @@ import static jadx.core.dex.visitors.regions.IfMakerHelper.confirmMerge; import static jadx.core.dex.visitors.regions.IfMakerHelper.makeIfInfo; import static jadx.core.dex.visitors.regions.IfMakerHelper.mergeNestedIfNodes; import static jadx.core.dex.visitors.regions.IfMakerHelper.searchNestedIf; +import static jadx.core.utils.BlockUtils.followEmptyPath; import static jadx.core.utils.BlockUtils.getNextBlock; import static jadx.core.utils.BlockUtils.isPathExists; -import static jadx.core.utils.BlockUtils.skipSyntheticSuccessor; public class RegionMaker { private static final Logger LOG = LoggerFactory.getLogger(RegionMaker.class); @@ -125,6 +126,9 @@ public class RegionMaker { * Recursively traverse all blocks from 'block' until block from 'exits' */ private BlockNode traverse(IRegion r, BlockNode block, RegionStack stack) { + if (block.contains(AFlag.MTH_EXIT_BLOCK)) { + return null; + } BlockNode next = null; boolean processed = false; @@ -206,7 +210,7 @@ public class RegionMaker { IRegion outerRegion = stack.peekRegion(); stack.push(loopRegion); - IfInfo condInfo = makeIfInfo(loopRegion.getHeader()); + IfInfo condInfo = makeIfInfo(mth, loopRegion.getHeader()); condInfo = searchNestedIf(condInfo); confirmMerge(condInfo); if (!loop.getLoopBlocks().contains(condInfo.getThenBlock())) { @@ -259,7 +263,7 @@ public class RegionMaker { body = makeRegion(loopBody, stack); } // add blocks from loop start to first condition block - BlockNode conditionBlock = condInfo.getIfBlock(); + BlockNode conditionBlock = condInfo.getFirstIfBlock(); if (loopStart != conditionBlock) { Set blocks = BlockUtils.getAllPathsBlocks(loopStart, conditionBlock); blocks.remove(conditionBlock); @@ -347,13 +351,13 @@ public class RegionMaker { throw new JadxRuntimeException("Not found exit edge by exit block: " + mainExitBlock); } Edge mainExitEdge = mainEdgeOpt.get(); - BlockNode mainOutBlock = skipSyntheticSuccessor(mainExitEdge.getTarget()); + BlockNode mainOutBlock = mainExitEdge.getTarget(); for (Edge exitEdge : exitEdges) { if (exitEdge != mainExitEdge) { - BlockNode outBlock = skipSyntheticSuccessor(exitEdge.getTarget()); // all exit paths must be same or don't cross (will be inside loop) - if (!isEqualPaths(mainOutBlock, outBlock)) { - BlockNode crossBlock = BlockUtils.getPathCross(mth, mainOutBlock, outBlock); + BlockNode exitBlock = exitEdge.getTarget(); + if (!isEqualPaths(mainOutBlock, exitBlock)) { + BlockNode crossBlock = BlockUtils.getPathCross(mth, mainOutBlock, exitBlock); if (crossBlock != null) { return false; } @@ -431,8 +435,7 @@ public class RegionMaker { } private boolean canInsertBreak(BlockNode exit) { - if (exit.contains(AFlag.RETURN) - || BlockUtils.checkLastInsnType(exit, InsnType.BREAK)) { + if (BlockUtils.containsExitInsn(exit)) { return false; } List simplePath = BlockUtils.buildSimplePath(exit); @@ -455,29 +458,27 @@ public class RegionMaker { private boolean insertLoopBreak(RegionStack stack, LoopInfo loop, BlockNode loopExit, Edge exitEdge) { BlockNode exit = exitEdge.getTarget(); - BlockNode insertBlock = null; + Edge insertEdge = null; boolean confirm = false; - // process special cases - if (loopExit == exit) { - // try/catch at loop end - BlockNode source = exitEdge.getSource(); - if (source.contains(AType.CATCH_BLOCK) - && source.getSuccessors().size() == 2) { - BlockNode other = BlockUtils.selectOther(loopExit, source.getSuccessors()); - if (other != null) { - other = BlockUtils.skipSyntheticSuccessor(other); - if (other.contains(AType.EXC_HANDLER)) { - insertBlock = source; - confirm = true; - } - } + // process special cases: + // 1. jump to outer loop + BlockNode exitEnd = BlockUtils.followEmptyPath(exit); + List loops = exitEnd.getAll(AType.LOOP); + for (LoopInfo loopAtEnd : loops) { + if (loopAtEnd != loop) { + insertEdge = exitEdge; + confirm = true; + break; } } + if (!confirm) { + BlockNode insertBlock = null; while (exit != null) { if (insertBlock != null && isPathExists(loopExit, exit)) { // found cross if (canInsertBreak(insertBlock)) { + insertEdge = new Edge(insertBlock, insertBlock.getSuccessors().get(0)); confirm = true; break; } @@ -493,7 +494,7 @@ public class RegionMaker { } InsnNode breakInsn = new InsnNode(InsnType.BREAK, 0); breakInsn.addAttr(AType.LOOP, loop); - EdgeInsnAttr.addEdgeInsn(insertBlock, insertBlock.getSuccessors().get(0), breakInsn); + EdgeInsnAttr.addEdgeInsn(insertEdge, breakInsn); stack.addExit(exit); // add label to 'break' if needed addBreakLabel(exitEdge, exit, breakInsn); @@ -591,7 +592,7 @@ public class RegionMaker { synchRegion.getSubBlocks().add(block); curRegion.getSubBlocks().add(synchRegion); - Set exits = new HashSet<>(); + Set exits = new LinkedHashSet<>(); Set cacheSet = new HashSet<>(); traverseMonitorExits(synchRegion, insn.getArg(0), block, exits, cacheSet); @@ -625,7 +626,7 @@ public class RegionMaker { for (BlockNode exitBlock : exits) { // don't add exit blocks which leads to method end blocks ('return', 'throw', etc) List list = BlockUtils.buildSimplePath(exitBlock); - if (list.isEmpty() || !list.get(list.size() - 1).getSuccessors().isEmpty()) { + if (list.isEmpty() || !BlockUtils.isExitBlock(mth, Utils.last(list))) { stack.addExit(exitBlock); // we can still try using this as an exit block to make sure it's visited. exit = exitBlock; @@ -692,7 +693,7 @@ public class RegionMaker { return ifnode.getThenBlock(); } - IfInfo currentIf = makeIfInfo(block); + IfInfo currentIf = makeIfInfo(mth, block); if (currentIf == null) { return null; } @@ -710,7 +711,7 @@ public class RegionMaker { if (currentIf.getMergedBlocks().size() <= 1) { return null; } - currentIf = makeIfInfo(block); + currentIf = makeIfInfo(mth, block); currentIf = IfMakerHelper.restructureIf(mth, block, currentIf); if (currentIf == null) { // all attempts failed @@ -757,9 +758,6 @@ public class RegionMaker { private void addEdgeInsn(IfInfo ifInfo, Region region, EdgeInsnAttr edgeInsnAttr) { BlockNode start = edgeInsnAttr.getStart(); - if (start.contains(AFlag.ADDED_TO_REGION)) { - return; - } boolean fromThisIf = false; for (BlockNode ifBlock : ifInfo.getMergedBlocks()) { if (ifBlock.getSuccessors().contains(start)) { @@ -792,7 +790,7 @@ public class RegionMaker { BlockNode out; LoopInfo loop = mth.getLoopForBlock(block); if (loop == null) { - out = calcPostDomOut(mth, block, mth.getExitBlocks()); + out = calcPostDomOut(mth, block, mth.getPreExitBlocks()); } else { BlockNode loopEnd = loop.getEnd(); stack.addExit(loop.getStart()); @@ -899,7 +897,7 @@ public class RegionMaker { @Nullable private static BlockNode calcPostDomOut(MethodNode mth, BlockNode block, List exits) { - if (exits.size() == 1 && mth.getExitBlocks().equals(exits)) { + if (exits.size() == 1 && mth.getExitBlock().equals(exits.get(0))) { // simple case: for only one exit which is equal to method exit block return BlockUtils.calcImmediatePostDominator(mth, block); } @@ -992,12 +990,12 @@ public class RegionMaker { return newBlocksMap; } - private static void insertContinueInSwitch(BlockNode block, BlockNode out, BlockNode end) { + private void insertContinueInSwitch(BlockNode block, BlockNode out, BlockNode end) { int endId = end.getId(); for (BlockNode s : block.getCleanSuccessors()) { if (s.getDomFrontier().get(endId) && s != out) { // search predecessor of loop end on path from this successor - List list = BlockUtils.collectBlocksDominatedBy(s, s); + List list = BlockUtils.collectBlocksDominatedBy(mth, s, s); for (BlockNode p : end.getPredecessors()) { if (list.contains(p)) { if (p.isSynthetic()) { @@ -1011,18 +1009,15 @@ public class RegionMaker { } public IRegion processTryCatchBlocks(MethodNode mth) { - Set tcs = new HashSet<>(); - for (ExceptionHandler handler : mth.getExceptionHandlers()) { - tcs.add(handler.getTryBlock()); - } - for (TryCatchBlock tc : tcs) { + List tcs = mth.getAll(AType.TRY_BLOCKS_LIST); + for (TryCatchBlockAttr tc : tcs) { List blocks = new ArrayList<>(tc.getHandlersCount()); Set splitters = new HashSet<>(); for (ExceptionHandler handler : tc.getHandlers()) { BlockNode handlerBlock = handler.getHandlerBlock(); if (handlerBlock != null) { blocks.add(handlerBlock); - splitters.addAll(handlerBlock.getPredecessors()); + splitters.add(BlockUtils.getTopSplitterForHandler(handlerBlock)); } else { LOG.debug(ErrorsCounter.formatMsg(mth, "No exception handler block: " + handler)); } @@ -1055,12 +1050,12 @@ public class RegionMaker { /** * Search handlers successor blocks not included in any region. */ - protected IRegion processHandlersOutBlocks(MethodNode mth, Set tcs) { + protected IRegion processHandlersOutBlocks(MethodNode mth, List tcs) { Set allRegionBlocks = new HashSet<>(); RegionUtils.getAllRegionBlocks(mth.getRegion(), allRegionBlocks); Set succBlocks = new HashSet<>(); - for (TryCatchBlock tc : tcs) { + for (TryCatchBlockAttr tc : tcs) { for (ExceptionHandler handler : tc.getHandlers()) { IContainer region = handler.getHandlerRegion(); if (region != null) { @@ -1093,11 +1088,7 @@ public class RegionMaker { RegionStack stack = new RegionStack(this.mth); BlockNode dom; if (handler.isFinally()) { - SplitterBlockAttr splitterAttr = start.get(AType.SPLITTER_BLOCK); - if (splitterAttr == null) { - return; - } - dom = splitterAttr.getBlock(); + dom = BlockUtils.getTopSplitterForHandler(start); } else { dom = start; stack.addExits(exits); @@ -1131,13 +1122,13 @@ public class RegionMaker { if (b1 == null || b2 == null) { return false; } - return isEqualReturnBlocks(b1, b2) || isSyntheticPath(b1, b2); + return isEqualReturnBlocks(b1, b2) || isEmptySyntheticPath(b1, b2); } - private static boolean isSyntheticPath(BlockNode b1, BlockNode b2) { - BlockNode n1 = skipSyntheticSuccessor(b1); - BlockNode n2 = skipSyntheticSuccessor(b2); - return (n1 != b1 || n2 != b2) && isEqualPaths(n1, n2); + private static boolean isEmptySyntheticPath(BlockNode b1, BlockNode b2) { + BlockNode n1 = followEmptyPath(b1); + BlockNode n2 = followEmptyPath(b2); + return n1 == n2 || isEqualReturnBlocks(n1, n2); } public static boolean isEqualReturnBlocks(BlockNode b1, BlockNode b2) { @@ -1154,6 +1145,17 @@ public class RegionMaker { if (i1.getArgsCount() != i2.getArgsCount()) { return false; } - return i1.getArgsCount() == 0 || i1.getArg(0).equals(i2.getArg(0)); + if (i1.getArgsCount() == 0) { + return true; + } + InsnArg firstArg = i1.getArg(0); + InsnArg secondArg = i2.getArg(0); + if (firstArg.isSameConst(secondArg)) { + return true; + } + if (i1.getSourceLine() != i2.getSourceLine()) { + return false; + } + return firstArg.equals(secondArg); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMakerVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMakerVisitor.java index 2726b83db..a26c28a90 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMakerVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/RegionMakerVisitor.java @@ -28,6 +28,7 @@ import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.utils.InsnRemover; import jadx.core.utils.RegionUtils; +import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxException; /** @@ -47,7 +48,8 @@ public class RegionMakerVisitor extends AbstractVisitor { RegionStack state = new RegionStack(mth); // fill region structure - mth.setRegion(rm.makeRegion(mth.getEnterBlock(), state)); + BlockNode startBlock = Utils.first(mth.getEnterBlock().getCleanSuccessors()); + mth.setRegion(rm.makeRegion(startBlock, state)); if (!mth.isNoExceptionHandlers()) { IRegion expOutBlock = rm.processTryCatchBlocks(mth); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ReturnVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ReturnVisitor.java index 87d3c7598..a61807727 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ReturnVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ReturnVisitor.java @@ -24,7 +24,6 @@ public class ReturnVisitor extends AbstractVisitor { @Override public void visit(MethodNode mth) throws JadxException { - // remove useless returns in void methods if (mth.isVoidReturn()) { DepthRegionTraversal.traverse(mth, new ReturnRemoverVisitor()); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/TernaryMod.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/TernaryMod.java index cbfbc1c49..d9e734ea0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/TernaryMod.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/TernaryMod.java @@ -276,9 +276,10 @@ public class TernaryMod implements IRegionIterativeVisitor { if (!ifRegion.getParent().replaceSubBlock(ifRegion, header)) { return; } - InsnList.remove(block, insn); TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(), phiInsn.getResult(), InsnArg.wrapInsnIntoArg(insn), otherArg); + InsnRemover.unbindResult(mth, insn); + InsnList.remove(block, insn); InsnRemover.unbindAllArgs(mth, phiInsn); header.getInstructions().clear(); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/CodeShrinkVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/CodeShrinkVisitor.java index 916dfb61f..8b223efdb 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/CodeShrinkVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/shrink/CodeShrinkVisitor.java @@ -42,7 +42,7 @@ public class CodeShrinkVisitor extends AbstractVisitor { } for (BlockNode block : mth.getBasicBlocks()) { shrinkBlock(mth, block); - simplifyMoveInsns(block); + simplifyMoveInsns(mth, block); } } @@ -153,6 +153,7 @@ public class CodeShrinkVisitor extends AbstractVisitor { if (parentInsn != null) { parentInsn.inheritMetadata(insn); } + InsnRemover.unbindResult(mth, insn); InsnRemover.removeWithoutUnbind(mth, block, insn); } return replaced; @@ -211,7 +212,7 @@ public class CodeShrinkVisitor extends AbstractVisitor { throw new JadxRuntimeException("Can't process instruction move : " + assignBlock); } - private static void simplifyMoveInsns(BlockNode block) { + private static void simplifyMoveInsns(MethodNode mth, BlockNode block) { List insns = block.getInstructions(); int size = insns.size(); for (int i = 0; i < size; i++) { @@ -221,9 +222,9 @@ public class CodeShrinkVisitor extends AbstractVisitor { InsnArg arg = insn.getArg(0); if (arg.isInsnWrap()) { InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn(); - wrapInsn.setResult(insn.getResult()); - wrapInsn.copyAttributesFrom(insn); - wrapInsn.addSourceLineFrom(insn); + InsnRemover.unbindResult(mth, wrapInsn); + wrapInsn.setResult(insn.getResult().duplicate()); + wrapInsn.inheritMetadata(insn); wrapInsn.setOffset(insn.getOffset()); wrapInsn.remove(AFlag.WRAPPED); block.getInstructions().set(i, wrapInsn); 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 142e47106..656ce5fa8 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 @@ -20,7 +20,7 @@ import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.JadxVisitor; -import jadx.core.dex.visitors.blocksmaker.BlockFinish; +import jadx.core.dex.visitors.blocks.BlockProcessor; import jadx.core.utils.InsnList; import jadx.core.utils.InsnRemover; import jadx.core.utils.exceptions.JadxException; @@ -29,7 +29,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException; @JadxVisitor( name = "SSATransform", desc = "Calculate Single Side Assign (SSA) variables", - runAfter = BlockFinish.class + runAfter = BlockProcessor.class ) public class SSATransform extends AbstractVisitor { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java index 8605f405b..a77d60392 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java @@ -48,7 +48,7 @@ import jadx.core.dex.visitors.ConstInlineVisitor; import jadx.core.dex.visitors.InitCodeVariables; import jadx.core.dex.visitors.JadxVisitor; import jadx.core.dex.visitors.ModVisitor; -import jadx.core.dex.visitors.blocksmaker.BlockSplitter; +import jadx.core.dex.visitors.blocks.BlockSplitter; import jadx.core.dex.visitors.ssa.SSATransform; import jadx.core.utils.BlockUtils; import jadx.core.utils.InsnList; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java index 30a0ca158..179e2226d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java @@ -84,8 +84,9 @@ public final class TypeUpdate { return SAME; } if (Consts.DEBUG_TYPE_INFERENCE) { - LOG.debug("Applying types for {} -> {}", ssaVar, candidateType); - updates.forEach(updateEntry -> LOG.debug(" {} -> {}", updateEntry.getType(), updateEntry.getArg())); + LOG.debug("Applying type {} to {}", ssaVar.toShortString(), candidateType); + updates.forEach(updateEntry -> LOG.debug(" {} -> {} in {}", + updateEntry.getType(), updateEntry.getArg(), updateEntry.getArg().getParentInsn())); } updateInfo.applyUpdates(); return CHANGED; @@ -334,7 +335,6 @@ public final class TypeUpdate { argNum -> typeUtils.replaceClassGenerics(candidateType, argTypes.get(argNum))); } return SAME; - } private TypeUpdateResult applyInvokeTypes(TypeUpdateInfo updateInfo, BaseInvokeNode invoke, int argsCount, @@ -439,14 +439,15 @@ public final class TypeUpdate { } boolean allSame = true; for (InsnArg insnArg : insn.getArguments()) { - if (insnArg != arg) { - TypeUpdateResult result = updateTypeChecked(updateInfo, insnArg, candidateType); - if (result == REJECT) { - return result; - } - if (result != SAME) { - allSame = false; - } + if (insnArg == arg) { + continue; + } + TypeUpdateResult result = updateTypeChecked(updateInfo, insnArg, candidateType); + if (result == REJECT) { + return result; + } + if (result != SAME) { + allSame = false; } } return allSame ? SAME : CHANGED; 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 9de1f3681..426383af4 100644 --- a/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/BlockUtils.java @@ -1,20 +1,26 @@ package jadx.core.utils; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Arrays; import java.util.BitSet; import java.util.Collection; import java.util.Collections; +import java.util.Deque; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Queue; import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Predicate; import org.jetbrains.annotations.Nullable; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; -import jadx.core.dex.attributes.nodes.IgnoreEdgeAttr; import jadx.core.dex.attributes.nodes.LoopInfo; import jadx.core.dex.attributes.nodes.PhiListAttr; import jadx.core.dex.instructions.IfNode; @@ -76,8 +82,10 @@ public class BlockUtils { return null; } - public static boolean isBlockMustBeCleared(BlockNode b) { - if (b.contains(AType.EXC_HANDLER) || b.contains(AFlag.REMOVE)) { + public static boolean isExceptionHandlerPath(BlockNode b) { + if (b.contains(AType.EXC_HANDLER) + || b.contains(AFlag.EXC_BOTTOM_SPLITTER) + || b.contains(AFlag.REMOVE)) { return true; } if (b.contains(AFlag.SYNTHETIC)) { @@ -93,7 +101,7 @@ public class BlockUtils { private static List cleanBlockList(List list) { List ret = new ArrayList<>(list.size()); for (BlockNode block : list) { - if (!isBlockMustBeCleared(block)) { + if (!isExceptionHandlerPath(block)) { ret.add(block); } } @@ -106,29 +114,12 @@ public class BlockUtils { public static void cleanBitSet(MethodNode mth, BitSet bs) { for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) { BlockNode block = mth.getBasicBlocks().get(i); - if (isBlockMustBeCleared(block)) { + if (isExceptionHandlerPath(block)) { bs.clear(i); } } } - /** - * Return predecessors list without blocks contains 'IGNORE_EDGE' attribute. - * - * @return new list of filtered predecessors - */ - public 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; - } - public static boolean isBackEdge(BlockNode from, BlockNode to) { if (to == null) { return false; @@ -172,11 +163,36 @@ public class BlockUtils { return false; } + public static boolean checkFirstInsn(IBlock block, Predicate predicate) { + InsnNode insn = getFirstInsn(block); + return insn != null && predicate.test(insn); + } + public static boolean checkLastInsnType(IBlock block, InsnType expectedType) { InsnNode insn = getLastInsn(block); return insn != null && insn.getType() == expectedType; } + public static InsnNode getLastInsnWithType(IBlock block, InsnType expectedType) { + InsnNode insn = getLastInsn(block); + if (insn != null && insn.getType() == expectedType) { + return insn; + } + return null; + } + + @Nullable + public static InsnNode getFirstInsn(@Nullable IBlock block) { + if (block == null) { + return null; + } + List insns = block.getInstructions(); + if (insns.isEmpty()) { + return null; + } + return insns.get(0); + } + @Nullable public static InsnNode getLastInsn(@Nullable IBlock block) { if (block == null) { @@ -189,6 +205,26 @@ public class BlockUtils { return insns.get(insns.size() - 1); } + public static boolean isExitBlock(MethodNode mth, BlockNode block) { + BlockNode exitBlock = mth.getExitBlock(); + if (block == exitBlock) { + return true; + } + return exitBlock.getPredecessors().contains(block); + } + + public static boolean containsExitInsn(IBlock block) { + InsnNode lastInsn = BlockUtils.getLastInsn(block); + if (lastInsn == null) { + return false; + } + InsnType type = lastInsn.getType(); + return type == InsnType.RETURN + || type == InsnType.THROW + || type == InsnType.BREAK + || type == InsnType.CONTINUE; + } + @Nullable public static BlockNode getBlockByInsn(MethodNode mth, @Nullable InsnNode insn) { if (insn == null) { @@ -302,7 +338,7 @@ public class BlockUtils { } public static BitSet blocksToBitSet(MethodNode mth, Collection blocks) { - BitSet bs = new BitSet(mth.getBasicBlocks().size()); + BitSet bs = newBlocksBitSet(mth); for (BlockNode block : blocks) { bs.set(block.getId()); } @@ -333,6 +369,16 @@ public class BlockUtils { return blocks; } + public static void forEachBlockFromBitSet(MethodNode mth, BitSet bs, Consumer consumer) { + if (bs == null || bs == EmptyBitSet.EMPTY || bs.isEmpty()) { + return; + } + List blocks = mth.getBasicBlocks(); + for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) { + consumer.accept(blocks.get(i)); + } + } + /** * Return first successor which not exception handler and not follow loop back edge */ @@ -358,6 +404,85 @@ public class BlockUtils { return null; } + /** + * Visit blocks on any path from start to end. + * Only one path will be visited! + */ + public static boolean visitBlocksOnPath(MethodNode mth, BlockNode start, BlockNode end, Consumer visitor) { + visitor.accept(start); + if (start == end) { + return true; + } + if (start.getCleanSuccessors().contains(end)) { + visitor.accept(end); + return true; + } + // DFS on clean successors + BitSet visited = newBlocksBitSet(mth); + Deque queue = new ArrayDeque<>(); + queue.addLast(start); + while (true) { + BlockNode current = queue.peekLast(); + if (current == null) { + return false; + } + boolean added = false; + for (BlockNode next : current.getCleanSuccessors()) { + if (next == end) { + queue.removeFirst(); // start already visited + queue.addLast(next); + queue.forEach(visitor); + return true; + } + int id = next.getId(); + if (!visited.get(id)) { + visited.set(id); + queue.addLast(next); + added = true; + break; + } + } + if (!added) { + queue.pollLast(); + if (queue.isEmpty()) { + return false; + } + } + } + } + + public static List collectPredecessors(MethodNode mth, BlockNode start, Collection stopBlocks) { + BitSet bs = newBlocksBitSet(mth); + if (!stopBlocks.isEmpty()) { + bs.or(blocksToBitSet(mth, stopBlocks)); + } + List list = new ArrayList<>(); + traversePredecessors(start, bs, list::add); + return list; + } + + /** + * Up BFS + */ + private static void traversePredecessors(BlockNode start, BitSet visited, Consumer visitor) { + Queue queue = new ArrayDeque<>(); + queue.add(start); + while (true) { + BlockNode current = queue.poll(); + if (current == null) { + return; + } + visitor.accept(current); + for (BlockNode next : current.getPredecessors()) { + int id = next.getId(); + if (!visited.get(id)) { + visited.set(id); + queue.add(next); + } + } + } + } + /** * Collect blocks from all possible execution paths from 'start' to 'end' */ @@ -399,6 +524,15 @@ public class BlockUtils { return false; } + public static boolean isPathExists(Collection startBlocks, BlockNode end) { + for (BlockNode startBlock : startBlocks) { + if (!isPathExists(startBlock, end)) { + return false; + } + } + return true; + } + public static boolean isPathExists(BlockNode start, BlockNode end) { if (start == end || end.isDominator(start) @@ -442,6 +576,28 @@ public class BlockUtils { return null; } + /** + * Search last block in control flow graph from input set. + */ + public static BlockNode getBottomBlock(Collection blocks) { + if (blocks.size() == 1) { + return blocks.iterator().next(); + } + for (BlockNode bottomCandidate : blocks) { + boolean bottom = true; + for (BlockNode from : blocks) { + if (bottomCandidate != from && !isAnyPathExists(from, bottomCandidate)) { + bottom = false; + break; + } + } + if (bottom) { + return bottomCandidate; + } + } + return null; + } + public static boolean isOnlyOnePathExists(BlockNode start, BlockNode end) { if (start == end) { return true; @@ -476,57 +632,126 @@ public class BlockUtils { return null; } + /** + * Search lowest common ancestor in dominator tree for input set. + */ + @Nullable + public static BlockNode getCommonDominator(MethodNode mth, List blocks) { + BitSet doms = newBlocksBitSet(mth); + // collect all dominators from input set + doms.set(0, mth.getBasicBlocks().size()); + blocks.forEach(b -> doms.and(b.getDoms())); + // exclude all dominators of immediate dominator (including self) + BitSet combine = newBlocksBitSet(mth); + combine.or(doms); + forEachBlockFromBitSet(mth, doms, block -> { + BlockNode idom = block.getIDom(); + if (idom != null) { + combine.andNot(idom.getDoms()); + combine.clear(idom.getId()); + } + }); + return bitSetToOneBlock(mth, combine); + } + + /** + * Return common cross block for input set. + * + * @return null if cross is a method exit block. + */ + @Nullable + public static BlockNode getPathCross(MethodNode mth, Collection blocks) { + BitSet domFrontBS = newBlocksBitSet(mth); + boolean first = true; + for (BlockNode b : blocks) { + if (first) { + domFrontBS.or(b.getDomFrontier()); + first = false; + } else { + domFrontBS.and(b.getDomFrontier()); + } + } + domFrontBS.clear(mth.getExitBlock().getId()); + if (domFrontBS.isEmpty()) { + return null; + } + BlockNode oneBlock = bitSetToOneBlock(mth, domFrontBS); + if (oneBlock != null) { + return oneBlock; + } + BitSet excluded = newBlocksBitSet(mth); + // exclude method exit and loop start blocks + excluded.set(mth.getExitBlock().getId()); + // exclude loop start blocks + mth.getLoops().forEach(l -> excluded.set(l.getStart().getId())); + if (!mth.isNoExceptionHandlers()) { + // exclude exception handlers paths + mth.getExceptionHandlers().forEach(h -> excluded.or(h.getHandlerBlock().getDomFrontier())); + } + domFrontBS.andNot(excluded); + oneBlock = bitSetToOneBlock(mth, domFrontBS); + if (oneBlock != null) { + return oneBlock; + } + BitSet combinedDF = newBlocksBitSet(mth); + while (true) { + // collect dom frontier blocks from current set until only one block left + forEachBlockFromBitSet(mth, domFrontBS, block -> { + BitSet domFrontier = block.getDomFrontier(); + if (!domFrontier.isEmpty()) { + combinedDF.or(domFrontier); + combinedDF.clear(block.getId()); + } + }); + combinedDF.andNot(excluded); + int cardinality = combinedDF.cardinality(); + if (cardinality == 1) { + return bitSetToOneBlock(mth, combinedDF); + } + if (cardinality == 0) { + return null; + } + // replace domFrontBS with combinedDF + domFrontBS.clear(); + domFrontBS.or(combinedDF); + combinedDF.clear(); + } + } + public static BlockNode getPathCross(MethodNode mth, BlockNode b1, BlockNode b2) { + if (b1 == b2) { + return b1; + } if (b1 == null || b2 == null) { return null; } - if (b1.getDomFrontier() == null || b2.getDomFrontier() == null) { - return null; - } - BitSet b = new BitSet(); - b.or(b1.getDomFrontier()); - b.and(b2.getDomFrontier()); - b.clear(b1.getId()); - b.clear(b2.getId()); - if (b.cardinality() == 1) { - BlockNode end = mth.getBasicBlocks().get(b.nextSetBit(0)); - if (isPathExists(b1, end) && isPathExists(b2, end)) { - return end; - } - } - if (isPathExists(b1, b2)) { - return b2; - } - if (isPathExists(b2, b1)) { - return b1; - } - return null; + return getPathCross(mth, Arrays.asList(b1, b2)); } /** * Collect all block dominated by 'dominator', starting from 'start' */ - public static List collectBlocksDominatedBy(BlockNode dominator, BlockNode start) { + public static List collectBlocksDominatedBy(MethodNode mth, BlockNode dominator, BlockNode start) { List result = new ArrayList<>(); - collectWhileDominates(dominator, start, result, new HashSet<>(), false); + collectWhileDominates(dominator, start, result, newBlocksBitSet(mth), false); return result; } /** - * Collect all block dominated by 'dominator', starting from 'start', include exception handlers + * Collect all block dominated by 'dominator', starting from 'start', including exception handlers */ - public static List collectBlocksDominatedByWithExcHandlers(BlockNode dominator, BlockNode start) { - List result = new ArrayList<>(); - collectWhileDominates(dominator, start, result, new HashSet<>(), true); + public static Set collectBlocksDominatedByWithExcHandlers(MethodNode mth, BlockNode dominator, BlockNode start) { + Set result = new LinkedHashSet<>(); + collectWhileDominates(dominator, start, result, newBlocksBitSet(mth), true); return result; } - private static void collectWhileDominates(BlockNode dominator, BlockNode child, List result, - Set visited, boolean includeExcHandlers) { - if (visited.contains(child)) { + private static void collectWhileDominates(BlockNode dominator, BlockNode child, Collection result, + BitSet visited, boolean includeExcHandlers) { + if (visited.get(child.getId())) { return; } - visited.add(child); + visited.set(child.getId()); List successors = includeExcHandlers ? child.getSuccessors() : child.getCleanSuccessors(); for (BlockNode node : successors) { if (node.isDominator(dominator)) { @@ -562,7 +787,8 @@ public class BlockUtils { public static void skipPredSyntheticPaths(BlockNode block) { for (BlockNode pred : block.getPredecessors()) { if (pred.contains(AFlag.SYNTHETIC) - && !pred.contains(AType.SPLITTER_BLOCK) + && !pred.contains(AFlag.EXC_TOP_SPLITTER) + && !pred.contains(AFlag.EXC_BOTTOM_SPLITTER) && pred.getInstructions().isEmpty()) { pred.add(AFlag.DONT_GENERATE); skipPredSyntheticPaths(pred); @@ -570,6 +796,43 @@ public class BlockUtils { } } + /** + * Follow empty blocks and return end of path block (first not empty). + * Return start block if no such path. + */ + public static BlockNode followEmptyPath(BlockNode start) { + while (true) { + BlockNode next = getNextBlockOnEmptyPath(start); + if (next == null) { + return start; + } + start = next; + } + } + + public static void visitBlocksOnEmptyPath(BlockNode start, Consumer visitor) { + while (true) { + BlockNode next = getNextBlockOnEmptyPath(start); + if (next == null) { + return; + } + visitor.accept(next); + start = next; + } + } + + @Nullable + private static BlockNode getNextBlockOnEmptyPath(BlockNode block) { + if (!block.getInstructions().isEmpty() || block.getPredecessors().size() > 1) { + return null; + } + List successors = block.getCleanSuccessors(); + if (successors.size() != 1) { + return null; + } + return successors.get(0); + } + /** * Return true if on path from start to end no instructions and no branches. */ @@ -591,21 +854,13 @@ public class BlockUtils { return block == end; } - /** - * Return successor of synthetic block or same block otherwise. - */ - public static BlockNode skipSyntheticSuccessor(BlockNode block) { - if (block.isSynthetic() && block.getSuccessors().size() == 1) { - return block.getSuccessors().get(0); - } - return block; - } - /** * Return predecessor of synthetic block or same block otherwise. */ public static BlockNode skipSyntheticPredecessor(BlockNode block) { - if (block.isSynthetic() && block.getPredecessors().size() == 1) { + if (block.isSynthetic() + && block.getInstructions().isEmpty() + && block.getPredecessors().size() == 1) { return block.getPredecessors().get(0); } return block; @@ -626,12 +881,69 @@ public class BlockUtils { return insns; } + /** + * Return limited number of instructions from method. + * Return empty list if method contains more than limit. + */ + public static List collectInsnsWithLimit(List blocks, int limit) { + List insns = new ArrayList<>(limit); + for (BlockNode block : blocks) { + List blockInsns = block.getInstructions(); + int blockSize = blockInsns.size(); + if (blockSize == 0) { + continue; + } + if (insns.size() + blockSize > limit) { + return Collections.emptyList(); + } + insns.addAll(blockInsns); + } + return insns; + } + + /** + * Return insn if it is only one instruction in this method. Return null otherwise. + */ + @Nullable + public static InsnNode getOnlyOneInsnFromMth(MethodNode mth) { + InsnNode insn = null; + for (BlockNode block : mth.getBasicBlocks()) { + List blockInsns = block.getInstructions(); + int blockSize = blockInsns.size(); + if (blockSize == 0) { + continue; + } + if (blockSize > 1) { + return null; + } + if (insn != null) { + return null; + } + insn = blockInsns.get(0); + } + return insn; + } + public static boolean isFirstInsn(MethodNode mth, InsnNode insn) { - BlockNode enterBlock = mth.getEnterBlock(); - if (enterBlock == null || enterBlock.getInstructions().isEmpty()) { + BlockNode startBlock = followEmptyPath(mth.getEnterBlock()); + if (startBlock != null && !startBlock.getInstructions().isEmpty()) { + return startBlock.getInstructions().get(0) == insn; + } + // handle branching with empty blocks + BlockNode block = getBlockByInsn(mth, insn); + if (block == null) { + throw new JadxRuntimeException("Insn not found in method: " + insn); + } + if (block.getInstructions().get(0) != insn) { return false; } - return enterBlock.getInstructions().get(0) == insn; + Set allPathsBlocks = getAllPathsBlocks(mth.getEnterBlock(), block); + for (BlockNode pathBlock : allPathsBlocks) { + if (!pathBlock.getInstructions().isEmpty() && pathBlock != block) { + return false; + } + } + return true; } /** @@ -709,7 +1021,7 @@ public class BlockUtils { } public static Map calcPostDominance(MethodNode mth) { - return calcPartialPostDominance(mth, mth.getBasicBlocks(), mth.getExitBlocks().get(0)); + return calcPartialPostDominance(mth, mth.getBasicBlocks(), mth.getPreExitBlocks().get(0)); } public static Map calcPartialPostDominance(MethodNode mth, Collection blockNodes, BlockNode exitBlock) { @@ -805,4 +1117,22 @@ public class BlockUtils { } return bitSetToOneBlock(mth, bs); } + + public static BlockNode getTopSplitterForHandler(BlockNode handlerBlock) { + BlockNode block = getBlockWithFlag(handlerBlock.getPredecessors(), AFlag.EXC_TOP_SPLITTER); + if (block == null) { + throw new JadxRuntimeException("Can't find top splitter block for handler:" + handlerBlock); + } + return block; + } + + @Nullable + public static BlockNode getBlockWithFlag(List blocks, AFlag flag) { + for (BlockNode block : blocks) { + if (block.contains(flag)) { + return block; + } + } + return null; + } } diff --git a/jadx-core/src/main/java/jadx/core/utils/DebugChecks.java b/jadx-core/src/main/java/jadx/core/utils/DebugChecks.java index 91cb2a825..8da3d0e0e 100644 --- a/jadx-core/src/main/java/jadx/core/utils/DebugChecks.java +++ b/jadx-core/src/main/java/jadx/core/utils/DebugChecks.java @@ -51,6 +51,7 @@ public class DebugChecks { checkInsn(mth, insn); } } + checkSSAVars(mth); // checkPHI(mth); } @@ -95,6 +96,58 @@ public class DebugChecks { } } + private static void checkSSAVars(MethodNode mth) { + for (SSAVar ssaVar : mth.getSVars()) { + RegisterArg assignArg = ssaVar.getAssign(); + if (assignArg.contains(AFlag.REMOVE)) { + // ignore removed vars + continue; + } + InsnNode assignInsn = assignArg.getParentInsn(); + if (assignInsn != null) { + if (insnMissing(mth, assignInsn)) { + throw new JadxRuntimeException("Insn not found for assign arg in SSAVar: " + ssaVar + ", insn: " + assignInsn); + } + RegisterArg resArg = assignInsn.getResult(); + if (resArg == null) { + throw new JadxRuntimeException("SSA assign insn result missing. SSAVar: " + ssaVar + ", insn: " + assignInsn); + } + SSAVar assignVar = resArg.getSVar(); + if (!assignVar.equals(ssaVar)) { + throw new JadxRuntimeException("Unexpected SSAVar in assign. " + + "Expected: " + ssaVar + ", got: " + assignVar + ", insn: " + assignInsn); + } + } + for (RegisterArg arg : ssaVar.getUseList()) { + InsnNode useInsn = arg.getParentInsn(); + if (useInsn == null) { + throw new JadxRuntimeException("Parent insn can't be null for arg in use list of SSAVar: " + ssaVar); + } + if (insnMissing(mth, useInsn)) { + throw new JadxRuntimeException("Insn not found for use arg for SSAVar: " + ssaVar + ", insn: " + useInsn); + } + int argIndex = useInsn.getArgIndex(arg); + if (argIndex == -1) { + throw new JadxRuntimeException("Use arg not found in insn for SSAVar: " + ssaVar + ", insn: " + useInsn); + } + InsnArg foundArg = useInsn.getArg(argIndex); + if (!foundArg.equals(arg)) { + throw new JadxRuntimeException( + "Incorrect use arg in insn for SSAVar: " + ssaVar + ", insn: " + useInsn + ", arg: " + foundArg); + } + } + } + } + + private static boolean insnMissing(MethodNode mth, InsnNode insn) { + if (insn.contains(AFlag.HIDDEN)) { + // skip search + return false; + } + BlockNode block = BlockUtils.getBlockByInsn(mth, insn); + return block == null; + } + private static void checkRegisterArg(MethodNode mth, RegisterArg reg) { InsnNode parentInsn = reg.getParentInsn(); if (parentInsn == null) { diff --git a/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java b/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java index 1395865ee..b22313a26 100644 --- a/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/DebugUtils.java @@ -30,6 +30,7 @@ import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; +import jadx.core.dex.regions.Region; import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.DotGraphVisitor; import jadx.core.dex.visitors.IDexTreeVisitor; @@ -111,7 +112,11 @@ public class DebugUtils { } public static void printRegions(MethodNode mth, boolean printInsns) { - printRegion(mth, mth.getRegion(), printInsns); + Region mthRegion = mth.getRegion(); + if (mthRegion == null) { + return; + } + printRegion(mth, mthRegion, printInsns); } public static void printRegion(MethodNode mth, IRegion region, boolean printInsns) { diff --git a/jadx-core/src/main/java/jadx/core/utils/InsnRemover.java b/jadx-core/src/main/java/jadx/core/utils/InsnRemover.java index d14d5cfa0..39523133b 100644 --- a/jadx-core/src/main/java/jadx/core/utils/InsnRemover.java +++ b/jadx-core/src/main/java/jadx/core/utils/InsnRemover.java @@ -79,9 +79,12 @@ public class InsnRemover { } public static void unbindInsns(@Nullable MethodNode mth, List insns) { - for (InsnNode insn : insns) { - unbindInsn(mth, insn); - } + // remove all usage first so on result unbind we can remove unused ssa vars + insns.forEach(insn -> unbindAllArgs(mth, insn)); + insns.forEach(insn -> { + unbindResult(mth, insn); + insn.add(AFlag.DONT_GENERATE); + }); } public static void unbindAllArgs(@Nullable MethodNode mth, InsnNode insn) { @@ -101,11 +104,16 @@ public class InsnRemover { public static void unbindResult(@Nullable MethodNode mth, InsnNode insn) { RegisterArg r = insn.getResult(); - if (r != null && mth != null) { - SSAVar ssaVar = r.getSVar(); - if (ssaVar != null && ssaVar.getAssign() == insn.getResult()) { - removeSsaVar(mth, ssaVar); - } + if (r == null) { + return; + } + r.add(AFlag.REMOVE); // don't unset result arg, can be used to restore variable + if (mth == null) { + return; + } + SSAVar ssaVar = r.getSVar(); + if (ssaVar != null && ssaVar.getAssign() == insn.getResult()) { + removeSsaVar(mth, ssaVar); } } diff --git a/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java b/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java index 78c6c012c..00ce50634 100644 --- a/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java @@ -114,7 +114,7 @@ public class InsnUtils { @Nullable public static InsnNode searchSingleReturnInsn(MethodNode mth, Predicate test) { - if (!mth.isNoCode() && mth.getExitBlocks().size() == 1) { + if (!mth.isNoCode() && mth.getPreExitBlocks().size() == 1) { return searchInsn(mth, InsnType.RETURN, test); } return null; diff --git a/jadx-core/src/main/java/jadx/core/utils/ListUtils.java b/jadx-core/src/main/java/jadx/core/utils/ListUtils.java new file mode 100644 index 000000000..d987490ce --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/utils/ListUtils.java @@ -0,0 +1,53 @@ +package jadx.core.utils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +import org.jetbrains.annotations.Nullable; + +import jadx.core.dex.nodes.BlockNode; + +public class ListUtils { + + public static boolean isSingleElement(@Nullable List list, T obj) { + if (list == null || list.size() != 1) { + return false; + } + return Objects.equals(list.get(0), obj); + } + + public static boolean unorderedEquals(List first, List second) { + if (first.size() != second.size()) { + return false; + } + return first.containsAll(second); + } + + public static List map(Collection list, Function mapFunc) { + if (list == null || list.isEmpty()) { + return Collections.emptyList(); + } + List result = new ArrayList<>(list.size()); + for (T t : list) { + result.add(mapFunc.apply(t)); + } + return result; + } + + public static T first(List list) { + return list.get(0); + } + + public static T last(List list) { + return list.get(list.size() - 1); + } + + public static List distinctList(List list) { + return new ArrayList<>(new LinkedHashSet<>(list)); + } +} 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 285aae96f..6f7e63722 100644 --- a/jadx-core/src/main/java/jadx/core/utils/RegionUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/RegionUtils.java @@ -23,7 +23,7 @@ import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.regions.Region; import jadx.core.dex.trycatch.CatchAttr; import jadx.core.dex.trycatch.ExceptionHandler; -import jadx.core.dex.trycatch.TryCatchBlock; +import jadx.core.dex.trycatch.TryCatchBlockAttr; import jadx.core.utils.exceptions.JadxRuntimeException; public class RegionUtils { @@ -33,29 +33,23 @@ public class RegionUtils { public static boolean hasExitEdge(IContainer container) { if (container instanceof IBlock) { - InsnNode lastInsn = BlockUtils.getLastInsn((IBlock) container); - if (lastInsn == null) { - return false; - } - InsnType type = lastInsn.getType(); - return type == InsnType.RETURN - || type == InsnType.CONTINUE - || type == InsnType.BREAK - || type == InsnType.THROW; - } else if (container instanceof IBranchRegion) { + return BlockUtils.containsExitInsn((IBlock) container); + } + if (container instanceof IBranchRegion) { + // all branches must have exit edge for (IContainer br : ((IBranchRegion) container).getBranches()) { if (br == null || !hasExitEdge(br)) { return false; } } return true; - } else if (container instanceof IRegion) { + } + if (container instanceof IRegion) { IRegion region = (IRegion) container; List blocks = region.getSubBlocks(); return !blocks.isEmpty() && hasExitEdge(blocks.get(blocks.size() - 1)); - } else { - throw new JadxRuntimeException(unknownContainerType(container)); } + throw new JadxRuntimeException(unknownContainerType(container)); } public static InsnNode getFirstInsn(IContainer container) { @@ -156,9 +150,9 @@ public class RegionUtils { } if (insnType == InsnType.THROW) { // check if after throw execution can continue in current container - CatchAttr catchAttr = lastInsn.get(AType.CATCH_BLOCK); + CatchAttr catchAttr = lastInsn.get(AType.EXC_CATCH); if (catchAttr != null) { - for (ExceptionHandler handler : catchAttr.getTryBlock().getHandlers()) { + for (ExceptionHandler handler : catchAttr.getHandlers()) { if (RegionUtils.isRegionContainsBlock(rootContainer, handler.getHandlerBlock())) { return false; } @@ -270,9 +264,8 @@ public class RegionUtils { } public static List getExcHandlersForRegion(IContainer region) { - CatchAttr cb = region.get(AType.CATCH_BLOCK); - if (cb != null) { - TryCatchBlock tb = cb.getTryBlock(); + TryCatchBlockAttr tb = region.get(AType.TRY_BLOCK); + if (tb != null) { List list = new ArrayList<>(tb.getHandlersCount()); for (ExceptionHandler eh : tb.getHandlers()) { list.add(eh.getHandlerRegion()); @@ -292,9 +285,8 @@ public class RegionUtils { // process sub blocks for (IContainer b : r.getSubBlocks()) { // process try block - CatchAttr cb = b.get(AType.CATCH_BLOCK); - if (cb != null && b instanceof IRegion) { - TryCatchBlock tb = cb.getTryBlock(); + TryCatchBlockAttr tb = b.get(AType.TRY_BLOCK); + if (tb != null && b instanceof IRegion) { for (ExceptionHandler eh : tb.getHandlers()) { if (isRegionContainsRegion(eh.getHandlerRegion(), region)) { return true; @@ -401,19 +393,20 @@ public class RegionUtils { } if (cont instanceof BlockNode) { return BlockUtils.isPathExists(block, (BlockNode) cont); - } else if (cont instanceof IBlock) { + } + if (cont instanceof IBlock) { return false; - } else if (cont instanceof IRegion) { + } + if (cont instanceof IRegion) { IRegion region = (IRegion) cont; for (IContainer c : region.getSubBlocks()) { - if (!hasPathThroughBlock(block, c)) { - return false; + if (hasPathThroughBlock(block, c)) { + return true; } } - return true; - } else { - throw new JadxRuntimeException(unknownContainerType(cont)); + return false; } + throw new JadxRuntimeException(unknownContainerType(cont)); } protected static String unknownContainerType(IContainer container) { diff --git a/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java index bc244e7cb..62efd703c 100644 --- a/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java +++ b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java @@ -1,6 +1,10 @@ package jadx.tests.api.utils.assertj; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; import org.assertj.core.api.AbstractStringAssert; @@ -65,7 +69,9 @@ public class JadxCodeAssertions extends AbstractStringAssert public JadxCodeAssertions removeBlockComments() { String code = actual.replaceAll("/\\*.*\\*/", ""); - return new JadxCodeAssertions(code); + JadxCodeAssertions newCode = new JadxCodeAssertions(code); + newCode.print(); + return newCode; } public JadxCodeAssertions print() { @@ -81,7 +87,28 @@ public class JadxCodeAssertions extends AbstractStringAssert matches += TestUtils.count(actual, substring); } if (matches != 1) { - failWithMessage("Expected a only one match from <%s> but was <%d>", Arrays.toString(substringArr), matches); + failWithMessage("Expected only one match from <%s> but was <%d>", Arrays.toString(substringArr), matches); + } + return this; + } + + @SuppressWarnings("UnusedReturnValue") + @SafeVarargs + public final JadxCodeAssertions oneOf(Function... checks) { + int passed = 0; + List failed = new ArrayList<>(); + for (Function check : checks) { + try { + check.apply(this); + passed++; + } catch (Throwable e) { + failed.add(e); + } + } + if (passed != 1) { + failWithMessage("Expected only one match but passed: <%d>, failed: <%d>, details:\n<%s>", + passed, failed.size(), + failed.stream().map(Throwable::getMessage).collect(Collectors.joining("\nFailed check:\n "))); } return this; } diff --git a/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java b/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java index 56cbc1cb1..f7748f103 100644 --- a/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java +++ b/jadx-core/src/test/java/jadx/tests/external/BaseExternalTest.java @@ -60,7 +60,11 @@ public abstract class BaseExternalTest extends IntegrationTest { private void processAll(JadxDecompiler jadx) { for (JavaClass javaClass : jadx.getClasses()) { - javaClass.decompile(); + try { + javaClass.decompile(); + } catch (Exception e) { + LOG.error("Failed to decompile class: {}", javaClass, e); + } } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/arrays/TestArrayInitField.java b/jadx-core/src/test/java/jadx/tests/integration/arrays/TestArrayInitField.java index e5ee0e96e..1aecf3fb6 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/arrays/TestArrayInitField.java +++ b/jadx-core/src/test/java/jadx/tests/integration/arrays/TestArrayInitField.java @@ -2,26 +2,22 @@ package jadx.tests.integration.arrays; import org.junit.jupiter.api.Test; -import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.IntegrationTest; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.MatcherAssert.assertThat; +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; public class TestArrayInitField extends IntegrationTest { public static class TestCls { - static byte[] a = new byte[] { 10, 20, 30 }; byte[] b = new byte[] { 40, 50, 60 }; } @Test public void test() { - ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); - - assertThat(code, containsString("= {10, 20, 30};")); - assertThat(code, containsString("= {40, 50, 60};")); + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("static byte[] a = {10, 20, 30};") + .containsOne("byte[] b = {40, 50, 60};"); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/conditions/TestTernary4.java b/jadx-core/src/test/java/jadx/tests/integration/conditions/TestTernary4.java index 55f949b7a..2c516916f 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/conditions/TestTernary4.java +++ b/jadx-core/src/test/java/jadx/tests/integration/conditions/TestTernary4.java @@ -2,13 +2,11 @@ package jadx.tests.integration.conditions; import org.junit.jupiter.api.Test; -import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.SmaliTest; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.MatcherAssert.assertThat; +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; +@SuppressWarnings("CommentedOutCode") public class TestTernary4 extends SmaliTest { // @formatter:off @@ -36,10 +34,10 @@ public class TestTernary4 extends SmaliTest { @Test public void test() { - ClassNode cls = getClassNodeFromSmali(); - String code = cls.getCode().toString(); - - assertThat(code, not(containsString("r5"))); - assertThat(code, not(containsString("try"))); + assertThat(getClassNodeFromSmali()) + .code() + .removeBlockComments() + .doesNotContain("5") + .doesNotContain("try"); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/conditions/TestTernaryOneBranchInConstructor.java b/jadx-core/src/test/java/jadx/tests/integration/conditions/TestTernaryOneBranchInConstructor.java index 200cc3c1f..4a664a014 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/conditions/TestTernaryOneBranchInConstructor.java +++ b/jadx-core/src/test/java/jadx/tests/integration/conditions/TestTernaryOneBranchInConstructor.java @@ -28,6 +28,7 @@ public class TestTernaryOneBranchInConstructor extends IntegrationTest { assertThat(code, containsOne("this(str == null ? 0 : i);")); assertThat(code, not(containsString("//"))); + assertThat(code, not(containsString("call moved to the top of the method"))); } public static class TestCls2 { diff --git a/jadx-core/src/test/java/jadx/tests/integration/loops/TestBreakInComplexIf.java b/jadx-core/src/test/java/jadx/tests/integration/loops/TestBreakInComplexIf.java index 00f4c5847..23c8cab19 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/loops/TestBreakInComplexIf.java +++ b/jadx-core/src/test/java/jadx/tests/integration/loops/TestBreakInComplexIf.java @@ -39,10 +39,16 @@ public class TestBreakInComplexIf extends IntegrationTest { } public void check() { - Map map = new HashMap<>(); - map.put("3", new Point(100, 100)); - map.put("4", new Point(60, 100)); - assertThat(test(map, 2), is(3)); + Map first = new HashMap<>(); + first.put("3", new Point(100, 100)); + first.put("4", new Point(60, 100)); + assertThat(test(first, 2), is(3)); + + Map second = new HashMap<>(); + second.put("3", new Point(100, 100)); + second.put("4", new Point(60, 0)); // check break + second.put("5", new Point(60, 100)); + assertThat(test(second, 2), is(2)); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/loops/TestContinueInLoop2.java b/jadx-core/src/test/java/jadx/tests/integration/loops/TestContinueInLoop2.java deleted file mode 100644 index f0ff8a0ba..000000000 --- a/jadx-core/src/test/java/jadx/tests/integration/loops/TestContinueInLoop2.java +++ /dev/null @@ -1,73 +0,0 @@ -package jadx.tests.integration.loops; - -import org.junit.jupiter.api.Test; - -import jadx.core.dex.attributes.AType; -import jadx.core.dex.instructions.InsnType; -import jadx.core.dex.nodes.BlockNode; -import jadx.core.dex.nodes.ClassNode; -import jadx.core.dex.nodes.InsnNode; -import jadx.core.dex.nodes.MethodNode; -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.utils.BlockUtils; -import jadx.core.utils.InsnRemover; -import jadx.tests.api.IntegrationTest; - -import static jadx.tests.api.utils.JadxMatchers.containsOne; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.MatcherAssert.assertThat; - -public class TestContinueInLoop2 extends IntegrationTest { - - public static class TestCls { - private static void test(MethodNode mth, BlockNode block) { - ExcHandlerAttr handlerAttr = block.get(AType.EXC_HANDLER); - if (handlerAttr != null) { - ExceptionHandler excHandler = handlerAttr.getHandler(); - excHandler.addBlock(block); - for (BlockNode node : BlockUtils.collectBlocksDominatedBy(block, block)) { - excHandler.addBlock(node); - } - for (BlockNode excBlock : excHandler.getBlocks()) { - InsnRemover remover = new InsnRemover(mth, excBlock); - for (InsnNode insn : excBlock.getInstructions()) { - if (insn.getType() == InsnType.MONITOR_ENTER) { - break; - } - if (insn.getType() == InsnType.MONITOR_EXIT) { - remover.addAndUnbind(insn); - } - } - remover.perform(); - - 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) { - handlerBlock.merge(mth, catchBlock); - catchBlock.removeInsn(mth, insn); - } - } - } - } - } - } - } - } - - @Test - public void test() { - ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); - - assertThat(code, containsOne("break;")); - assertThat(code, not(containsString("continue;"))); - } -} diff --git a/jadx-core/src/test/java/jadx/tests/integration/loops/TestLoopDetection.java b/jadx-core/src/test/java/jadx/tests/integration/loops/TestLoopDetection.java index 38b2b72d6..1d7fc5461 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/loops/TestLoopDetection.java +++ b/jadx-core/src/test/java/jadx/tests/integration/loops/TestLoopDetection.java @@ -6,6 +6,7 @@ import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.IntegrationTest; import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; @@ -24,6 +25,12 @@ public class TestLoopDetection extends IntegrationTest { i++; } } + + public void check() { + int[] a = { 1, 1, 1, 1, 1 }; + test(a, 3); + assertThat(a, is(new int[] { 2, 2, 2, 0, 0 })); + } } @Test diff --git a/jadx-core/src/test/java/jadx/tests/integration/loops/TestLoopRestore.java b/jadx-core/src/test/java/jadx/tests/integration/loops/TestLoopRestore.java index b61e01de2..d6253294b 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/loops/TestLoopRestore.java +++ b/jadx-core/src/test/java/jadx/tests/integration/loops/TestLoopRestore.java @@ -2,7 +2,6 @@ package jadx.tests.integration.loops; import org.junit.jupiter.api.Test; -import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.SmaliTest; import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; @@ -11,8 +10,8 @@ public class TestLoopRestore extends SmaliTest { @Test public void test() { - ClassNode cls = getClassNodeFromSmali(); - assertThat(cls).code() + assertThat(getClassNodeFromSmali()) + .code() .containsOne("try {") .containsOne("for (byte b : digest) {"); } diff --git a/jadx-core/src/test/java/jadx/tests/integration/loops/TestNestedLoops3.java b/jadx-core/src/test/java/jadx/tests/integration/loops/TestNestedLoops3.java index d267b6db3..5fa931711 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/loops/TestNestedLoops3.java +++ b/jadx-core/src/test/java/jadx/tests/integration/loops/TestNestedLoops3.java @@ -32,7 +32,7 @@ public class TestNestedLoops3 extends IntegrationTest { exc(); break; } catch (Exception e) { - // + // ignore } } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/loops/TestNestedLoops4.java b/jadx-core/src/test/java/jadx/tests/integration/loops/TestNestedLoops4.java index 7b8846ccc..d6efaff3b 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/loops/TestNestedLoops4.java +++ b/jadx-core/src/test/java/jadx/tests/integration/loops/TestNestedLoops4.java @@ -5,6 +5,8 @@ import org.junit.jupiter.api.Test; import jadx.NotYetImplemented; import jadx.tests.api.IntegrationTest; +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + public class TestNestedLoops4 extends IntegrationTest { public static class TestCls { @@ -28,11 +30,18 @@ public class TestNestedLoops4 extends IntegrationTest { } return tmp; } + + public void check() { + assertThat(testFor()).isEqualTo(12); + } } @Test @NotYetImplemented public void test() { - getClassNode(TestCls.class); + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("break;") + .containsOne("return 0;"); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/loops/TestSynchronizedInEndlessLoop.java b/jadx-core/src/test/java/jadx/tests/integration/loops/TestSynchronizedInEndlessLoop.java index 13f9c760b..5135b8716 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/loops/TestSynchronizedInEndlessLoop.java +++ b/jadx-core/src/test/java/jadx/tests/integration/loops/TestSynchronizedInEndlessLoop.java @@ -10,6 +10,7 @@ import static org.hamcrest.MatcherAssert.assertThat; public class TestSynchronizedInEndlessLoop extends IntegrationTest { + @SuppressWarnings("BusyWait") public static class TestCls { int f = 5; @@ -18,7 +19,8 @@ public class TestSynchronizedInEndlessLoop extends IntegrationTest { synchronized (this) { if (f > 7) { return 7; - } else if (f < 3) { + } + if (f < 3) { return 3; } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestDuplicateCast.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestDuplicateCast.java index 805261b51..cf3459b4c 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestDuplicateCast.java +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestDuplicateCast.java @@ -9,6 +9,7 @@ import jadx.core.dex.instructions.args.InsnWrapArg; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; +import jadx.core.utils.BlockUtils; import jadx.tests.api.IntegrationTest; import static org.hamcrest.CoreMatchers.containsString; @@ -38,7 +39,7 @@ public class TestDuplicateCast extends IntegrationTest { String code = cls.getCode().toString(); assertThat(code, containsString("return (int[]) o;")); - List insns = mth.getBasicBlocks().get(1).getInstructions(); + List insns = BlockUtils.collectAllInsns(mth.getBasicBlocks()); assertThat(insns, hasSize(1)); InsnNode insnNode = insns.get(0); assertThat(insnNode.getType(), is(InsnType.RETURN)); diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestFieldInit2.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestFieldInit2.java index 85fc31bf2..18823f282 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestFieldInit2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestFieldInit2.java @@ -34,7 +34,6 @@ public class TestFieldInit2 extends IntegrationTest { @Test public void test() { - printDisassemble(); ClassNode cls = getClassNode(TestCls.class); String code = cls.getCode().toString(); diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestOverridePrivateMethod.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestOverridePrivateMethod.java index 59ccd031f..3eea8bf6d 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestOverridePrivateMethod.java +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestOverridePrivateMethod.java @@ -10,14 +10,22 @@ public class TestOverridePrivateMethod extends IntegrationTest { public static class TestCls { public static class BaseClass { - private void a() { + private int a() { + return 1; } } public static class MyClass extends BaseClass { - public void a() { + public int a() { + return 2; } } + + public void check() { + assertThat(new MyClass().a()).isEqualTo(2); + assertThat(new BaseClass().a()).isEqualTo(1); + // TODO: assertThat(((BaseClass) new MyClass()).a()).isEqualTo(1); + } } @Test diff --git a/jadx-core/src/test/java/jadx/tests/integration/synchronize/TestSynchronized3.java b/jadx-core/src/test/java/jadx/tests/integration/synchronize/TestSynchronized3.java index 313691f45..0ad776d76 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/synchronize/TestSynchronized3.java +++ b/jadx-core/src/test/java/jadx/tests/integration/synchronize/TestSynchronized3.java @@ -20,7 +20,7 @@ public class TestSynchronized3 extends IntegrationTest { while (true) { synchronized (this) { if (x == 0) { - throw new IllegalStateException("bad luck"); + throw new IllegalStateException(); } x++; if (x == 10) { diff --git a/jadx-core/src/test/java/jadx/tests/integration/synchronize/TestSynchronized6.java b/jadx-core/src/test/java/jadx/tests/integration/synchronize/TestSynchronized6.java new file mode 100644 index 000000000..b0480877e --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/synchronize/TestSynchronized6.java @@ -0,0 +1,44 @@ +package jadx.tests.integration.synchronize; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestSynchronized6 extends SmaliTest { + + public static class TestCls { + private final Object lock = new Object(); + + private boolean test(Object obj) { + synchronized (this.lock) { + return isA(obj) || isB(obj); + } + } + + private boolean isA(Object obj) { + return false; + } + + private boolean isB(Object obj) { + return false; + } + } + + @Test + public void test() { + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("synchronized (this.lock) {") + .containsOne("isA(obj) || isB(obj);"); // TODO: "return isA(obj) || isB(obj);" + } + + @Test + public void testSmali() { + assertThat(getClassNodeFromSmali()) + .code() + .containsOne("synchronized (this.lock) {"); + // TODO: .containsOne("return isA(obj) || isB(obj);"); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestEmptyCatch.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestEmptyCatch.java new file mode 100644 index 000000000..7ed48359f --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestEmptyCatch.java @@ -0,0 +1,22 @@ +package jadx.tests.integration.trycatch; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +/** + * Empty catch blocks in enum switch remap building + */ +public class TestEmptyCatch extends SmaliTest { + + @Test + public void test() { + disableCompilation(); + assertThat(getClassNodeFromSmali()) + .code() + .countString(5, "try {") + .countString(5, "} catch (NoSuchFieldError unused"); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestEmptyFinally.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestEmptyFinally.java index 972891de1..6adbb9d50 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestEmptyFinally.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestEmptyFinally.java @@ -5,17 +5,14 @@ import java.io.IOException; import org.junit.jupiter.api.Test; -import jadx.NotYetImplemented; -import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.IntegrationTest; -import static jadx.tests.api.utils.JadxMatchers.containsOne; -import static org.hamcrest.MatcherAssert.assertThat; +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; public class TestEmptyFinally extends IntegrationTest { + @SuppressWarnings("EmptyFinallyBlock") public static class TestCls { - @SuppressWarnings("EmptyFinallyBlock") public void test(FileInputStream f1) { try { f1.close(); @@ -27,13 +24,11 @@ public class TestEmptyFinally extends IntegrationTest { } } - @NotYetImplemented @Test public void test() { - ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); - - assertThat(code, containsOne("} catch (IOException e) {")); - assertThat(code, containsOne("} finally {")); // ??? + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("} catch (IOException e) {") + .doesNotContain("} finally {"); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestFinally3.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestFinally3.java index 22158e15a..7d3648d1d 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestFinally3.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestFinally3.java @@ -54,15 +54,6 @@ public class TestFinally3 extends SmaliTest { @Test public void test() { - assertThat(getClassNode(TestCls.class)) - .code() - .containsOne("} finally {") - .doesNotContain("close(null);"); - } - - @NotYetImplemented("Finally instruction duplicated") - @Test - public void test2() { assertThat(getClassNode(TestCls.class)) .code() .containsOne("} finally {") 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 index e7e03d0aa..3fd7ed837 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestFinallyExtract.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestFinallyExtract.java @@ -45,13 +45,13 @@ public class TestFinallyExtract extends IntegrationTest { ClassNode cls = getClassNode(TestCls.class); String code = cls.getCode().toString(); + assertThat(code, containsOne("} finally {")); assertThat(code, not(containsString("if (0 == 0) {"))); assertThat(code, containsOne("boolean success = false;")); assertThat(code, containsOne("try {")); assertThat(code, containsOne("success = true;")); assertThat(code, containsOne("return value;")); - assertThat(code, containsOne("} finally {")); assertThat(code, containsOne("if (!success) {")); } diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestLoopInTryCatch.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestLoopInTryCatch.java index cd17c91c6..8c5a74adf 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestLoopInTryCatch.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestLoopInTryCatch.java @@ -11,7 +11,7 @@ public class TestLoopInTryCatch extends SmaliTest { public void test() { assertThat(getClassNodeFromSmali()) .code() - .containsLines(2, + .oneOf(c -> c.containsLines(2, "int i;", "while (true) {", " try {", @@ -24,6 +24,20 @@ public class TestLoopInTryCatch extends SmaliTest { " }", "}", "if (i == 1) {", - "}"); + "}"), + c -> c.containsLines(2, + "int i;", + "while (true) {", + " try {", + " i = getI();", + " if (i == 1 || i == 2) {", + " break;", + " }", + " } catch (RuntimeException unused) {", + " return;", + " }", + "}", + "if (i == 1) {", + "}")); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestNestedTryCatch.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestNestedTryCatch.java index 9d461fa04..8e28f0576 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestNestedTryCatch.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestNestedTryCatch.java @@ -12,17 +12,17 @@ import static org.hamcrest.MatcherAssert.assertThat; public class TestNestedTryCatch extends IntegrationTest { public static class TestCls { - public void f() { + public void test() { try { Thread.sleep(1); try { Thread.sleep(2); } catch (InterruptedException ignored) { + System.out.println(2); } } catch (Exception ignored) { + System.out.println(1); } - - return; } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch7.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch7.java index 92a820922..5a710cbf6 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch7.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch7.java @@ -33,7 +33,6 @@ public class TestTryCatch7 extends IntegrationTest { @Test public void testNoDebug() { - // useDexInput(); noDebugInfo(); ClassNode cls = getClassNode(TestCls.class); String code = cls.getCode().toString(); 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 index 6f88fbb3c..bd6d3943b 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally.java @@ -2,11 +2,9 @@ package jadx.tests.integration.trycatch; import org.junit.jupiter.api.Test; -import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.IntegrationTest; -import static jadx.tests.api.utils.JadxMatchers.containsOne; -import static org.hamcrest.MatcherAssert.assertThat; +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; public class TestTryCatchFinally extends IntegrationTest { @@ -42,14 +40,27 @@ public class TestTryCatchFinally extends IntegrationTest { @Test public void test() { - ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("this.f = false;") + .containsOne("exc(obj);") + .containsOne("} catch (Exception e) {") + .containsOne("e.printStackTrace();") + .containsOne("} finally {") + .containsOne("this.f = true;") + .containsOne("return this.f;") + .doesNotContain("boolean z"); + } - assertThat(code, containsOne("exc(obj);")); - assertThat(code, containsOne("} catch (Exception e) {")); - assertThat(code, containsOne("e.printStackTrace();")); - assertThat(code, containsOne("} finally {")); - // assertThat(code, containsOne("this.f = true;")); // TODO: fix registers in duplicated code - assertThat(code, containsOne("return this.f;")); + @Test + public void testWithoutFinally() { + args.setExtractFinally(false); + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("exc(obj);") + .containsOne(indent(3) + "} catch (Exception e) {") + .containsOne(indent(2) + "} catch (Throwable th) {") + .containsOne("this.f = false;") + .countString(3, "this.f = true;"); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally10.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally10.java index ed9c2f089..f1e975f6a 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally10.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally10.java @@ -2,13 +2,9 @@ package jadx.tests.integration.trycatch; import org.junit.jupiter.api.Test; -import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.SmaliTest; -import static jadx.tests.api.utils.JadxMatchers.containsOne; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.not; +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; public class TestTryCatchFinally10 extends SmaliTest { @@ -37,12 +33,13 @@ public class TestTryCatchFinally10 extends SmaliTest { @Test public void test() { disableCompilation(); - ClassNode cls = getClassNodeFromSmali(); - String code = cls.getCode().toString(); - - assertThat(code, not(containsString("boolean z = null;"))); - assertThat(code, not(containsString("} catch (Throwable"))); - assertThat(code, containsOne("} finally {")); - assertThat(code, containsOne(".close();")); + assertThat(getClassNodeFromSmali()) + .code() + .doesNotContain("boolean z = null;") + .doesNotContain("} catch (Throwable") + .containsOne("} finally {") + .containsOne(".close();") + .containsOne("} catch (IOException e") + .containsOne(".logException("); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally3.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally3.java index 236178ed7..9fa7e5969 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally3.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally3.java @@ -19,7 +19,7 @@ public class TestTryCatchFinally3 extends IntegrationTest { public static class TestCls { private static final Logger LOG = LoggerFactory.getLogger(TestCls.class); - public static void process(ClassNode cls, List passes) { + public static void test(ClassNode cls, List passes) { try { cls.load(); for (IDexTreeVisitor visitor : passes) { diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally5.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally5.java index b01f67b5a..b3860c997 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally5.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatchFinally5.java @@ -5,12 +5,9 @@ import java.util.List; import org.junit.jupiter.api.Test; -import jadx.NotYetImplemented; -import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.IntegrationTest; -import static jadx.tests.api.utils.JadxMatchers.containsOne; -import static org.hamcrest.MatcherAssert.assertThat; +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; public class TestTryCatchFinally5 extends IntegrationTest { @@ -62,18 +59,9 @@ public class TestTryCatchFinally5 extends IntegrationTest { @Test public void test() { - ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); - - assertThat(code, containsOne("} finally {")); - } - - @Test - @NotYetImplemented - public void test2() { - ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); - - assertThat(code, containsOne("d.close();")); + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("} finally {") + .containsOne("d.close();"); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryWithEmptyCatchTriple.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryWithEmptyCatchTriple.java index 0abe33eb5..759241b0a 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryWithEmptyCatchTriple.java +++ b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryWithEmptyCatchTriple.java @@ -2,20 +2,18 @@ package jadx.tests.integration.trycatch; import org.junit.jupiter.api.Test; -import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.SmaliTest; -import static jadx.tests.api.utils.JadxMatchers.containsOne; -import static org.hamcrest.MatcherAssert.assertThat; +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; public class TestTryWithEmptyCatchTriple extends SmaliTest { @Test public void test() { - ClassNode cls = getClassNodeFromSmaliWithPkg("trycatch", "TestTryWithEmptyCatchTriple"); - String code = cls.getCode().toString(); - - assertThat(code, containsOne("} catch (Error unused) {")); - assertThat(code, containsOne("} catch (Error unused2) {")); - assertThat(code, containsOne("} catch (Error unused3) {")); + assertThat(getClassNodeFromSmali()) + .code() + // all catches are empty + .containsLines(2, "} catch (Error unused) {", "}") + .containsLines(2, "} catch (Error unused2) {", "}") + .containsLines(2, "} catch (Error unused3) {", "}"); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver17.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver17.java new file mode 100644 index 000000000..0b7d55569 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver17.java @@ -0,0 +1,42 @@ +package jadx.tests.integration.types; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +/** + * Issue 1197 + */ +public class TestTypeResolver17 extends SmaliTest { + // @formatter:off + /* + private static String test(Context context, Uri uri, String str, String str2) { + Cursor cursor = null; + try { + cursor = context.getContentResolver().query(uri, new String[]{str}, null, null, null); + if (cursor.moveToFirst() && !cursor.isNull(0)) { + return cursor.getString(0); + } + closeQuietly(cursor); + return str2; + } catch (Exception e) { + Log.w("DocumentFile", "Failed query: " + e); + return str2; + } finally { + closeQuietly(cursor); + } + } + */ + // @formatter:on + + @Test + public void test() { + disableCompilation(); + assertThat(getClassNodeFromSmali()) + .code() + .containsOne("Cursor cursor = null;") + .doesNotContain("(AutoCloseable autoCloseable = "); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver3.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver3.java index 74ac3bf84..bb6a226b6 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver3.java +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver3.java @@ -2,14 +2,13 @@ package jadx.tests.integration.types; import org.junit.jupiter.api.Test; -import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.IntegrationTest; -import static jadx.tests.api.utils.JadxMatchers.containsOne; -import static org.hamcrest.MatcherAssert.assertThat; +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; public class TestTypeResolver3 extends IntegrationTest { + @SuppressWarnings("UseCompareMethod") public static class TestCls { public int test(String s1, String s2) { @@ -23,10 +22,12 @@ public class TestTypeResolver3 extends IntegrationTest { @Test public void test() { - ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); - - assertThat(code, containsOne("return s1.length() == s2.length() ? 0 : s1.length() < s2.length() ? -1 : 1;")); + useJavaInput(); + assertThat(getClassNode(TestCls.class)) + .code() + .containsOneOf( + "return s1.length() == s2.length() ? 0 : s1.length() < s2.length() ? -1 : 1;", + "return s1.length() < s2.length() ? -1 : 1;"); } @Test diff --git a/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariablesGeneric.java b/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariablesGeneric.java index b862fac98..494356f20 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariablesGeneric.java +++ b/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariablesGeneric.java @@ -2,12 +2,9 @@ package jadx.tests.integration.variables; import org.junit.jupiter.api.Test; -import jadx.core.dex.nodes.ClassNode; import jadx.tests.api.SmaliTest; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.not; +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; public class TestVariablesGeneric extends SmaliTest { // @formatter:off @@ -27,11 +24,11 @@ public class TestVariablesGeneric extends SmaliTest { @Test public void test() { disableCompilation(); - ClassNode cls = getClassNodeFromSmaliWithPkg("variables", "TestVariablesGeneric"); - String code = cls.getCode().toString(); - - assertThat(code, not(containsString("iVar2"))); - assertThat(code, containsString("public static j a(i iVar, c cVar) {")); - assertThat(code, containsString("if (iVar == null) {")); + assertThat(getClassNodeFromSmali()) + .code() + .doesNotContain("iVar2") + .containsOne("public static j a(i iVar, c cVar) {") + .containsOne("if (iVar == null) {") + .countString(2, "} catch (Throwable th"); } } diff --git a/jadx-core/src/test/smali/synchronize/TestSynchronized6.smali b/jadx-core/src/test/smali/synchronize/TestSynchronized6.smali new file mode 100644 index 000000000..894611ea6 --- /dev/null +++ b/jadx-core/src/test/smali/synchronize/TestSynchronized6.smali @@ -0,0 +1,70 @@ +.class public Lsynchronize/TestSynchronized6; +.super Ljava/lang/Object; + +.field private final lock:Ljava/lang/Object; + +.method public constructor ()V + .registers 2 + + invoke-direct {p0}, Ljava/lang/Object;->()V + new-instance v0, Ljava/lang/Object; + invoke-direct {v0}, Ljava/lang/Object;->()V + iput-object v0, p0, Lsynchronize/TestSynchronized6;->lock:Ljava/lang/Object; + return-void +.end method + +.method private test(Ljava/lang/Object;)Z + .locals 2 + + .line 169 + iget-object v0, p0, Lsynchronize/TestSynchronized6;->lock:Ljava/lang/Object; + monitor-enter v0 + + .line 170 + :try_start_0 + invoke-direct {p0, p1}, Lsynchronize/TestSynchronized6;->isA(Ljava/lang/Object;)Z + move-result v1 + + if-nez v1, :cond_1 + + invoke-direct {p0, p1}, Lsynchronize/TestSynchronized6;->isB(Ljava/lang/Object;)Z + move-result p1 + + if-eqz p1, :cond_0 + goto :goto_0 + + :cond_0 + const/4 p1, 0x0 + goto :goto_1 + + :cond_1 + :goto_0 + const/4 p1, 0x1 + + :goto_1 + monitor-exit v0 + + return p1 + + :catchall_0 + move-exception p1 + + .line 171 + monitor-exit v0 + :try_end_0 + .catchall {:try_start_0 .. :try_end_0} :catchall_0 + + throw p1 +.end method + +.method private isA(Ljava/lang/Object;)Z + .registers 3 + const/4 v0, 0x0 + return v0 +.end method + +.method private isB(Ljava/lang/Object;)Z + .registers 3 + const/4 v0, 0x0 + return v0 +.end method diff --git a/jadx-core/src/test/smali/trycatch/TestEmptyCatch.smali b/jadx-core/src/test/smali/trycatch/TestEmptyCatch.smali new file mode 100644 index 000000000..546e75c42 --- /dev/null +++ b/jadx-core/src/test/smali/trycatch/TestEmptyCatch.smali @@ -0,0 +1,103 @@ +.class synthetic Ltrycatch/TestEmptyCatch; +.super Ljava/lang/Object; + + +# static fields +.field static final synthetic $SwitchMap$Access:[I + + +# direct methods +.method static constructor ()V + .locals 3 + + .line 49 + invoke-static {}, Lother/EnumAccess;->values()[Lother/EnumAccess; + + move-result-object v0 + + array-length v0, v0 + + new-array v0, v0, [I + + sput-object v0, Ltrycatch/TestEmptyCatch;->$SwitchMap$Access:[I + + :try_start_0 + sget-object v1, Lother/EnumAccess;->QUERY:Lother/EnumAccess; + + invoke-virtual {v1}, Ljava/lang/Enum;->ordinal()I + + move-result v1 + + const/4 v2, 0x1 + + aput v2, v0, v1 + :try_end_0 + .catch Ljava/lang/NoSuchFieldError; {:try_start_0 .. :try_end_0} :catch_0 + + :catch_0 + :try_start_1 + sget-object v0, Ltrycatch/TestEmptyCatch;->$SwitchMap$Access:[I + + sget-object v1, Lother/EnumAccess;->MODIFY:Lother/EnumAccess; + + invoke-virtual {v1}, Ljava/lang/Enum;->ordinal()I + + move-result v1 + + const/4 v2, 0x2 + + aput v2, v0, v1 + :try_end_1 + .catch Ljava/lang/NoSuchFieldError; {:try_start_1 .. :try_end_1} :catch_1 + + :catch_1 + :try_start_2 + sget-object v0, Ltrycatch/TestEmptyCatch;->$SwitchMap$Access:[I + + sget-object v1, Lother/EnumAccess;->MODIFY_CONST:Lother/EnumAccess; + + invoke-virtual {v1}, Ljava/lang/Enum;->ordinal()I + + move-result v1 + + const/4 v2, 0x3 + + aput v2, v0, v1 + :try_end_2 + .catch Ljava/lang/NoSuchFieldError; {:try_start_2 .. :try_end_2} :catch_2 + + :catch_2 + :try_start_3 + sget-object v0, Ltrycatch/TestEmptyCatch;->$SwitchMap$Access:[I + + sget-object v1, Lother/EnumAccess;->MODIFY_GETTER_SETTER:Lother/EnumAccess; + + invoke-virtual {v1}, Ljava/lang/Enum;->ordinal()I + + move-result v1 + + const/4 v2, 0x4 + + aput v2, v0, v1 + :try_end_3 + .catch Ljava/lang/NoSuchFieldError; {:try_start_3 .. :try_end_3} :catch_3 + + :catch_3 + :try_start_4 + sget-object v0, Ltrycatch/TestEmptyCatch;->$SwitchMap$Access:[I + + sget-object v1, Lother/EnumAccess;->CONVERT_ACCESSOR_TO_DATA:Lother/EnumAccess; + + invoke-virtual {v1}, Ljava/lang/Enum;->ordinal()I + + move-result v1 + + const/4 v2, 0x5 + + aput v2, v0, v1 + :try_end_4 + .catch Ljava/lang/NoSuchFieldError; {:try_start_4 .. :try_end_4} :catch_4 + + :catch_4 + return-void +.end method diff --git a/jadx-core/src/test/smali/types/TestTypeResolver17.smali b/jadx-core/src/test/smali/types/TestTypeResolver17.smali new file mode 100644 index 000000000..92ecec97c --- /dev/null +++ b/jadx-core/src/test/smali/types/TestTypeResolver17.smali @@ -0,0 +1,112 @@ +.class public Ltypes/TestTypeResolver17; +.super Ljava/lang/Object; + + +.method private static closeQuietly(Ljava/lang/AutoCloseable;)V + .locals 0 + return-void +.end method + +.method private static test(Landroid/content/Context;Landroid/net/Uri;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + .locals 7 + + .line 159 + invoke-virtual {p0}, Landroid/content/Context;->getContentResolver()Landroid/content/ContentResolver; + + move-result-object v0 + + const/4 p0, 0x1 + + const/4 v6, 0x0 + + :try_start_0 + new-array v2, p0, [Ljava/lang/String; + + const/4 p0, 0x0 + + aput-object p2, v2, p0 + + const/4 v3, 0x0 + + const/4 v4, 0x0 + + const/4 v5, 0x0 + + move-object v1, p1 + + .line 163 + invoke-virtual/range {v0 .. v5}, Landroid/content/ContentResolver;->query(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor; + + move-result-object v6 + + .line 164 + invoke-interface {v6}, Landroid/database/Cursor;->moveToFirst()Z + + move-result p1 + + if-eqz p1, :cond_0 + + invoke-interface {v6, p0}, Landroid/database/Cursor;->isNull(I)Z + + move-result p1 + + if-nez p1, :cond_0 + + .line 165 + invoke-interface {v6, p0}, Landroid/database/Cursor;->getString(I)Ljava/lang/String; + + move-result-object p0 + :try_end_0 + .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0 + .catchall {:try_start_0 .. :try_end_0} :catchall_0 + + .line 173 + invoke-static {v6}, Ltypes/TestTypeResolver17;->closeQuietly(Ljava/lang/AutoCloseable;)V + + return-object p0 + + :cond_0 + invoke-static {v6}, Ltypes/TestTypeResolver17;->closeQuietly(Ljava/lang/AutoCloseable;)V + + return-object p3 + + :catchall_0 + move-exception p0 + + goto :goto_0 + + :catch_0 + move-exception p0 + + :try_start_1 + const-string p1, "DocumentFile" + + .line 170 + new-instance p2, Ljava/lang/StringBuilder; + + invoke-direct {p2}, Ljava/lang/StringBuilder;->()V + + const-string v0, "Failed query: " + + invoke-virtual {p2, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; + + invoke-virtual {p2, p0}, Ljava/lang/StringBuilder;->append(Ljava/lang/Object;)Ljava/lang/StringBuilder; + + invoke-virtual {p2}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; + + move-result-object p0 + + invoke-static {p1, p0}, Landroid/util/Log;->w(Ljava/lang/String;Ljava/lang/String;)I + :try_end_1 + .catchall {:try_start_1 .. :try_end_1} :catchall_0 + + .line 173 + invoke-static {v6}, Ltypes/TestTypeResolver17;->closeQuietly(Ljava/lang/AutoCloseable;)V + + return-object p3 + + :goto_0 + invoke-static {v6}, Ltypes/TestTypeResolver17;->closeQuietly(Ljava/lang/AutoCloseable;)V + + throw p0 +.end method diff --git a/jadx-gui/src/main/java/jadx/gui/device/debugger/smali/Smali.java b/jadx-gui/src/main/java/jadx/gui/device/debugger/smali/Smali.java index 80f71a270..23649ffde 100644 --- a/jadx-gui/src/main/java/jadx/gui/device/debugger/smali/Smali.java +++ b/jadx-gui/src/main/java/jadx/gui/device/debugger/smali/Smali.java @@ -299,29 +299,29 @@ public class Smali { private void writeTries(ICodeReader codeReader, LineInfo line) { List tries = codeReader.getTries(); for (ITry aTry : tries) { - int end = aTry.getEndAddress(); + int end = aTry.getEndOffset(); String tryEndTip = String.format(FMT_TRY_END_TAG, end); - String tryStartTip = String.format(FMT_TRY_TAG, aTry.getStartAddress()); + String tryStartTip = String.format(FMT_TRY_TAG, aTry.getStartOffset()); String tryStartTipExtra = " # :" + tryStartTip.substring(0, tryStartTip.length() - 1); - line.addTip(aTry.getStartAddress(), tryStartTip, " # :" + tryEndTip.substring(0, tryEndTip.length() - 1)); + line.addTip(aTry.getStartOffset(), tryStartTip, " # :" + tryEndTip.substring(0, tryEndTip.length() - 1)); line.addTip(end, tryEndTip, tryStartTipExtra); ICatch iCatch = aTry.getCatch(); - int[] addresses = iCatch.getAddresses(); + int[] addresses = iCatch.getHandlers(); int addr; for (int i = 0; i < addresses.length; i++) { addr = addresses[i]; String catchTip = String.format(FMT_CATCH_TAG, addr); line.addTip(addr, catchTip, " # " + iCatch.getTypes()[i]); line.addTip(addr, catchTip, tryStartTipExtra); - line.addTip(aTry.getStartAddress(), tryStartTip, " # :" + catchTip.substring(0, catchTip.length() - 1)); + line.addTip(aTry.getStartOffset(), tryStartTip, " # :" + catchTip.substring(0, catchTip.length() - 1)); } - addr = iCatch.getCatchAllAddress(); + addr = iCatch.getCatchAllHandler(); if (addr > -1) { String catchAllTip = String.format(FMT_CATCH_ALL_TAG, addr); line.addTip(addr, catchAllTip, tryStartTipExtra); - line.addTip(aTry.getStartAddress(), tryStartTip, " # :" + catchAllTip.substring(0, catchAllTip.length() - 1)); + line.addTip(aTry.getStartOffset(), tryStartTip, " # :" + catchAllTip.substring(0, catchAllTip.length() - 1)); } } } diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaFileLoader.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaFileLoader.java index a343b977a..bd3b5cb60 100644 --- a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaFileLoader.java +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/JavaFileLoader.java @@ -108,12 +108,8 @@ public class JavaFileLoader { private static int getNextUniqId() { classUniqId++; if (classUniqId >= 0xFFFF) { - resetDexUniqId(); + classUniqId = 1; } return classUniqId; } - - public static void resetDexUniqId() { - classUniqId = 1; - } } diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaCodeReader.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaCodeReader.java index 50aa5d2cd..fff1e81b0 100644 --- a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaCodeReader.java +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/JavaCodeReader.java @@ -189,23 +189,23 @@ public class JavaCodeReader implements ICodeReader { } private static CatchData convertSingleCatches(List list) { - int allAddr = -1; + int allHandler = -1; for (JavaSingleCatch singleCatch : list) { if (singleCatch.getType() == null) { - allAddr = singleCatch.getHandler(); + allHandler = singleCatch.getHandler(); list.remove(singleCatch); break; } } int len = list.size(); - int[] addrs = new int[len]; + int[] handlers = new int[len]; String[] types = new String[len]; for (int i = 0; i < len; i++) { JavaSingleCatch singleCatch = list.get(i); - addrs[i] = singleCatch.getHandler(); + handlers[i] = singleCatch.getHandler(); types[i] = singleCatch.getType(); } - return new CatchData(addrs, types, allAddr); + return new CatchData(handlers, types, allHandler); } private Set getExcHandlers() { diff --git a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/trycatch/JavaTryData.java b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/trycatch/JavaTryData.java index 9172ea41b..46ed90201 100644 --- a/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/trycatch/JavaTryData.java +++ b/jadx-plugins/jadx-java-input/src/main/java/jadx/plugins/input/java/data/code/trycatch/JavaTryData.java @@ -2,16 +2,17 @@ package jadx.plugins.input.java.data.code.trycatch; import jadx.api.plugins.input.data.ICatch; import jadx.api.plugins.input.data.ITry; +import jadx.api.plugins.utils.Utils; public class JavaTryData implements ITry { - private final int startAddr; - private final int endAddr; + private final int startOffset; + private final int endOffset; private ICatch catchHandler; - public JavaTryData(int startAddr, int endAddr) { - this.startAddr = startAddr; - this.endAddr = endAddr; + public JavaTryData(int startOffset, int endOffset) { + this.startOffset = startOffset; + this.endOffset = endOffset; } @Override @@ -24,18 +25,18 @@ public class JavaTryData implements ITry { } @Override - public int getStartAddress() { - return startAddr; + public int getStartOffset() { + return startOffset; } @Override - public int getEndAddress() { - return endAddr; + public int getEndOffset() { + return endOffset; } @Override public int hashCode() { - return startAddr + 31 * endAddr; + return startOffset + 31 * endOffset; } @Override @@ -47,11 +48,11 @@ public class JavaTryData implements ITry { return false; } JavaTryData that = (JavaTryData) o; - return startAddr == that.startAddr && endAddr == that.endAddr; + return startOffset == that.startOffset && endOffset == that.endOffset; } @Override public String toString() { - return "Try{" + startAddr + " - " + endAddr + ": " + catchHandler + '}'; + return "Try{" + Utils.formatOffset(startOffset) + " - " + Utils.formatOffset(endOffset) + ": " + catchHandler + '}'; } } diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICatch.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICatch.java index c4fb65851..57cd16ad7 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICatch.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ICatch.java @@ -3,7 +3,7 @@ package jadx.api.plugins.input.data; public interface ICatch { String[] getTypes(); - int[] getAddresses(); + int[] getHandlers(); - int getCatchAllAddress(); + int getCatchAllHandler(); } diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ITry.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ITry.java index 578aa6b33..cd1507d88 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ITry.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/ITry.java @@ -3,7 +3,7 @@ package jadx.api.plugins.input.data; public interface ITry { ICatch getCatch(); - int getStartAddress(); + int getStartOffset(); - int getEndAddress(); + int getEndOffset(); } diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/CatchData.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/CatchData.java index ba4f8f3f9..a88852c3a 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/CatchData.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/CatchData.java @@ -1,21 +1,22 @@ package jadx.api.plugins.input.data.impl; import jadx.api.plugins.input.data.ICatch; +import jadx.api.plugins.utils.Utils; public class CatchData implements ICatch { - private final int[] addr; + private final int[] handlers; private final String[] types; - private final int allAddr; + private final int allHandler; - public CatchData(int[] addr, String[] types, int allAddr) { - this.addr = addr; + public CatchData(int[] handlers, String[] types, int allHandler) { + this.handlers = handlers; this.types = types; - this.allAddr = allAddr; + this.allHandler = allHandler; } @Override - public int[] getAddresses() { - return addr; + public int[] getHandlers() { + return handlers; } @Override @@ -24,8 +25,8 @@ public class CatchData implements ICatch { } @Override - public int getCatchAllAddress() { - return allAddr; + public int getCatchAllHandler() { + return allHandler; } @Override @@ -33,10 +34,10 @@ public class CatchData implements ICatch { StringBuilder sb = new StringBuilder("Catch:"); int size = types.length; for (int i = 0; i < size; i++) { - sb.append(' ').append(types[i]).append("->").append(addr[i]); + sb.append(' ').append(types[i]).append("->").append(Utils.formatOffset(handlers[i])); } - if (allAddr != -1) { - sb.append(" all->").append(allAddr); + if (allHandler != -1) { + sb.append(" all->").append(Utils.formatOffset(allHandler)); } return sb.toString(); } diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/TryData.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/TryData.java index f247e0327..62f205b28 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/TryData.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/input/data/impl/TryData.java @@ -2,16 +2,17 @@ package jadx.api.plugins.input.data.impl; import jadx.api.plugins.input.data.ICatch; import jadx.api.plugins.input.data.ITry; +import jadx.api.plugins.utils.Utils; public class TryData implements ITry { - private final int startAddr; - private final int endAddr; + private final int startOffset; + private final int endOffset; private final ICatch catchHandler; - public TryData(int startAddr, int endAddr, ICatch catchHandler) { - this.startAddr = startAddr; - this.endAddr = endAddr; + public TryData(int startOffset, int endOffset, ICatch catchHandler) { + this.startOffset = startOffset; + this.endOffset = endOffset; this.catchHandler = catchHandler; } @@ -21,17 +22,17 @@ public class TryData implements ITry { } @Override - public int getStartAddress() { - return startAddr; + public int getStartOffset() { + return startOffset; } @Override - public int getEndAddress() { - return endAddr; + public int getEndOffset() { + return endOffset; } @Override public String toString() { - return "Try{" + startAddr + " - " + endAddr + ": " + catchHandler + '}'; + return "Try{" + Utils.formatOffset(startOffset) + " - " + Utils.formatOffset(endOffset) + ": " + catchHandler + '}'; } } diff --git a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/utils/Utils.java b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/utils/Utils.java index 9e78bb982..447a4ee06 100644 --- a/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/utils/Utils.java +++ b/jadx-plugins/jadx-plugins-api/src/main/java/jadx/api/plugins/utils/Utils.java @@ -1,23 +1,26 @@ package jadx.api.plugins.utils; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.function.Function; import org.jetbrains.annotations.Nullable; public class Utils { - public static void addToList(List list, @Nullable T item) { + public static void addToList(Collection list, @Nullable T item) { if (item != null) { list.add(item); } } - public static void addToList(List list, @Nullable I item, Function map) { + public static void addToList(Collection list, @Nullable I item, Function map) { if (item != null) { T value = map.apply(item); if (value != null) { @@ -44,6 +47,24 @@ public class Utils { return list; } + public static List concatDistinct(List a, List b) { + int aSize = a.size(); + int bSize = b.size(); + if (aSize == 0 && bSize == 0) { + return Collections.emptyList(); + } + if (aSize == 0) { + return b; + } + if (bSize == 0) { + return a; + } + Set set = new LinkedHashSet<>(aSize + bSize); + set.addAll(a); + set.addAll(b); + return new ArrayList<>(set); + } + public static String listToStr(List list) { if (list == null) { return "null";