feat: rewrite try-catch processing

This commit is contained in:
Skylot
2021-08-10 13:07:05 +03:00
parent 12a66bd83e
commit 7c0671c81b
114 changed files with 2901 additions and 1810 deletions
@@ -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";
+12 -12
View File
@@ -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;
}
}
@@ -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);
@@ -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()) {
@@ -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)) {
@@ -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,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,4 +1,4 @@
package jadx.core.dex.visitors.blocksmaker.helpers;
package jadx.core.dex.visitors.finaly;
import java.util.ArrayList;
import java.util.IdentityHashMap;
@@ -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;
@@ -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 {
@@ -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");
}
}
@@ -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;");
}
}
@@ -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,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();");
}
}
@@ -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