feat: rewrite try-catch processing
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.data.IJavaNodeRef;
|
||||
|
||||
public class JadxNodeRef implements IJavaNodeRef, Comparable<IJavaNodeRef> {
|
||||
public class JadxNodeRef implements IJavaNodeRef {
|
||||
|
||||
@Nullable
|
||||
public static JadxNodeRef forJavaNode(JavaNode javaNode) {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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<IDexTreeVisitor> 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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<T extends IJadxAttribute> implements IJadxAttrType<T> {
|
||||
public static final AType<SkipMethodArgsAttr> SKIP_MTH_ARGS = new AType<>();
|
||||
public static final AType<MethodOverrideAttr> METHOD_OVERRIDE = new AType<>();
|
||||
public static final AType<MethodTypeVarsAttr> METHOD_TYPE_VARS = new AType<>();
|
||||
public static final AType<AttrList<TryCatchBlockAttr>> TRY_BLOCKS_LIST = new AType<>();
|
||||
|
||||
// region
|
||||
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
|
||||
|
||||
// block
|
||||
public static final AType<PhiListAttr> PHI_LIST = new AType<>();
|
||||
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<>();
|
||||
public static final AType<ForceReturnAttr> FORCE_RETURN = new AType<>();
|
||||
public static final AType<CatchAttr> CATCH_BLOCK = new AType<>();
|
||||
public static final AType<SplitterBlockAttr> SPLITTER_BLOCK = new AType<>();
|
||||
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
|
||||
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
|
||||
public static final AType<TmpEdgeAttr> TMP_EDGE = new AType<>();
|
||||
public static final AType<TryCatchBlockAttr> TRY_BLOCK = new AType<>();
|
||||
|
||||
// block or insn
|
||||
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<>();
|
||||
public static final AType<CatchAttr> EXC_CATCH = new AType<>();
|
||||
|
||||
// instruction
|
||||
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
|
||||
|
||||
@@ -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<T> implements IJadxAttribute {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Utils.listToString(list, ICodeWriter.NL);
|
||||
return Utils.listToString(list, ", ");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,11 @@ public abstract class AttrNode implements IAttributeNode {
|
||||
initStorage().add(type, obj);
|
||||
}
|
||||
|
||||
public <T> void addAttr(IJadxAttrType<AttrList<T>> type, List<T> list) {
|
||||
AttributeStorage strg = initStorage();
|
||||
list.forEach(attr -> strg.add(type, attr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyAttributesFrom(AttrNode attrNode) {
|
||||
AttributeStorage copyFrom = attrNode.storage;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<BlockNode> blocks = new HashSet<>(3);
|
||||
|
||||
public Set<BlockNode> getBlocks() {
|
||||
return blocks;
|
||||
}
|
||||
|
||||
public boolean contains(BlockNode block) {
|
||||
return blocks.contains(block);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<IgnoreEdgeAttr> getAttrType() {
|
||||
return AType.IGNORE_EDGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IGNORE_EDGES: " + Utils.listToString(blocks);
|
||||
}
|
||||
}
|
||||
@@ -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<Edge> getExitEdges() {
|
||||
List<Edge> edges = new LinkedList<>();
|
||||
List<Edge> edges = new ArrayList<>();
|
||||
Set<BlockNode> 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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<? extends IJadxAttribute> getAttrType() {
|
||||
return AType.TMP_EDGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TMP_EDGE: " + block;
|
||||
}
|
||||
}
|
||||
@@ -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 extends InsnArg> T copyCommonParams(T copy) {
|
||||
copy.copyAttributesFrom(this);
|
||||
copy.setParentInsn(parentInsn);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Bloc
|
||||
}
|
||||
|
||||
public List<BlockNode> getCleanSuccessors() {
|
||||
return cleanSuccessors;
|
||||
return this.cleanSuccessors;
|
||||
}
|
||||
|
||||
public void updateCleanSuccessors() {
|
||||
@@ -67,12 +66,17 @@ public final class BlockNode extends AttrNode implements IBlock, Comparable<Bloc
|
||||
}
|
||||
|
||||
public void lock() {
|
||||
cleanSuccessors = lockList(cleanSuccessors);
|
||||
successors = lockList(successors);
|
||||
predecessors = lockList(predecessors);
|
||||
dominatesOn = lockList(dominatesOn);
|
||||
if (domFrontier == null) {
|
||||
throw new JadxRuntimeException("Dominance frontier not set for block: " + this);
|
||||
try {
|
||||
List<BlockNode> 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<Bloc
|
||||
}
|
||||
List<BlockNode> 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<Bloc
|
||||
toRemove.add(loop.getStart());
|
||||
}
|
||||
}
|
||||
IgnoreEdgeAttr ignoreEdgeAttr = block.get(AType.IGNORE_EDGE);
|
||||
if (ignoreEdgeAttr != null) {
|
||||
toRemove.addAll(ignoreEdgeAttr.getBlocks());
|
||||
}
|
||||
if (toRemove.isEmpty()) {
|
||||
return sucList;
|
||||
}
|
||||
|
||||
@@ -463,6 +463,7 @@ public class InsnNode extends LineAttrNode {
|
||||
case CONST_CLASS:
|
||||
case CMP_L:
|
||||
case CMP_G:
|
||||
case NOP:
|
||||
return false;
|
||||
|
||||
default:
|
||||
|
||||
@@ -21,7 +21,6 @@ import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
import jadx.core.dex.attributes.nodes.NotificationAttrNode;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.AccessInfo.AFType;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.InsnDecoder;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
@@ -68,7 +67,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
||||
private InsnNode[] instructions;
|
||||
private List<BlockNode> blocks;
|
||||
private BlockNode enterBlock;
|
||||
private List<BlockNode> exitBlocks;
|
||||
private BlockNode exitBlock;
|
||||
private List<SSAVar> sVars;
|
||||
private List<ExceptionHandler> exceptionHandlers;
|
||||
private List<LoopInfo> 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<RegisterArg> 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<BlockNode> 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<BlockNode> 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) {
|
||||
|
||||
@@ -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<ExceptionHandler, IContainer> 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<BlockNode> mergedBlocks;
|
||||
private final List<BlockNode> mergedBlocks;
|
||||
private final BlockNode thenBlock;
|
||||
private final BlockNode elseBlock;
|
||||
private final Set<BlockNode> skipBlocks;
|
||||
private final List<InsnNode> 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<BlockNode> mergedBlocks, Set<BlockNode> skipBlocks, List<InsnNode> forceInlineInsns) {
|
||||
private IfInfo(MethodNode mth, IfCondition condition, BlockNode thenBlock, BlockNode elseBlock,
|
||||
List<BlockNode> mergedBlocks, Set<BlockNode> skipBlocks, List<InsnNode> 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<BlockNode> getMergedBlocks() {
|
||||
public List<BlockNode> 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<InsnNode> getForceInlineInsns() {
|
||||
return forceInlineInsns;
|
||||
}
|
||||
|
||||
@@ -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<ExceptionHandler> handlers;
|
||||
|
||||
public CatchAttr(TryCatchBlock block) {
|
||||
this.tryBlock = block;
|
||||
public CatchAttr(List<ExceptionHandler> handlers) {
|
||||
this.handlers = handlers;
|
||||
}
|
||||
|
||||
public List<ExceptionHandler> getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<CatchAttr> 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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ClassInfo> catchTypes = new TreeSet<>();
|
||||
private final int handleOffset;
|
||||
private final List<ClassInfo> catchTypes = new ArrayList<>(1);
|
||||
private final int handlerOffset;
|
||||
|
||||
private BlockNode handlerBlock;
|
||||
private final List<BlockNode> 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<ClassInfo> types) {
|
||||
@@ -60,7 +62,7 @@ public class ExceptionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public Set<ClassInfo> getCatchTypes() {
|
||||
public List<ClassInfo> getCatchTypes() {
|
||||
return catchTypes;
|
||||
}
|
||||
|
||||
@@ -68,7 +70,7 @@ public class ExceptionHandler {
|
||||
if (isCatchAll()) {
|
||||
return ArgType.THROWABLE;
|
||||
}
|
||||
Set<ClassInfo> types = getCatchTypes();
|
||||
List<ClassInfo> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<SplitterBlockAttr> getAttrType() {
|
||||
return AType.SPLITTER_BLOCK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Splitter:" + block;
|
||||
}
|
||||
}
|
||||
@@ -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<ExceptionHandler> handlers;
|
||||
|
||||
// references for fast remove/modify
|
||||
private final List<InsnNode> insns;
|
||||
private final CatchAttr attr;
|
||||
|
||||
public TryCatchBlock(int handlersCount) {
|
||||
handlers = new ArrayList<>(handlersCount);
|
||||
insns = new ArrayList<>();
|
||||
attr = new CatchAttr(this);
|
||||
}
|
||||
|
||||
public Iterable<ExceptionHandler> 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<ExceptionHandler> 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<ExceptionHandler> 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<InsnNode> 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) + " }";
|
||||
}
|
||||
}
|
||||
@@ -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<ExceptionHandler> handlers;
|
||||
private List<BlockNode> blocks;
|
||||
|
||||
private TryCatchBlockAttr outerTryBlock;
|
||||
private List<TryCatchBlockAttr> innerTryBlocks = Collections.emptyList();
|
||||
private boolean merged = false;
|
||||
|
||||
private BlockNode topSplitter;
|
||||
|
||||
public TryCatchBlockAttr(int id, List<ExceptionHandler> handlers, List<BlockNode> 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<InsnNode> 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<ExceptionHandler> getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
public int getHandlersCount() {
|
||||
return handlers.size();
|
||||
}
|
||||
|
||||
public List<BlockNode> getBlocks() {
|
||||
return blocks;
|
||||
}
|
||||
|
||||
public void setBlocks(List<BlockNode> 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<TryCatchBlockAttr> 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<? extends IJadxAttribute> 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<ITry> tries = codeReader.getTries();
|
||||
private static void initTryCatches(MethodNode mth, InsnNode[] insnByOffset, List<ITry> tries) {
|
||||
if (tries.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int handlersCount = 0;
|
||||
Set<Integer> addrs = new HashSet<>();
|
||||
List<TryCatchBlock> 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<ExceptionHandler> 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<Integer> addrs, ICatch catchBlock) {
|
||||
int[] handlerAddrArr = catchBlock.getAddresses();
|
||||
private static List<ExceptionHandler> 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<ExceptionHandler> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<RegisterArg> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<InsnNode> 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<RegisterArg> args = mth.getArgRegs();
|
||||
if (isRemovedClassInArgs(cls, args)) {
|
||||
modifySyntheticMethod(cls, mth, args);
|
||||
if (af.isConstructor()) {
|
||||
InsnNode insn = BlockUtils.getOnlyOneInsnFromMth(mth);
|
||||
if (insn != null) {
|
||||
List<RegisterArg> 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<RegisterArg> args) {
|
||||
List<InsnNode> 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<RegisterArg> 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);
|
||||
|
||||
@@ -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<InsnNode> 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<RegisterArg> 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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<InsnNode> 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) {
|
||||
|
||||
@@ -137,13 +137,17 @@ public class ExtractFieldInit extends AbstractVisitor {
|
||||
}
|
||||
List<InitInfo> infoList = new ArrayList<>(constrList.size());
|
||||
for (MethodNode constrMth : constrList) {
|
||||
if (constrMth.isNoCode() || constrMth.getBasicBlocks().isEmpty()) {
|
||||
if (constrMth.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
List<BlockNode> 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 {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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<BlockNode> 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<InsnNode> 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<InsnNode> 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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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<TryCatchBlockAttr> 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<TryCatchBlockAttr> tryBlocks) {
|
||||
if (tryBlocks.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int limit = tryBlocks.size() * 3;
|
||||
int count = 0;
|
||||
Deque<TryCatchBlockAttr> 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<BlockNode> 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<TryCatchBlockAttr> prepareTryBlocks(MethodNode mth) {
|
||||
Map<ExceptionHandler, List<BlockNode>> 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<TryCatchBlockAttr> tryBlocks = new ArrayList<>();
|
||||
blocksByHandler.forEach((eh, blocks) -> {
|
||||
List<ExceptionHandler> 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<TryCatchBlockAttr> 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<TryCatchBlockAttr> 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<TryCatchBlockAttr> tryBlocks,
|
||||
TryCatchBlockAttr outerTryBlock, TryCatchBlockAttr innerTryBlock) {
|
||||
if (outerTryBlock.getBlocks().equals(innerTryBlock.getBlocks())) {
|
||||
// same try blocks -> merge handlers
|
||||
List<ExceptionHandler> 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<BlockNode> 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<BlockNode> 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<BlockNode> mergedBlocks = Utils.concatDistinct(outerTryBlock.getBlocks(), innerTryBlock.getBlocks());
|
||||
List<ExceptionHandler> 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<BlockNode> 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<BlockNode> 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<BlockNode> 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<BlockNode> 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<TryCatchBlockAttr> 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<BlockNode> handlerBlocks = ListUtils.map(tryCatch.getHandlers(), ExceptionHandler::getHandlerBlock);
|
||||
List<BlockNode> 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<RegisterArg> 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<TryCatchBlockAttr> tryBlocks) {
|
||||
TypeCompare typeCompare = mth.root().getTypeCompare();
|
||||
Comparator<ArgType> 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<ArgType> 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;
|
||||
}
|
||||
}
|
||||
+99
-191
@@ -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<BlockNode> basicBlocks, BlockNode entryBlock) {
|
||||
@@ -251,7 +255,7 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
} while (changed);
|
||||
}
|
||||
|
||||
private static void calcImmediateDominators(List<BlockNode> basicBlocks, BlockNode entryBlock) {
|
||||
private static void calcImmediateDominators(MethodNode mth, List<BlockNode> 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<BlockNode> domSortedBlocks = new ArrayList<>(mth.getBasicBlocks().size());
|
||||
Deque<BlockNode> 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<BlockNode> 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<BlockNode> basicBlocks = mth.getBasicBlocks();
|
||||
boolean changed = false;
|
||||
List<BlockNode> 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<BlockNode> blocksForMerge = collectExcHandlerBlocks(block, excHandlerAttr);
|
||||
if (mergeHandlers(mth, blocksForMerge, excHandlerAttr)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static List<BlockNode> collectExcHandlerBlocks(BlockNode block, ExcHandlerAttr excHandlerAttr) {
|
||||
List<BlockNode> 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<BlockNode> 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<BlockNode> 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<BlockNode> preds = exitBlock.getPredecessors();
|
||||
List<BlockNode> 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<BlockNode> 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<BlockNode> 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);
|
||||
+133
-167
@@ -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<Integer, BlockNode> 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<Integer, BlockNode> 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<Integer, BlockNode> 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<Integer, BlockNode> 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<Integer, BlockNode> 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<Integer, BlockNode> blocksMap) {
|
||||
private static void setupConnectionsFromJumps(MethodNode mth, Map<Integer, BlockNode> blocksMap) {
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
List<JumpInfo> 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<Integer, BlockNode> 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<Integer, BlockNode> 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<BlockNode> 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<Integer, BlockNode> 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<BlockNode> 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<BlockNode> toRemove) {
|
||||
static void collectSuccessors(BlockNode startBlock, BlockNode methodEnterBlock, Set<BlockNode> toRemove) {
|
||||
Deque<BlockNode> stack = new ArrayDeque<>();
|
||||
stack.add(startBlock);
|
||||
while (!stack.isEmpty()) {
|
||||
-136
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<BlockNode, SplitterBlockAttr> 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<BlockNode, SplitterBlockAttr> entry : splitters.entrySet()) {
|
||||
BlockNode pred = entry.getKey();
|
||||
SplitterBlockAttr splitterAttr = entry.getValue();
|
||||
if (pred == topSplitter) {
|
||||
block.addAttr(splitterAttr);
|
||||
} else {
|
||||
pred.remove(AType.SPLITTER_BLOCK);
|
||||
for (BlockNode s : pred.getCleanSuccessors()) {
|
||||
s.remove(AType.SPLITTER_BLOCK);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,8 +173,8 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
|
||||
return;
|
||||
}
|
||||
InsnNode origReturn = null;
|
||||
List<InsnNode> newReturns = new ArrayList<>(mth.getExitBlocks().size());
|
||||
for (BlockNode exit : mth.getExitBlocks()) {
|
||||
List<InsnNode> 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)) {
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+1
-1
@@ -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;
|
||||
+1
-1
@@ -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;
|
||||
+132
-86
@@ -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<TryCatchBlockAttr> 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<TryCatchBlockAttr> 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<IDexTreeVisitor> 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<InsnNode> 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<BlockNode> 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<BlockNode> 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<ExceptionHandler> 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<BlockNode> 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<BlockNode> 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<BlockNode> list = new ArrayList<>();
|
||||
list.add(start);
|
||||
list.addAll(BlockUtils.collectBlocksDominatedByWithExcHandlers(start, start));
|
||||
Set<BlockNode> checkSet = new LinkedHashSet<>(list);
|
||||
for (BlockNode block : checkSet) {
|
||||
List<BlockNode> pathBlocks = getPathStarts(mth, bottom, bottomFinallyBlock);
|
||||
for (BlockNode pred : pathBlocks) {
|
||||
List<BlockNode> 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<TryCatchBlockAttr> 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<BlockNode> getPathStarts(MethodNode mth, BlockNode bottom, BlockNode bottomFinallyBlock) {
|
||||
Stream<BlockNode> 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<ExceptionHandler> handlers) {
|
||||
handlers.addAll(tryBlock.getHandlers());
|
||||
for (TryCatchBlockAttr innerTryBlock : tryBlock.getInnerTryBlocks()) {
|
||||
collectAllHandlers(innerTryBlock, handlers);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean checkSlices(FinallyExtractInfo extractInfo) {
|
||||
InsnsSlice finallySlice = extractInfo.getFinallyInsnsSlice();
|
||||
List<InsnNode> finallyInsnsList = finallySlice.getInsnsList();
|
||||
@@ -398,8 +411,8 @@ public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
InsnsSlice dupSlice, FinallyExtractInfo extractInfo) {
|
||||
InsnsSlice finallySlice = extractInfo.getFinallyInsnsSlice();
|
||||
|
||||
List<BlockNode> finallyCS = finallyBlock.getCleanSuccessors();
|
||||
List<BlockNode> dupCS = dupBlock.getCleanSuccessors();
|
||||
List<BlockNode> finallyCS = getSuccessorsWithoutLoop(finallyBlock);
|
||||
List<BlockNode> 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<BlockNode> 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<InsnNode> dupInsns = dupBlock.getInstructions();
|
||||
List<InsnNode> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<BlockNode> thenSC = thenBlock.getCleanSuccessors();
|
||||
List<BlockNode> 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<BlockNode> preds = block.getPredecessors();
|
||||
Set<BlockNode> ifBlocks = info.getMergedBlocks();
|
||||
List<BlockNode> 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<BlockNode> c1, Collection<BlockNode> 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<BlockNode> 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<BlockNode> 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<BlockNode> 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<BlockNode> skipped) {
|
||||
while (block != null
|
||||
&& block.getCleanSuccessors().size() < 2
|
||||
&& block.getPredecessors().size() == 1) {
|
||||
skipped.add(block);
|
||||
block = getNextBlock(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
+28
-75
@@ -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<BlockNode, TryCatchBlock> 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<TryCatchBlockAttr> 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<BlockNode, TryCatchBlock> tryBlocksMap) {
|
||||
Set<TryCatchBlock> 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<TryCatchBlockAttr> collectTryCatchBlocks(MethodNode mth) {
|
||||
List<TryCatchBlockAttr> list = mth.getAll(AType.TRY_BLOCKS_LIST);
|
||||
if (list.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<TryCatchBlockAttr> 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<BlockNode, TryCatchBlock> 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<BlockNode> 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<BlockNode, TryCatchBlock> tryBlocksMap, IRegion region) {
|
||||
// search dominator blocks in this region (don't need to go deeper)
|
||||
for (Map.Entry<BlockNode, TryCatchBlock> 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<TryCatchBlockAttr> 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
|
||||
|
||||
@@ -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<BlockNode> 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<BlockNode> 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<LoopInfo> 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<BlockNode> exits = new HashSet<>();
|
||||
Set<BlockNode> exits = new LinkedHashSet<>();
|
||||
Set<BlockNode> 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<BlockNode> 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<BlockNode> 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<BlockNode> list = BlockUtils.collectBlocksDominatedBy(s, s);
|
||||
List<BlockNode> 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<TryCatchBlock> tcs = new HashSet<>();
|
||||
for (ExceptionHandler handler : mth.getExceptionHandlers()) {
|
||||
tcs.add(handler.getTryBlock());
|
||||
}
|
||||
for (TryCatchBlock tc : tcs) {
|
||||
List<TryCatchBlockAttr> tcs = mth.getAll(AType.TRY_BLOCKS_LIST);
|
||||
for (TryCatchBlockAttr tc : tcs) {
|
||||
List<BlockNode> blocks = new ArrayList<>(tc.getHandlersCount());
|
||||
Set<BlockNode> 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<TryCatchBlock> tcs) {
|
||||
protected IRegion processHandlersOutBlocks(MethodNode mth, List<TryCatchBlockAttr> tcs) {
|
||||
Set<IBlock> allRegionBlocks = new HashSet<>();
|
||||
RegionUtils.getAllRegionBlocks(mth.getRegion(), allRegionBlocks);
|
||||
|
||||
Set<IBlock> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<InsnNode> 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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<BlockNode> cleanBlockList(List<BlockNode> list) {
|
||||
List<BlockNode> 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<BlockNode> filterPredecessors(BlockNode block) {
|
||||
List<BlockNode> predecessors = block.getPredecessors();
|
||||
List<BlockNode> 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<InsnNode> 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<InsnNode> 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<BlockNode> 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<BlockNode> consumer) {
|
||||
if (bs == null || bs == EmptyBitSet.EMPTY || bs.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<BlockNode> 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<BlockNode> 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<BlockNode> 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<BlockNode> collectPredecessors(MethodNode mth, BlockNode start, Collection<BlockNode> stopBlocks) {
|
||||
BitSet bs = newBlocksBitSet(mth);
|
||||
if (!stopBlocks.isEmpty()) {
|
||||
bs.or(blocksToBitSet(mth, stopBlocks));
|
||||
}
|
||||
List<BlockNode> list = new ArrayList<>();
|
||||
traversePredecessors(start, bs, list::add);
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Up BFS
|
||||
*/
|
||||
private static void traversePredecessors(BlockNode start, BitSet visited, Consumer<BlockNode> visitor) {
|
||||
Queue<BlockNode> 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<BlockNode> 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<BlockNode> 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<BlockNode> 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<BlockNode> 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<BlockNode> collectBlocksDominatedBy(BlockNode dominator, BlockNode start) {
|
||||
public static List<BlockNode> collectBlocksDominatedBy(MethodNode mth, BlockNode dominator, BlockNode start) {
|
||||
List<BlockNode> 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<BlockNode> collectBlocksDominatedByWithExcHandlers(BlockNode dominator, BlockNode start) {
|
||||
List<BlockNode> result = new ArrayList<>();
|
||||
collectWhileDominates(dominator, start, result, new HashSet<>(), true);
|
||||
public static Set<BlockNode> collectBlocksDominatedByWithExcHandlers(MethodNode mth, BlockNode dominator, BlockNode start) {
|
||||
Set<BlockNode> result = new LinkedHashSet<>();
|
||||
collectWhileDominates(dominator, start, result, newBlocksBitSet(mth), true);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void collectWhileDominates(BlockNode dominator, BlockNode child, List<BlockNode> result,
|
||||
Set<BlockNode> visited, boolean includeExcHandlers) {
|
||||
if (visited.contains(child)) {
|
||||
private static void collectWhileDominates(BlockNode dominator, BlockNode child, Collection<BlockNode> result,
|
||||
BitSet visited, boolean includeExcHandlers) {
|
||||
if (visited.get(child.getId())) {
|
||||
return;
|
||||
}
|
||||
visited.add(child);
|
||||
visited.set(child.getId());
|
||||
List<BlockNode> 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<BlockNode> 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<BlockNode> 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<InsnNode> collectInsnsWithLimit(List<BlockNode> blocks, int limit) {
|
||||
List<InsnNode> insns = new ArrayList<>(limit);
|
||||
for (BlockNode block : blocks) {
|
||||
List<InsnNode> 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<InsnNode> 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<BlockNode> 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<BlockNode, BitSet> calcPostDominance(MethodNode mth) {
|
||||
return calcPartialPostDominance(mth, mth.getBasicBlocks(), mth.getExitBlocks().get(0));
|
||||
return calcPartialPostDominance(mth, mth.getBasicBlocks(), mth.getPreExitBlocks().get(0));
|
||||
}
|
||||
|
||||
public static Map<BlockNode, BitSet> calcPartialPostDominance(MethodNode mth, Collection<BlockNode> 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<BlockNode> blocks, AFlag flag) {
|
||||
for (BlockNode block : blocks) {
|
||||
if (block.contains(flag)) {
|
||||
return block;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -79,9 +79,12 @@ public class InsnRemover {
|
||||
}
|
||||
|
||||
public static void unbindInsns(@Nullable MethodNode mth, List<InsnNode> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ public class InsnUtils {
|
||||
|
||||
@Nullable
|
||||
public static InsnNode searchSingleReturnInsn(MethodNode mth, Predicate<InsnNode> test) {
|
||||
if (!mth.isNoCode() && mth.getExitBlocks().size() == 1) {
|
||||
if (!mth.isNoCode() && mth.getPreExitBlocks().size() == 1) {
|
||||
return searchInsn(mth, InsnType.RETURN, test);
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -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 <T> boolean isSingleElement(@Nullable List<T> list, T obj) {
|
||||
if (list == null || list.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
return Objects.equals(list.get(0), obj);
|
||||
}
|
||||
|
||||
public static <T> boolean unorderedEquals(List<T> first, List<T> second) {
|
||||
if (first.size() != second.size()) {
|
||||
return false;
|
||||
}
|
||||
return first.containsAll(second);
|
||||
}
|
||||
|
||||
public static <T, R> List<R> map(Collection<T> list, Function<T, R> mapFunc) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<R> result = new ArrayList<>(list.size());
|
||||
for (T t : list) {
|
||||
result.add(mapFunc.apply(t));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> T first(List<T> list) {
|
||||
return list.get(0);
|
||||
}
|
||||
|
||||
public static <T> T last(List<T> list) {
|
||||
return list.get(list.size() - 1);
|
||||
}
|
||||
|
||||
public static List<BlockNode> distinctList(List<BlockNode> list) {
|
||||
return new ArrayList<>(new LinkedHashSet<>(list));
|
||||
}
|
||||
}
|
||||
@@ -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<IContainer> 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<IContainer> 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<IContainer> 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) {
|
||||
|
||||
@@ -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<JadxCodeAssertions>
|
||||
|
||||
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<JadxCodeAssertions>
|
||||
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<JadxCodeAssertions, JadxCodeAssertions>... checks) {
|
||||
int passed = 0;
|
||||
List<Throwable> failed = new ArrayList<>();
|
||||
for (Function<JadxCodeAssertions, JadxCodeAssertions> 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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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};");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -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 {
|
||||
|
||||
@@ -39,10 +39,16 @@ public class TestBreakInComplexIf extends IntegrationTest {
|
||||
}
|
||||
|
||||
public void check() {
|
||||
Map<String, Point> map = new HashMap<>();
|
||||
map.put("3", new Point(100, 100));
|
||||
map.put("4", new Point(60, 100));
|
||||
assertThat(test(map, 2), is(3));
|
||||
Map<String, Point> first = new HashMap<>();
|
||||
first.put("3", new Point(100, 100));
|
||||
first.put("4", new Point(60, 100));
|
||||
assertThat(test(first, 2), is(3));
|
||||
|
||||
Map<String, Point> 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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;")));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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) {");
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ public class TestNestedLoops3 extends IntegrationTest {
|
||||
exc();
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
//
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;");
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<InsnNode> insns = mth.getBasicBlocks().get(1).getInstructions();
|
||||
List<InsnNode> insns = BlockUtils.collectAllInsns(mth.getBasicBlocks());
|
||||
assertThat(insns, hasSize(1));
|
||||
InsnNode insnNode = insns.get(0);
|
||||
assertThat(insnNode.getType(), is(InsnType.RETURN));
|
||||
|
||||
@@ -34,7 +34,6 @@ public class TestFieldInit2 extends IntegrationTest {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
printDisassemble();
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
|
||||
+10
-2
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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 {");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {")
|
||||
|
||||
@@ -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) {"));
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {",
|
||||
"}"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<IDexTreeVisitor> passes) {
|
||||
public static void test(ClassNode cls, List<IDexTreeVisitor> passes) {
|
||||
try {
|
||||
cls.load();
|
||||
for (IDexTreeVisitor visitor : passes) {
|
||||
|
||||
@@ -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();");
|
||||
}
|
||||
}
|
||||
|
||||
+7
-9
@@ -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) {", "}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = ");
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user