Commit c8923950 authored by Skylot's avatar Skylot

fix: redone finally extract

parent 4ce5cc84
...@@ -20,6 +20,8 @@ import jadx.core.dex.visitors.EnumVisitor; ...@@ -20,6 +20,8 @@ import jadx.core.dex.visitors.EnumVisitor;
import jadx.core.dex.visitors.ExtractFieldInit; import jadx.core.dex.visitors.ExtractFieldInit;
import jadx.core.dex.visitors.FallbackModeVisitor; import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.dex.visitors.IDexTreeVisitor; import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.InitCodeVariables;
import jadx.core.dex.visitors.MarkFinallyVisitor;
import jadx.core.dex.visitors.MethodInlineVisitor; import jadx.core.dex.visitors.MethodInlineVisitor;
import jadx.core.dex.visitors.ModVisitor; import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.PrepareForCodeGen; import jadx.core.dex.visitors.PrepareForCodeGen;
...@@ -27,19 +29,18 @@ import jadx.core.dex.visitors.ReSugarCode; ...@@ -27,19 +29,18 @@ import jadx.core.dex.visitors.ReSugarCode;
import jadx.core.dex.visitors.RenameVisitor; import jadx.core.dex.visitors.RenameVisitor;
import jadx.core.dex.visitors.SimplifyVisitor; import jadx.core.dex.visitors.SimplifyVisitor;
import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler; import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler;
import jadx.core.dex.visitors.blocksmaker.BlockFinallyExtract;
import jadx.core.dex.visitors.blocksmaker.BlockFinish; import jadx.core.dex.visitors.blocksmaker.BlockFinish;
import jadx.core.dex.visitors.blocksmaker.BlockProcessor; import jadx.core.dex.visitors.blocksmaker.BlockProcessor;
import jadx.core.dex.visitors.blocksmaker.BlockSplitter; import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor; import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
import jadx.core.dex.visitors.debuginfo.DebugInfoParseVisitor; import jadx.core.dex.visitors.debuginfo.DebugInfoParseVisitor;
import jadx.core.dex.visitors.regions.CheckRegions; import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.CleanRegions;
import jadx.core.dex.visitors.regions.IfRegionVisitor; import jadx.core.dex.visitors.regions.IfRegionVisitor;
import jadx.core.dex.visitors.regions.LoopRegionVisitor; import jadx.core.dex.visitors.regions.LoopRegionVisitor;
import jadx.core.dex.visitors.regions.RegionMakerVisitor; import jadx.core.dex.visitors.regions.RegionMakerVisitor;
import jadx.core.dex.visitors.regions.ReturnVisitor; import jadx.core.dex.visitors.regions.ReturnVisitor;
import jadx.core.dex.visitors.regions.variables.ProcessVariables; import jadx.core.dex.visitors.regions.variables.ProcessVariables;
import jadx.core.dex.visitors.ssa.EliminatePhiNodes;
import jadx.core.dex.visitors.ssa.SSATransform; import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor; import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
...@@ -68,14 +69,14 @@ public class Jadx { ...@@ -68,14 +69,14 @@ public class Jadx {
passes.add(new BlockProcessor()); passes.add(new BlockProcessor());
passes.add(new BlockExceptionHandler()); passes.add(new BlockExceptionHandler());
passes.add(new BlockFinallyExtract());
passes.add(new BlockFinish()); passes.add(new BlockFinish());
passes.add(new SSATransform()); passes.add(new SSATransform());
passes.add(new ConstructorVisitor()); passes.add(new ConstructorVisitor());
passes.add(new InitCodeVariables());
passes.add(new MarkFinallyVisitor());
passes.add(new ConstInlineVisitor()); passes.add(new ConstInlineVisitor());
passes.add(new TypeInferenceVisitor()); passes.add(new TypeInferenceVisitor());
passes.add(new EliminatePhiNodes());
passes.add(new DebugInfoApplyVisitor()); passes.add(new DebugInfoApplyVisitor());
passes.add(new ModVisitor()); passes.add(new ModVisitor());
...@@ -88,6 +89,7 @@ public class Jadx { ...@@ -88,6 +89,7 @@ public class Jadx {
passes.add(new RegionMakerVisitor()); passes.add(new RegionMakerVisitor());
passes.add(new IfRegionVisitor()); passes.add(new IfRegionVisitor());
passes.add(new ReturnVisitor()); passes.add(new ReturnVisitor());
passes.add(new CleanRegions());
passes.add(new CodeShrinker()); passes.add(new CodeShrinker());
passes.add(new SimplifyVisitor()); passes.add(new SimplifyVisitor());
......
...@@ -499,7 +499,6 @@ public class InsnGen { ...@@ -499,7 +499,6 @@ public class InsnGen {
break; break;
case PHI: case PHI:
case MERGE:
fallbackOnlyInsn(insn); fallbackOnlyInsn(insn);
code.add(insn.getType().toString()).add("("); code.add(insn.getType().toString()).add("(");
for (InsnArg insnArg : insn.getArguments()) { for (InsnArg insnArg : insn.getArguments()) {
......
...@@ -19,6 +19,8 @@ import jadx.core.dex.instructions.args.NamedArg; ...@@ -19,6 +19,8 @@ import jadx.core.dex.instructions.args.NamedArg;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.instructions.mods.ConstructorInsn; import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.StringUtils; import jadx.core.utils.StringUtils;
...@@ -55,6 +57,17 @@ public class NameGen { ...@@ -55,6 +57,17 @@ public class NameGen {
public NameGen(MethodNode mth, boolean fallback) { public NameGen(MethodNode mth, boolean fallback) {
this.mth = mth; this.mth = mth;
this.fallback = fallback; this.fallback = fallback;
addNamesUsedInClass();
}
private void addNamesUsedInClass() {
ClassNode parentClass = mth.getParentClass();
for (FieldNode field : parentClass.getFields()) {
varNames.add(field.getAlias());
}
for (ClassNode innerClass : parentClass.getInnerClasses()) {
varNames.add(innerClass.getAlias().getShortName());
}
} }
public String assignArg(CodeVar var) { public String assignArg(CodeVar var) {
......
...@@ -3,6 +3,7 @@ package jadx.core.codegen; ...@@ -3,6 +3,7 @@ package jadx.core.codegen;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -36,6 +37,7 @@ import jadx.core.dex.regions.loops.ForLoop; ...@@ -36,6 +37,7 @@ import jadx.core.dex.regions.loops.ForLoop;
import jadx.core.dex.regions.loops.LoopRegion; import jadx.core.dex.regions.loops.LoopRegion;
import jadx.core.dex.regions.loops.LoopType; import jadx.core.dex.regions.loops.LoopType;
import jadx.core.dex.trycatch.ExceptionHandler; import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.ErrorsCounter; import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.RegionUtils; import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.CodegenException; import jadx.core.utils.exceptions.CodegenException;
...@@ -98,6 +100,10 @@ public class RegionGen extends InsnGen { ...@@ -98,6 +100,10 @@ public class RegionGen extends InsnGen {
} }
private void makeSimpleBlock(IBlock block, CodeWriter code) throws CodegenException { private void makeSimpleBlock(IBlock block, CodeWriter code) throws CodegenException {
if (block.contains(AFlag.DONT_GENERATE)) {
return;
}
for (InsnNode insn : block.getInstructions()) { for (InsnNode insn : block.getInstructions()) {
if (!insn.contains(AFlag.DONT_GENERATE)) { if (!insn.contains(AFlag.DONT_GENERATE)) {
makeInsn(insn, code); makeInsn(insn, code);
...@@ -233,7 +239,8 @@ public class RegionGen extends InsnGen { ...@@ -233,7 +239,8 @@ public class RegionGen extends InsnGen {
} }
private CodeWriter makeSwitch(SwitchRegion sw, CodeWriter code) throws CodegenException { private CodeWriter makeSwitch(SwitchRegion sw, CodeWriter code) throws CodegenException {
SwitchNode insn = (SwitchNode) sw.getHeader().getInstructions().get(0); SwitchNode insn = (SwitchNode) BlockUtils.getLastInsn(sw.getHeader());
Objects.requireNonNull(insn, "Switch insn not found in header");
InsnArg arg = insn.getArg(0); InsnArg arg = insn.getArg(0);
code.startLine("switch ("); code.startLine("switch (");
addArg(code, arg, false); addArg(code, arg, false);
......
...@@ -8,26 +8,28 @@ public enum AFlag { ...@@ -8,26 +8,28 @@ public enum AFlag {
LOOP_END, LOOP_END,
SYNTHETIC, SYNTHETIC,
FINAL, // SSAVar attribute for make var final
RETURN, // block contains only return instruction RETURN, // block contains only return instruction
ORIG_RETURN, ORIG_RETURN,
DECLARE_VAR,
DONT_WRAP, DONT_WRAP,
DONT_INLINE, DONT_INLINE,
DONT_GENERATE, // process as usual, but don't output to generated code DONT_GENERATE, // process as usual, but don't output to generated code
REMOVE, // can be completely removed REMOVE, // can be completely removed
ADDED_TO_REGION, ADDED_TO_REGION,
FINALLY_INSNS,
SKIP_FIRST_ARG, SKIP_FIRST_ARG,
SKIP_ARG, // skip argument in invoke call SKIP_ARG, // skip argument in invoke call
ANONYMOUS_CONSTRUCTOR, ANONYMOUS_CONSTRUCTOR,
ANONYMOUS_CLASS, ANONYMOUS_CLASS,
THIS, THIS,
METHOD_ARGUMENT, // RegisterArg attribute for method arguments METHOD_ARGUMENT, // RegisterArg attribute for method arguments
CUSTOM_DECLARE, // variable for this register don't need declaration CUSTOM_DECLARE, // variable for this register don't need declaration
DECLARE_VAR,
ELSE_IF_CHAIN, ELSE_IF_CHAIN,
......
...@@ -27,6 +27,9 @@ public class PhiListAttr implements IAttribute { ...@@ -27,6 +27,9 @@ public class PhiListAttr implements IAttribute {
for (PhiInsn phiInsn : list) { for (PhiInsn phiInsn : list) {
sb.append('r').append(phiInsn.getResult().getRegNum()).append(" "); sb.append('r').append(phiInsn.getResult().getRegNum()).append(" ");
} }
for (PhiInsn phiInsn : list) {
sb.append("\n ").append(phiInsn).append(" ").append(phiInsn.getAttributesString());
}
return sb.toString(); return sb.toString();
} }
} }
...@@ -30,7 +30,7 @@ public class RegDebugInfoAttr implements IAttribute { ...@@ -30,7 +30,7 @@ public class RegDebugInfoAttr implements IAttribute {
} }
@Override @Override
public AType getType() { public AType<RegDebugInfoAttr> getType() {
return AType.REG_DEBUG_INFO; return AType.REG_DEBUG_INFO;
} }
......
...@@ -15,35 +15,38 @@ import jadx.core.utils.exceptions.JadxRuntimeException; ...@@ -15,35 +15,38 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
public final class FillArrayNode extends InsnNode { public final class FillArrayNode extends InsnNode {
private static final ArgType ONE_BYTE_TYPE = ArgType.unknown(PrimitiveType.BOOLEAN, PrimitiveType.BYTE);
private static final ArgType TWO_BYTES_TYPE = ArgType.unknown(PrimitiveType.SHORT, PrimitiveType.CHAR);
private static final ArgType FOUR_BYTES_TYPE = ArgType.unknown(PrimitiveType.INT, PrimitiveType.FLOAT);
private static final ArgType EIGHT_BYTES_TYPE = ArgType.unknown(PrimitiveType.LONG, PrimitiveType.DOUBLE);
private final Object data; private final Object data;
private final int size; private final int size;
private ArgType elemType; private ArgType elemType;
public FillArrayNode(int resReg, FillArrayDataPayloadDecodedInstruction payload) { public FillArrayNode(int resReg, FillArrayDataPayloadDecodedInstruction payload) {
super(InsnType.FILL_ARRAY, 1); super(InsnType.FILL_ARRAY, 1);
ArgType elType; ArgType elType = getElementType(payload.getElementWidthUnit());
switch (payload.getElementWidthUnit()) { addArg(InsnArg.reg(resReg, ArgType.array(elType)));
this.data = payload.getData();
this.size = payload.getSize();
this.elemType = elType;
}
private static ArgType getElementType(short elementWidthUnit) {
switch (elementWidthUnit) {
case 1: case 1:
elType = ArgType.unknown(PrimitiveType.BOOLEAN, PrimitiveType.BYTE); return ONE_BYTE_TYPE;
break;
case 2: case 2:
elType = ArgType.unknown(PrimitiveType.SHORT, PrimitiveType.CHAR); return TWO_BYTES_TYPE;
break;
case 4: case 4:
elType = ArgType.unknown(PrimitiveType.INT, PrimitiveType.FLOAT); return FOUR_BYTES_TYPE;
break;
case 8: case 8:
elType = ArgType.unknown(PrimitiveType.LONG, PrimitiveType.DOUBLE); return EIGHT_BYTES_TYPE;
break;
default: default:
throw new JadxRuntimeException("Unknown array element width: " + payload.getElementWidthUnit()); throw new JadxRuntimeException("Unknown array element width: " + elementWidthUnit);
} }
addArg(InsnArg.reg(resReg, ArgType.array(elType)));
this.data = payload.getData();
this.size = payload.getSize();
this.elemType = elType;
} }
public Object getData() { public Object getData() {
......
...@@ -19,7 +19,6 @@ import jadx.core.dex.info.MethodInfo; ...@@ -19,7 +19,6 @@ import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
...@@ -405,12 +404,10 @@ public class InsnDecoder { ...@@ -405,12 +404,10 @@ public class InsnDecoder {
return new GotoNode(insn.getTarget()); return new GotoNode(insn.getTarget());
case Opcodes.THROW: case Opcodes.THROW:
return insn(InsnType.THROW, null, return insn(InsnType.THROW, null, InsnArg.reg(insn, 0, ArgType.THROWABLE));
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)));
case Opcodes.MOVE_EXCEPTION: case Opcodes.MOVE_EXCEPTION:
return insn(InsnType.MOVE_EXCEPTION, return insn(InsnType.MOVE_EXCEPTION, InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT_NO_ARRAY));
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)));
case Opcodes.RETURN_VOID: case Opcodes.RETURN_VOID:
return new InsnNode(InsnType.RETURN, 0); return new InsnNode(InsnType.RETURN, 0);
......
...@@ -66,9 +66,6 @@ public enum InsnType { ...@@ -66,9 +66,6 @@ public enum InsnType {
ONE_ARG, ONE_ARG,
PHI, PHI,
// merge all arguments in one
MERGE,
// TODO: now multidimensional arrays created using Array.newInstance function // TODO: now multidimensional arrays created using Array.newInstance function
NEW_MULTIDIM_ARRAY NEW_MULTIDIM_ARRAY
} }
...@@ -24,6 +24,7 @@ public final class PhiInsn extends InsnNode { ...@@ -24,6 +24,7 @@ public final class PhiInsn extends InsnNode {
this.blockBinds = new LinkedHashMap<>(predecessors); this.blockBinds = new LinkedHashMap<>(predecessors);
setResult(InsnArg.reg(regNum, ArgType.UNKNOWN)); setResult(InsnArg.reg(regNum, ArgType.UNKNOWN));
add(AFlag.DONT_INLINE); add(AFlag.DONT_INLINE);
add(AFlag.DONT_GENERATE);
} }
public RegisterArg bindArg(BlockNode pred) { public RegisterArg bindArg(BlockNode pred) {
......
...@@ -30,6 +30,8 @@ public abstract class ArgType { ...@@ -30,6 +30,8 @@ public abstract class ArgType {
public static final ArgType UNKNOWN = unknown(PrimitiveType.values()); public static final ArgType UNKNOWN = unknown(PrimitiveType.values());
public static final ArgType UNKNOWN_OBJECT = unknown(PrimitiveType.OBJECT, PrimitiveType.ARRAY); public static final ArgType UNKNOWN_OBJECT = unknown(PrimitiveType.OBJECT, PrimitiveType.ARRAY);
public static final ArgType UNKNOWN_OBJECT_NO_ARRAY = unknown(PrimitiveType.OBJECT);
public static final ArgType UNKNOWN_ARRAY = array(UNKNOWN);
public static final ArgType NARROW = unknown( public static final ArgType NARROW = unknown(
PrimitiveType.INT, PrimitiveType.FLOAT, PrimitiveType.INT, PrimitiveType.FLOAT,
......
package jadx.core.dex.instructions.args; package jadx.core.dex.instructions.args;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
public class CodeVar { public class CodeVar {
private String name; private String name;
private ArgType type; private ArgType type;
private List<SSAVar> ssaVars; private List<SSAVar> ssaVars = new ArrayList<>(3);
private boolean isFinal; private boolean isFinal;
private boolean isThis; private boolean isThis;
...@@ -42,14 +43,16 @@ public class CodeVar { ...@@ -42,14 +43,16 @@ public class CodeVar {
return ssaVars; return ssaVars;
} }
public void setSsaVars(List<SSAVar> ssaVars) { public void addSsaVar(SSAVar ssaVar) {
if (ssaVars.size() == 1) { if (!ssaVars.contains(ssaVar)) {
this.ssaVars = Collections.singletonList(ssaVars.get(0)); ssaVars.add(ssaVar);
} else {
this.ssaVars = ssaVars;
} }
} }
public void setSsaVars(List<SSAVar> ssaVars) {
this.ssaVars = ssaVars;
}
public boolean isFinal() { public boolean isFinal() {
return isFinal; return isFinal;
} }
......
...@@ -139,6 +139,7 @@ public class SSAVar extends AttrNode { ...@@ -139,6 +139,7 @@ public class SSAVar extends AttrNode {
public void setCodeVar(@NotNull CodeVar codeVar) { public void setCodeVar(@NotNull CodeVar codeVar) {
this.codeVar = codeVar; this.codeVar = codeVar;
codeVar.addSsaVar(this);
} }
public boolean isCodeVarSet() { public boolean isCodeVarSet() {
......
...@@ -15,6 +15,7 @@ import jadx.core.dex.instructions.args.LiteralArg; ...@@ -15,6 +15,7 @@ import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
public final class IfCondition { public final class IfCondition {
...@@ -54,10 +55,11 @@ public final class IfCondition { ...@@ -54,10 +55,11 @@ public final class IfCondition {
} }
public static IfCondition fromIfBlock(BlockNode header) { public static IfCondition fromIfBlock(BlockNode header) {
if (header == null) { InsnNode lastInsn = BlockUtils.getLastInsn(header);
if (lastInsn == null) {
return null; return null;
} }
return fromIfNode((IfNode) header.getInstructions().get(0)); return fromIfNode((IfNode) lastInsn);
} }
public static IfCondition fromIfNode(IfNode insn) { public static IfCondition fromIfNode(IfNode insn) {
......
...@@ -8,8 +8,9 @@ import jadx.core.dex.nodes.BlockNode; ...@@ -8,8 +8,9 @@ import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IBranchRegion; import jadx.core.dex.nodes.IBranchRegion;
import jadx.core.dex.nodes.IContainer; import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.AbstractRegion; import jadx.core.dex.regions.AbstractRegion;
import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.BlockUtils;
public final class IfRegion extends AbstractRegion implements IBranchRegion { public final class IfRegion extends AbstractRegion implements IBranchRegion {
...@@ -21,9 +22,6 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion { ...@@ -21,9 +22,6 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
public IfRegion(IRegion parent, BlockNode header) { public IfRegion(IRegion parent, BlockNode header) {
super(parent); super(parent);
if (header.getInstructions().size() != 1) {
throw new JadxRuntimeException("Expected only one instruction in 'if' header");
}
this.header = header; this.header = header;
this.condition = IfCondition.fromIfBlock(header); this.condition = IfCondition.fromIfBlock(header);
} }
...@@ -74,10 +72,8 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion { ...@@ -74,10 +72,8 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
} }
public int getSourceLine() { public int getSourceLine() {
if (header.getInstructions().isEmpty()) { InsnNode lastInsn = BlockUtils.getLastInsn(header);
return 0; return lastInsn == null ? 0 : lastInsn.getSourceLine();
}
return header.getInstructions().get(0).getSourceLine();
} }
@Override @Override
...@@ -130,6 +126,6 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion { ...@@ -130,6 +126,6 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
@Override @Override
public String toString() { public String toString() {
return "IF " + header + " then (" + thenRegion + ") else (" + elseRegion + ")"; return "IF " + header + " THEN:" + thenRegion + " ELSE:" + elseRegion;
} }
} }
...@@ -15,6 +15,7 @@ import jadx.core.dex.nodes.IRegion; ...@@ -15,6 +15,7 @@ import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.regions.AbstractRegion; import jadx.core.dex.regions.AbstractRegion;
import jadx.core.dex.regions.conditions.IfCondition; import jadx.core.dex.regions.conditions.IfCondition;
import jadx.core.utils.BlockUtils;
public final class LoopRegion extends AbstractRegion { public final class LoopRegion extends AbstractRegion {
...@@ -76,7 +77,7 @@ public final class LoopRegion extends AbstractRegion { ...@@ -76,7 +77,7 @@ public final class LoopRegion extends AbstractRegion {
} }
private IfNode getIfInsn() { private IfNode getIfInsn() {
return (IfNode) conditionBlock.getInstructions().get(0); return (IfNode) BlockUtils.getLastInsn(conditionBlock);
} }
/** /**
...@@ -132,13 +133,8 @@ public final class LoopRegion extends AbstractRegion { ...@@ -132,13 +133,8 @@ public final class LoopRegion extends AbstractRegion {
} }
public int getConditionSourceLine() { public int getConditionSourceLine() {
if (conditionBlock != null) { InsnNode lastInsn = BlockUtils.getLastInsn(conditionBlock);
List<InsnNode> condInsns = conditionBlock.getInstructions(); return lastInsn == null ? 0 : lastInsn.getSourceLine();
if (!condInsns.isEmpty()) {
return condInsns.get(0).getSourceLine();
}
}
return 0;
} }
public LoopType getType() { public LoopType getType() {
......
...@@ -223,6 +223,14 @@ public class CodeShrinker extends AbstractVisitor { ...@@ -223,6 +223,14 @@ public class CodeShrinker extends AbstractVisitor {
if (assignInsn == null || assignInsn.contains(AFlag.DONT_INLINE)) { if (assignInsn == null || assignInsn.contains(AFlag.DONT_INLINE)) {
continue; continue;
} }
List<RegisterArg> useList = sVar.getUseList();
if (!useList.isEmpty()) {
InsnNode parentInsn = useList.get(0).getParentInsn();
if (parentInsn != null && parentInsn.contains(AFlag.DONT_GENERATE)) {
continue;
}
}
int assignPos = insnList.getIndex(assignInsn); int assignPos = insnList.getIndex(assignInsn);
if (assignPos != -1) { if (assignPos != -1) {
WrapInfo wrapInfo = argsInfo.checkInline(assignPos, arg); WrapInfo wrapInfo = argsInfo.checkInline(assignPos, arg);
......
...@@ -27,7 +27,10 @@ import jadx.core.utils.exceptions.JadxOverflowException; ...@@ -27,7 +27,10 @@ import jadx.core.utils.exceptions.JadxOverflowException;
@JadxVisitor( @JadxVisitor(
name = "Constants Inline", name = "Constants Inline",
desc = "Inline constant registers into instructions", desc = "Inline constant registers into instructions",
runAfter = SSATransform.class, runAfter = {
SSATransform.class,
MarkFinallyVisitor.class
},
runBefore = TypeInferenceVisitor.class runBefore = TypeInferenceVisitor.class
) )
public class ConstInlineVisitor extends AbstractVisitor { public class ConstInlineVisitor extends AbstractVisitor {
...@@ -48,7 +51,7 @@ public class ConstInlineVisitor extends AbstractVisitor { ...@@ -48,7 +51,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
} }
private static void checkInsn(MethodNode mth, InsnNode insn, List<InsnNode> toRemove) { private static void checkInsn(MethodNode mth, InsnNode insn, List<InsnNode> toRemove) {
if (insn.contains(AFlag.DONT_INLINE)) { if (insn.contains(AFlag.DONT_INLINE) || insn.contains(AFlag.DONT_GENERATE)) {
return; return;
} }
InsnType insnType = insn.getType(); InsnType insnType = insn.getType();
...@@ -71,9 +74,36 @@ public class ConstInlineVisitor extends AbstractVisitor { ...@@ -71,9 +74,36 @@ public class ConstInlineVisitor extends AbstractVisitor {
} }
return; return;
} }
if (checkForFinallyBlock(sVar)) {
return;
}
// all check passed, run replace
replaceConst(mth, insn, lit, toRemove); replaceConst(mth, insn, lit, toRemove);
} }
private static boolean checkForFinallyBlock(SSAVar sVar) {
List<SSAVar> ssaVars = sVar.getCodeVar().getSsaVars();
if (ssaVars.size() <= 1) {
return false;
}
int countInsns = 0;
int countFinallyInsns = 0;
for (SSAVar ssaVar : ssaVars) {
for (RegisterArg reg : ssaVar.getUseList()) {
InsnNode parentInsn = reg.getParentInsn();
if (parentInsn != null) {
countInsns++;
if (parentInsn.contains(AFlag.FINALLY_INSNS)) {
countFinallyInsns++;
}
}
}
}
return countFinallyInsns != 0 && countFinallyInsns != countInsns;
}
/** /**
* Don't inline null object if: * Don't inline null object if:
* - used as instance arg in invoke instruction * - used as instance arg in invoke instruction
...@@ -101,7 +131,7 @@ public class ConstInlineVisitor extends AbstractVisitor { ...@@ -101,7 +131,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
return false; return false;
} }
private static void replaceConst(MethodNode mth, InsnNode constInsn, long literal, List<InsnNode> toRemove) { private static int replaceConst(MethodNode mth, InsnNode constInsn, long literal, List<InsnNode> toRemove) {
SSAVar ssaVar = constInsn.getResult().getSVar(); SSAVar ssaVar = constInsn.getResult().getSVar();
List<RegisterArg> useList = new ArrayList<>(ssaVar.getUseList()); List<RegisterArg> useList = new ArrayList<>(ssaVar.getUseList());
int replaceCount = 0; int replaceCount = 0;
...@@ -113,6 +143,7 @@ public class ConstInlineVisitor extends AbstractVisitor { ...@@ -113,6 +143,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
if (replaceCount == useList.size()) { if (replaceCount == useList.size()) {
toRemove.add(constInsn); toRemove.add(constInsn);
} }
return replaceCount;
} }
private static boolean replaceArg(MethodNode mth, RegisterArg arg, long literal, InsnNode constInsn, List<InsnNode> toRemove) { private static boolean replaceArg(MethodNode mth, RegisterArg arg, long literal, InsnNode constInsn, List<InsnNode> toRemove) {
...@@ -121,7 +152,7 @@ public class ConstInlineVisitor extends AbstractVisitor { ...@@ -121,7 +152,7 @@ public class ConstInlineVisitor extends AbstractVisitor {
return false; return false;
} }
InsnType insnType = useInsn.getType(); InsnType insnType = useInsn.getType();
if (insnType == InsnType.PHI || insnType == InsnType.MERGE) { if (insnType == InsnType.PHI) {
return false; return false;
} }
ArgType argType = arg.getInitType(); ArgType argType = arg.getInitType();
......
...@@ -185,9 +185,9 @@ public class DotGraphVisitor extends AbstractVisitor { ...@@ -185,9 +185,9 @@ public class DotGraphVisitor extends AbstractVisitor {
dot.add("}\"];"); dot.add("}\"];");
BlockNode falsePath = null; BlockNode falsePath = null;
List<InsnNode> list = block.getInstructions(); InsnNode lastInsn = BlockUtils.getLastInsn(block);
if (!list.isEmpty() && list.get(0).getType() == InsnType.IF) { if (lastInsn != null && lastInsn.getType() == InsnType.IF) {
falsePath = ((IfNode) list.get(0)).getElseBlock(); falsePath = ((IfNode) lastInsn).getElseBlock();
} }
for (BlockNode next : block.getSuccessors()) { for (BlockNode next : block.getSuccessors()) {
String style = next == falsePath ? "[style=dashed]" : ""; String style = next == falsePath ? "[style=dashed]" : "";
......
package jadx.core.dex.visitors.ssa; package jadx.core.dex.visitors;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set; import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.PhiListAttr;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.PhiInsn; import jadx.core.dex.instructions.PhiInsn;
import jadx.core.dex.instructions.args.CodeVar; import jadx.core.dex.instructions.args.CodeVar;
import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.instructions.args.SSAVar;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.exceptions.JadxException; import jadx.core.utils.exceptions.JadxException;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class EliminatePhiNodes extends AbstractVisitor { @JadxVisitor(
private static final Logger LOG = LoggerFactory.getLogger(EliminatePhiNodes.class); name = "InitCodeVariables",
desc = "Initialize code variables",
runAfter = SSATransform.class
)
public class InitCodeVariables extends AbstractVisitor {
@Override @Override
public void visit(MethodNode mth) throws JadxException { public void visit(MethodNode mth) throws JadxException {
if (mth.isNoCode()) { if (mth.isNoCode()) {
return; return;
} }
replaceMergeInstructions(mth);
removePhiInstructions(mth);
initCodeVars(mth); initCodeVars(mth);
} }
private static void removePhiInstructions(MethodNode mth) { private static void initCodeVars(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
PhiListAttr phiList = block.get(AType.PHI_LIST);
if (phiList == null) {
continue;
}
List<PhiInsn> list = phiList.getList();
for (PhiInsn phiInsn : list) {
removeInsn(mth, block, phiInsn);
}
}
}
private static void removeInsn(MethodNode mth, BlockNode block, PhiInsn phiInsn) {
Iterator<InsnNode> it = block.getInstructions().iterator();
while (it.hasNext()) {
InsnNode insn = it.next();
if (insn == phiInsn) {
it.remove();
return;
}
}
LOG.warn("Phi node not removed: {}, mth: {}", phiInsn, mth);
phiInsn.add(AFlag.DONT_GENERATE);
}
private void replaceMergeInstructions(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
List<InsnNode> insns = block.getInstructions();
if (insns.isEmpty()) {
continue;
}
InsnNode insn = insns.get(0);
if (insn.getType() == InsnType.MERGE) {
replaceMerge(mth, block, insn);
}
}
}
/**
* Replace 'MERGE' with 'PHI' insn.
*/
private void replaceMerge(MethodNode mth, BlockNode block, InsnNode insn) {
if (insn.getArgsCount() != 2) {
throw new JadxRuntimeException("Unexpected count of arguments in merge insn: " + insn);
}
RegisterArg oldArg = (RegisterArg) insn.getArg(1);
RegisterArg newArg = (RegisterArg) insn.getArg(0);
int newRegNum = newArg.getRegNum();
if (oldArg.getRegNum() == newRegNum) {
throw new JadxRuntimeException("Unexpected register number in merge insn: " + insn);
}
SSAVar oldSVar = oldArg.getSVar();
RegisterArg assignArg = oldSVar.getAssign();
InsnNode assignParentInsn = assignArg.getParentInsn();
BlockNode assignBlock = BlockUtils.getBlockByInsn(mth, assignParentInsn);
if (assignBlock == null) {
throw new JadxRuntimeException("Unknown assign block for " + assignParentInsn);
}
BlockNode assignPred = null;
for (BlockNode pred : block.getPredecessors()) {
if (BlockUtils.isPathExists(assignBlock, pred)) {
assignPred = pred;
break;
}
}
if (assignPred == null) {
throw new JadxRuntimeException("Assign predecessor not found for " + assignBlock + " from " + block);
}
// all checks passed
RegisterArg newAssignArg = oldArg.duplicate(newRegNum, null);
SSAVar newSVar = mth.makeNewSVar(newRegNum, newAssignArg);
newSVar.setName(oldSVar.getName());
mth.root().getTypeUpdate().apply(newSVar, assignArg.getType());
if (assignParentInsn != null) {
assignParentInsn.setResult(newAssignArg);
}
for (RegisterArg useArg : new ArrayList<>(oldSVar.getUseList())) {
RegisterArg newUseArg = useArg.duplicate(newRegNum, newSVar);
InsnNode parentInsn = useArg.getParentInsn();
if (parentInsn != null) {
newSVar.use(newUseArg);
parentInsn.replaceArg(useArg, newUseArg);
}
}
block.getInstructions().remove(0);
PhiInsn phiInsn = SSATransform.addPhi(mth, block, newRegNum);
phiInsn.setResult(insn.getResult());
phiInsn.bindArg(newAssignArg.duplicate(), assignPred);
phiInsn.bindArg(newArg.duplicate(),
BlockUtils.selectOtherSafe(assignPred, block.getPredecessors()));
}
private void initCodeVars(MethodNode mth) {
for (RegisterArg mthArg : mth.getArguments(true)) { for (RegisterArg mthArg : mth.getArguments(true)) {
initCodeVar(mthArg.getSVar()); initCodeVar(mthArg.getSVar());
} }
...@@ -144,7 +36,7 @@ public class EliminatePhiNodes extends AbstractVisitor { ...@@ -144,7 +36,7 @@ public class EliminatePhiNodes extends AbstractVisitor {
} }
} }
private void initCodeVar(SSAVar ssaVar) { public static void initCodeVar(SSAVar ssaVar) {
if (ssaVar.isCodeVarSet()) { if (ssaVar.isCodeVarSet()) {
return; return;
} }
......
...@@ -235,7 +235,6 @@ public class ModVisitor extends AbstractVisitor { ...@@ -235,7 +235,6 @@ public class ModVisitor extends AbstractVisitor {
RegisterArg reg = (RegisterArg) arg; RegisterArg reg = (RegisterArg) arg;
SSAVar sVar = reg.getSVar(); SSAVar sVar = reg.getSVar();
if (sVar != null) { if (sVar != null) {
sVar.add(AFlag.FINAL);
sVar.getCodeVar().setFinal(true); sVar.getCodeVar().setFinal(true);
sVar.add(AFlag.DONT_INLINE); sVar.add(AFlag.DONT_INLINE);
} }
......
...@@ -36,6 +36,9 @@ public class PrepareForCodeGen extends AbstractVisitor { ...@@ -36,6 +36,9 @@ public class PrepareForCodeGen extends AbstractVisitor {
return; return;
} }
for (BlockNode block : blocks) { for (BlockNode block : blocks) {
if (block.contains(AFlag.DONT_GENERATE)) {
continue;
}
removeInstructions(block); removeInstructions(block);
checkInline(block); checkInline(block);
// removeParenthesis(block); // removeParenthesis(block);
......
...@@ -4,13 +4,21 @@ import java.util.ArrayList; ...@@ -4,13 +4,21 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import jadx.core.dex.instructions.*;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import jadx.core.Consts; import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo; import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ArithOp;
import jadx.core.dex.instructions.CallMthInterface;
import jadx.core.dex.instructions.ConstStringNode;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.FieldArg; import jadx.core.dex.instructions.args.FieldArg;
import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnArg;
...@@ -44,6 +52,9 @@ public class SimplifyVisitor extends AbstractVisitor { ...@@ -44,6 +52,9 @@ public class SimplifyVisitor extends AbstractVisitor {
} }
private static InsnNode simplifyInsn(MethodNode mth, InsnNode insn) { private static InsnNode simplifyInsn(MethodNode mth, InsnNode insn) {
if (insn.contains(AFlag.DONT_GENERATE)) {
return null;
}
for (InsnArg arg : insn.getArguments()) { for (InsnArg arg : insn.getArguments()) {
if (arg.isInsnWrap()) { if (arg.isInsnWrap()) {
InsnNode ni = simplifyInsn(mth, ((InsnWrapArg) arg).getWrapInsn()); InsnNode ni = simplifyInsn(mth, ((InsnWrapArg) arg).getWrapInsn());
......
...@@ -49,19 +49,17 @@ public class BlockExceptionHandler extends AbstractVisitor { ...@@ -49,19 +49,17 @@ public class BlockExceptionHandler extends AbstractVisitor {
} }
ExceptionHandler excHandler = handlerAttr.getHandler(); ExceptionHandler excHandler = handlerAttr.getHandler();
ArgType argType = excHandler.getArgType(); ArgType argType = excHandler.getArgType();
if (!block.getInstructions().isEmpty()) { InsnNode me = BlockUtils.getLastInsn(block);
InsnNode me = block.getInstructions().get(0); if (me != null && me.getType() == InsnType.MOVE_EXCEPTION) {
if (me.getType() == InsnType.MOVE_EXCEPTION) { // set correct type for 'move-exception' operation
// set correct type for 'move-exception' operation RegisterArg resArg = InsnArg.reg(me.getResult().getRegNum(), argType);
RegisterArg resArg = InsnArg.reg(me.getResult().getRegNum(), argType); resArg.copyAttributesFrom(me);
resArg.copyAttributesFrom(me); me.setResult(resArg);
me.setResult(resArg); me.add(AFlag.DONT_INLINE);
me.add(AFlag.DONT_INLINE); resArg.add(AFlag.CUSTOM_DECLARE);
resArg.add(AFlag.CUSTOM_DECLARE); excHandler.setArg(resArg);
excHandler.setArg(resArg); me.addAttr(handlerAttr);
me.addAttr(handlerAttr); return;
return;
}
} }
// handler arguments not used // handler arguments not used
excHandler.setArg(new NamedArg("unused", argType)); excHandler.setArg(new NamedArg("unused", argType));
......
...@@ -560,11 +560,8 @@ public class BlockProcessor extends AbstractVisitor { ...@@ -560,11 +560,8 @@ public class BlockProcessor extends AbstractVisitor {
} }
private static RegisterArg getMoveExceptionRegister(BlockNode block) { private static RegisterArg getMoveExceptionRegister(BlockNode block) {
if (block.getInstructions().isEmpty()) { InsnNode insn = BlockUtils.getLastInsn(block);
return null; if (insn == null || insn.getType() != InsnType.MOVE_EXCEPTION) {
}
InsnNode insn = block.getInstructions().get(0);
if (insn.getType() != InsnType.MOVE_EXCEPTION) {
return null; return null;
} }
return insn.getResult(); return insn.getResult();
......
package jadx.core.dex.visitors.blocksmaker.helpers;
import jadx.core.dex.nodes.BlockNode;
public final class BlocksPair {
private final BlockNode first;
private final BlockNode second;
public BlocksPair(BlockNode first, BlockNode second) {
this.first = first;
this.second = second;
}
public BlockNode getFirst() {
return first;
}
public BlockNode getSecond() {
return second;
}
@Override
public int hashCode() {
return 31 * first.hashCode() + second.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof BlocksPair)) {
return false;
}
BlocksPair other = (BlocksPair) o;
return first.equals(other.first) && second.equals(other.second);
}
@Override
public String toString() {
return "(" + first + ", " + second + ")";
}
}
package jadx.core.dex.visitors.blocksmaker.helpers;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
public final class BlocksRemoveInfo {
private final Set<BlocksPair> processed = new HashSet<>();
private final Set<BlocksPair> outs = new HashSet<>();
private final Map<RegisterArg, RegisterArg> regMap = new HashMap<>();
private BlocksPair start;
private BlocksPair end;
private int startSplitIndex;
private int endSplitIndex;
private BlockNode startPredecessor;
private boolean applied;
public BlocksRemoveInfo(BlocksPair start) {
this.start = start;
}
public Set<BlocksPair> getProcessed() {
return processed;
}
public Set<BlocksPair> getOuts() {
return outs;
}
public BlocksPair getStart() {
return start;
}
public void setStart(BlocksPair start) {
this.start = start;
}
public BlocksPair getEnd() {
return end;
}
public void setEnd(BlocksPair end) {
this.end = end;
}
public int getStartSplitIndex() {
return startSplitIndex;
}
public void setStartSplitIndex(int startSplitIndex) {
this.startSplitIndex = startSplitIndex;
}
public int getEndSplitIndex() {
return endSplitIndex;
}
public void setEndSplitIndex(int endSplitIndex) {
this.endSplitIndex = endSplitIndex;
}
public void setStartPredecessor(BlockNode startPredecessor) {
this.startPredecessor = startPredecessor;
}
public BlockNode getStartPredecessor() {
return startPredecessor;
}
public Map<RegisterArg, RegisterArg> getRegMap() {
return regMap;
}
@Nullable
public BlockNode getByFirst(BlockNode first) {
for (BlocksPair blocksPair : processed) {
if (blocksPair.getFirst() == first) {
return blocksPair.getSecond();
}
}
return null;
}
@Nullable
public BlockNode getBySecond(BlockNode second) {
for (BlocksPair blocksPair : processed) {
if (blocksPair.getSecond() == second) {
return blocksPair.getSecond();
}
}
return null;
}
public boolean isApplied() {
return applied;
}
public void setApplied(boolean applied) {
this.applied = applied;
}
@Override
public String toString() {
return "BRI{start: " + start
+ ", end: " + end
+ ", processed: " + processed
+ ", outs: " + outs
+ ", regMap: " + regMap
+ ", split: " + startSplitIndex + "-" + endSplitIndex
+ "}";
}
}
package jadx.core.dex.visitors.blocksmaker.helpers;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.trycatch.ExceptionHandler;
public class FinallyExtractInfo {
private final ExceptionHandler finallyHandler;
private final List<BlockNode> allHandlerBlocks;
private final List<InsnsSlice> duplicateSlices = new ArrayList<>();
private final Set<BlockNode> checkedBlocks = new HashSet<>();
private final InsnsSlice finallyInsnsSlice = new InsnsSlice();
private final BlockNode startBlock;
public FinallyExtractInfo(ExceptionHandler finallyHandler, BlockNode startBlock, List<BlockNode> allHandlerBlocks) {
this.finallyHandler = finallyHandler;
this.startBlock = startBlock;
this.allHandlerBlocks = allHandlerBlocks;
}
public ExceptionHandler getFinallyHandler() {
return finallyHandler;
}
public List<BlockNode> getAllHandlerBlocks() {
return allHandlerBlocks;
}
public InsnsSlice getFinallyInsnsSlice() {
return finallyInsnsSlice;
}
public List<InsnsSlice> getDuplicateSlices() {
return duplicateSlices;
}
public Set<BlockNode> getCheckedBlocks() {
return checkedBlocks;
}
public BlockNode getStartBlock() {
return startBlock;
}
}
package jadx.core.dex.visitors.blocksmaker.helpers;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
public class InsnsSlice {
private final List<InsnNode> insnsList = new ArrayList<>();
private final Map<InsnNode, BlockNode> insnMap = new IdentityHashMap<>();
private boolean complete;
public void addInsn(InsnNode insn, BlockNode block) {
insnsList.add(insn);
insnMap.put(insn, block);
}
public void addBlock(BlockNode block) {
for (InsnNode insn : block.getInstructions()) {
addInsn(insn, block);
}
}
public void addInsns(BlockNode block, int startIndex, int endIndex) {
List<InsnNode> insns = block.getInstructions();
for (int i = startIndex; i < endIndex; i++) {
addInsn(insns.get(i), block);
}
}
@Nullable
public BlockNode getBlock(InsnNode insn) {
return insnMap.get(insn);
}
public List<InsnNode> getInsnsList() {
return insnsList;
}
public Set<BlockNode> getBlocks() {
Set<BlockNode> set = new LinkedHashSet<>();
for (InsnNode insn : insnsList) {
set.add(insnMap.get(insn));
}
return set;
}
public boolean isComplete() {
return complete;
}
public void setComplete(boolean complete) {
this.complete = complete;
}
@Override
public String toString() {
return "{["
+ insnsList.stream().map(insn -> insn.getType().toString()).collect(Collectors.joining(", "))
+ "]"
+ (complete ? " complete" : "")
+ "}";
}
}
...@@ -26,7 +26,6 @@ import jadx.core.dex.nodes.InsnNode; ...@@ -26,7 +26,6 @@ import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.JadxVisitor; import jadx.core.dex.visitors.JadxVisitor;
import jadx.core.dex.visitors.ssa.EliminatePhiNodes;
import jadx.core.dex.visitors.ssa.SSATransform; import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor; import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
import jadx.core.dex.visitors.typeinference.TypeUpdateResult; import jadx.core.dex.visitors.typeinference.TypeUpdateResult;
...@@ -39,8 +38,7 @@ import jadx.core.utils.exceptions.JadxException; ...@@ -39,8 +38,7 @@ import jadx.core.utils.exceptions.JadxException;
desc = "Apply debug info to registers (type and names)", desc = "Apply debug info to registers (type and names)",
runAfter = { runAfter = {
SSATransform.class, SSATransform.class,
TypeInferenceVisitor.class, TypeInferenceVisitor.class
EliminatePhiNodes.class
} }
) )
public class DebugInfoApplyVisitor extends AbstractVisitor { public class DebugInfoApplyVisitor extends AbstractVisitor {
......
...@@ -59,6 +59,7 @@ public class CheckRegions extends AbstractVisitor { ...@@ -59,6 +59,7 @@ public class CheckRegions extends AbstractVisitor {
if (!blocksInRegions.contains(block) if (!blocksInRegions.contains(block)
&& !block.getInstructions().isEmpty() && !block.getInstructions().isEmpty()
&& !block.contains(AFlag.ADDED_TO_REGION) && !block.contains(AFlag.ADDED_TO_REGION)
&& !block.contains(AFlag.DONT_GENERATE)
&& !block.contains(AFlag.REMOVE)) { && !block.contains(AFlag.REMOVE)) {
String blockCode = getBlockInsnStr(mth, block); String blockCode = getBlockInsnStr(mth, block);
mth.addWarn("Missing block: " + block + ", code skipped:" + CodeWriter.NL + blockCode); mth.addWarn("Missing block: " + block + ", code skipped:" + CodeWriter.NL + blockCode);
......
package jadx.core.dex.visitors.regions; package jadx.core.dex.visitors.regions;
import java.util.List;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion; import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.Region; import jadx.core.dex.regions.Region;
import jadx.core.dex.visitors.AbstractVisitor;
public class CleanRegions extends AbstractVisitor {
private static final IRegionVisitor REMOVE_REGION_VISITOR = new RemoveRegionVisitor();
public class CleanRegions { @Override
public void visit(MethodNode mth) {
process(mth);
}
public static void process(MethodNode mth) { public static void process(MethodNode mth) {
if (mth.isNoCode() || mth.getBasicBlocks().isEmpty()) { if (mth.isNoCode() || mth.getBasicBlocks().isEmpty()) {
return; return;
} }
IRegionVisitor removeEmptyBlocks = new AbstractRegionVisitor() { DepthRegionTraversal.traverse(mth, REMOVE_REGION_VISITOR);
@Override }
public boolean enterRegion(MethodNode mth, IRegion region) {
if (region instanceof Region) { private static class RemoveRegionVisitor extends AbstractRegionVisitor {
region.getSubBlocks().removeIf(container -> { @Override
if (container instanceof BlockNode) { public boolean enterRegion(MethodNode mth, IRegion region) {
BlockNode block = (BlockNode) container; if (region instanceof Region) {
return block.getInstructions().isEmpty(); region.getSubBlocks().removeIf(RemoveRegionVisitor::canRemoveRegion);
} }
return true;
}
private static boolean canRemoveRegion(IContainer container) {
if (container.contains(AFlag.DONT_GENERATE)) {
return true;
}
if (container instanceof BlockNode) {
BlockNode block = (BlockNode) container;
return block.getInstructions().isEmpty();
}
if (container instanceof IRegion) {
List<IContainer> subBlocks = ((IRegion) container).getSubBlocks();
for (IContainer subBlock : subBlocks) {
if (!canRemoveRegion(subBlock)) {
return false; return false;
}); }
} }
return true; return true;
} }
}; return false;
DepthRegionTraversal.traverse(mth, removeEmptyBlocks); }
}
private CleanRegions() {
} }
} }
...@@ -36,7 +36,10 @@ public class IfMakerHelper { ...@@ -36,7 +36,10 @@ public class IfMakerHelper {
} }
static IfInfo makeIfInfo(BlockNode ifBlock) { static IfInfo makeIfInfo(BlockNode ifBlock) {
IfNode ifNode = (IfNode) ifBlock.getInstructions().get(0); IfNode ifNode = (IfNode) BlockUtils.getLastInsn(ifBlock);
if (ifNode == null) {
throw new JadxRuntimeException("Empty IF block: " + ifBlock);
}
IfCondition condition = IfCondition.fromIfNode(ifNode); IfCondition condition = IfCondition.fromIfNode(ifNode);
IfInfo info = new IfInfo(condition, ifNode.getThenBlock(), ifNode.getElseBlock()); IfInfo info = new IfInfo(condition, ifNode.getThenBlock(), ifNode.getElseBlock());
info.setIfBlock(ifBlock); info.setIfBlock(ifBlock);
...@@ -328,8 +331,8 @@ public class IfMakerHelper { ...@@ -328,8 +331,8 @@ public class IfMakerHelper {
if (block == null || block.contains(AType.LOOP) || block.contains(AFlag.ADDED_TO_REGION)) { if (block == null || block.contains(AType.LOOP) || block.contains(AFlag.ADDED_TO_REGION)) {
return null; return null;
} }
List<InsnNode> insns = block.getInstructions(); InsnNode lastInsn = BlockUtils.getLastInsn(block);
if (insns.size() == 1 && insns.get(0).getType() == InsnType.IF) { if (lastInsn != null && lastInsn.getType() == InsnType.IF) {
return block; return block;
} }
// skip this block and search in successors chain // skip this block and search in successors chain
...@@ -342,6 +345,7 @@ public class IfMakerHelper { ...@@ -342,6 +345,7 @@ public class IfMakerHelper {
if (next.getPredecessors().size() != 1) { if (next.getPredecessors().size() != 1) {
return null; return null;
} }
List<InsnNode> insns = block.getInstructions();
boolean pass = true; boolean pass = true;
if (!insns.isEmpty()) { if (!insns.isEmpty()) {
// check that all instructions can be inlined // check that all instructions can be inlined
......
...@@ -197,7 +197,6 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor ...@@ -197,7 +197,6 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
} }
// array for each loop confirmed // array for each loop confirmed
len.add(AFlag.DONT_GENERATE);
incrInsn.getResult().add(AFlag.DONT_GENERATE); incrInsn.getResult().add(AFlag.DONT_GENERATE);
condArg.add(AFlag.DONT_GENERATE); condArg.add(AFlag.DONT_GENERATE);
bCondArg.add(AFlag.DONT_GENERATE); bCondArg.add(AFlag.DONT_GENERATE);
...@@ -208,6 +207,8 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor ...@@ -208,6 +207,8 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
((RegisterArg) arrayArg).getSVar().removeUse((RegisterArg) arrGetInsn.getArg(0)); ((RegisterArg) arrayArg).getSVar().removeUse((RegisterArg) arrGetInsn.getArg(0));
} }
CodeShrinker.shrinkMethod(mth); CodeShrinker.shrinkMethod(mth);
len.add(AFlag.DONT_GENERATE);
if (arrGetInsn.contains(AFlag.WRAPPED)) { if (arrGetInsn.contains(AFlag.WRAPPED)) {
InsnArg wrapArg = BlockUtils.searchWrappedInsnParent(mth, arrGetInsn); InsnArg wrapArg = BlockUtils.searchWrappedInsnParent(mth, arrGetInsn);
if (wrapArg != null && wrapArg.getParentInsn() != null) { if (wrapArg != null && wrapArg.getParentInsn() != null) {
......
...@@ -7,9 +7,6 @@ import java.util.List; ...@@ -7,9 +7,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.AType;
import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IBranchRegion; import jadx.core.dex.nodes.IBranchRegion;
...@@ -33,8 +30,6 @@ import jadx.core.utils.RegionUtils; ...@@ -33,8 +30,6 @@ import jadx.core.utils.RegionUtils;
*/ */
public class ProcessTryCatchRegions extends AbstractRegionVisitor { public class ProcessTryCatchRegions extends AbstractRegionVisitor {
private static final Logger LOG = LoggerFactory.getLogger(ProcessTryCatchRegions.class);
public static void process(MethodNode mth) { public static void process(MethodNode mth) {
if (mth.isNoCode() || mth.isNoExceptionHandlers()) { if (mth.isNoCode() || mth.isNoExceptionHandlers()) {
return; return;
......
...@@ -115,8 +115,8 @@ public class RegionMaker { ...@@ -115,8 +115,8 @@ public class RegionMaker {
} }
} }
if (!processed && block.getInstructions().size() == 1) { InsnNode insn = BlockUtils.getLastInsn(block);
InsnNode insn = block.getInstructions().get(0); if (!processed && insn != null) {
switch (insn.getType()) { switch (insn.getType()) {
case IF: case IF:
next = processIf(r, block, (IfNode) insn, stack); next = processIf(r, block, (IfNode) insn, stack);
...@@ -247,9 +247,11 @@ public class RegionMaker { ...@@ -247,9 +247,11 @@ public class RegionMaker {
*/ */
private LoopRegion makeLoopRegion(IRegion curRegion, LoopInfo loop, List<BlockNode> exitBlocks) { private LoopRegion makeLoopRegion(IRegion curRegion, LoopInfo loop, List<BlockNode> exitBlocks) {
for (BlockNode block : exitBlocks) { for (BlockNode block : exitBlocks) {
if (block.contains(AType.EXC_HANDLER) if (block.contains(AType.EXC_HANDLER)) {
|| block.getInstructions().size() != 1 continue;
|| block.getInstructions().get(0).getType() != InsnType.IF) { }
InsnNode lastInsn = BlockUtils.getLastInsn(block);
if (lastInsn == null || lastInsn.getType() != InsnType.IF) {
continue; continue;
} }
List<LoopInfo> loops = block.getAll(AType.LOOP); List<LoopInfo> loops = block.getAll(AType.LOOP);
...@@ -533,6 +535,7 @@ public class RegionMaker { ...@@ -533,6 +535,7 @@ public class RegionMaker {
insnBlock.add(AFlag.DONT_GENERATE); insnBlock.add(AFlag.DONT_GENERATE);
} }
exitInsn.add(AFlag.DONT_GENERATE); exitInsn.add(AFlag.DONT_GENERATE);
exitInsn.add(AFlag.REMOVE);
InstructionRemover.unbindInsn(mth, exitInsn); InstructionRemover.unbindInsn(mth, exitInsn);
} }
......
...@@ -54,7 +54,6 @@ public class RegionMakerVisitor extends AbstractVisitor { ...@@ -54,7 +54,6 @@ public class RegionMakerVisitor extends AbstractVisitor {
mth.getRegion().add(expOutBlock); mth.getRegion().add(expOutBlock);
} }
} }
postProcessRegions(mth); postProcessRegions(mth);
} }
...@@ -85,75 +84,76 @@ public class RegionMakerVisitor extends AbstractVisitor { ...@@ -85,75 +84,76 @@ public class RegionMakerVisitor extends AbstractVisitor {
insertEdgeInsn((Region) region); insertEdgeInsn((Region) region);
} }
} }
}
/** /**
* Insert insn block from edge insn attribute. * Insert insn block from edge insn attribute.
*/ */
private static void insertEdgeInsn(Region region) { private static void insertEdgeInsn(Region region) {
List<IContainer> subBlocks = region.getSubBlocks(); List<IContainer> subBlocks = region.getSubBlocks();
if (subBlocks.isEmpty()) { if (subBlocks.isEmpty()) {
return; return;
}
IContainer last = subBlocks.get(subBlocks.size() - 1);
List<EdgeInsnAttr> edgeInsnAttrs = last.getAll(AType.EDGE_INSN);
if (edgeInsnAttrs.isEmpty()) {
return;
}
EdgeInsnAttr insnAttr = edgeInsnAttrs.get(0);
if (!insnAttr.getStart().equals(last)) {
return;
}
List<InsnNode> insns = Collections.singletonList(insnAttr.getInsn());
region.add(new InsnContainer(insns));
}
private static void processSwitch(MethodNode mth, SwitchRegion sw) {
for (IContainer c : sw.getBranches()) {
if (!(c instanceof Region)) {
continue;
} }
Set<IBlock> blocks = new HashSet<>(); IContainer last = subBlocks.get(subBlocks.size() - 1);
RegionUtils.getAllRegionBlocks(c, blocks); List<EdgeInsnAttr> edgeInsnAttrs = last.getAll(AType.EDGE_INSN);
if (blocks.isEmpty()) { if (edgeInsnAttrs.isEmpty()) {
addBreakToContainer((Region) c); return;
continue;
} }
for (IBlock block : blocks) { EdgeInsnAttr insnAttr = edgeInsnAttrs.get(0);
if (!(block instanceof BlockNode)) { if (!insnAttr.getStart().equals(last)) {
continue; return;
} }
BlockNode bn = (BlockNode) block; List<InsnNode> insns = Collections.singletonList(insnAttr.getInsn());
for (BlockNode s : bn.getCleanSuccessors()) { region.add(new InsnContainer(insns));
if (!blocks.contains(s) }
&& !bn.contains(AFlag.ADDED_TO_REGION)
&& !s.contains(AFlag.FALL_THROUGH)) { private static void processSwitch(MethodNode mth, SwitchRegion sw) {
addBreak(mth, c, bn); for (IContainer c : sw.getBranches()) {
break; if (c instanceof Region) {
Set<IBlock> blocks = new HashSet<>();
RegionUtils.getAllRegionBlocks(c, blocks);
if (blocks.isEmpty()) {
addBreakToContainer((Region) c);
} else {
for (IBlock block : blocks) {
if (block instanceof BlockNode) {
addBreakForBlock(mth, c, blocks, (BlockNode) block);
}
}
} }
} }
} }
} }
}
private static void addBreak(MethodNode mth, IContainer c, BlockNode bn) { private static void addBreakToContainer(Region c) {
IContainer blockContainer = RegionUtils.getBlockContainer(c, bn); if (RegionUtils.hasExitEdge(c)) {
if (blockContainer instanceof Region) { return;
addBreakToContainer((Region) blockContainer); }
} else if (c instanceof Region) { List<InsnNode> insns = new ArrayList<>(1);
addBreakToContainer((Region) c); insns.add(new InsnNode(InsnType.BREAK, 0));
} else { c.add(new InsnContainer(insns));
LOG.warn("Can't insert break, container: {}, block: {}, mth: {}", blockContainer, bn, mth);
} }
}
private static void addBreakToContainer(Region c) { private static void addBreakForBlock(MethodNode mth, IContainer c, Set<IBlock> blocks, BlockNode bn) {
if (RegionUtils.hasExitEdge(c)) { for (BlockNode s : bn.getCleanSuccessors()) {
return; if (!blocks.contains(s)
&& !bn.contains(AFlag.ADDED_TO_REGION)
&& !s.contains(AFlag.FALL_THROUGH)) {
addBreak(mth, c, bn);
return;
}
}
}
private static void addBreak(MethodNode mth, IContainer c, BlockNode bn) {
IContainer blockContainer = RegionUtils.getBlockContainer(c, bn);
if (blockContainer instanceof Region) {
addBreakToContainer((Region) blockContainer);
} else if (c instanceof Region) {
addBreakToContainer((Region) c);
} else {
LOG.warn("Can't insert break, container: {}, block: {}, mth: {}", blockContainer, bn, mth);
}
} }
List<InsnNode> insns = new ArrayList<>(1);
insns.add(new InsnNode(InsnType.BREAK, 0));
c.add(new InsnContainer(insns));
} }
private static void removeSynchronized(MethodNode mth) { private static void removeSynchronized(MethodNode mth) {
......
...@@ -43,6 +43,10 @@ public class SSATransform extends AbstractVisitor { ...@@ -43,6 +43,10 @@ public class SSATransform extends AbstractVisitor {
} }
private static void process(MethodNode mth) { private static void process(MethodNode mth) {
if (!mth.getSVars().isEmpty()) {
return;
}
LiveVarAnalysis la = new LiveVarAnalysis(mth); LiveVarAnalysis la = new LiveVarAnalysis(mth);
la.runAnalysis(); la.runAnalysis();
int regsCount = mth.getRegsCount(); int regsCount = mth.getRegsCount();
...@@ -63,6 +67,8 @@ public class SSATransform extends AbstractVisitor { ...@@ -63,6 +67,8 @@ public class SSATransform extends AbstractVisitor {
throw new JadxRuntimeException("Phi nodes fix limit reached!"); throw new JadxRuntimeException("Phi nodes fix limit reached!");
} }
} while (repeatFix); } while (repeatFix);
hidePhiInsns(mth);
} }
private static void placePhi(MethodNode mth, int regNum, LiveVarAnalysis la) { private static void placePhi(MethodNode mth, int regNum, LiveVarAnalysis la) {
...@@ -117,9 +123,6 @@ public class SSATransform extends AbstractVisitor { ...@@ -117,9 +123,6 @@ public class SSATransform extends AbstractVisitor {
} }
private static void renameVariables(MethodNode mth) { private static void renameVariables(MethodNode mth) {
if (!mth.getSVars().isEmpty()) {
throw new JadxRuntimeException("SSA rename variables already executed");
}
int regsCount = mth.getRegsCount(); int regsCount = mth.getRegsCount();
SSAVar[] vars = new SSAVar[regsCount]; SSAVar[] vars = new SSAVar[regsCount];
int[] versions = new int[regsCount]; int[] versions = new int[regsCount];
...@@ -432,4 +435,10 @@ public class SSATransform extends AbstractVisitor { ...@@ -432,4 +435,10 @@ public class SSATransform extends AbstractVisitor {
} }
} }
} }
private static void hidePhiInsns(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
block.getInstructions().removeIf(insn -> insn.getType() == InsnType.PHI);
}
}
} }
...@@ -114,6 +114,9 @@ public class TypeCompare { ...@@ -114,6 +114,9 @@ public class TypeCompare {
if (unknown == ArgType.UNKNOWN_OBJECT && (known.isObject() || known.isArray())) { if (unknown == ArgType.UNKNOWN_OBJECT && (known.isObject() || known.isArray())) {
return NARROW; return NARROW;
} }
if (known.equals(ArgType.OBJECT) && unknown.isArray()) {
return WIDER;
}
PrimitiveType knownPrimitive; PrimitiveType knownPrimitive;
if (known.isPrimitive()) { if (known.isPrimitive()) {
knownPrimitive = known.getPrimitiveType(); knownPrimitive = known.getPrimitiveType();
......
...@@ -33,6 +33,7 @@ import jadx.core.dex.nodes.RootNode; ...@@ -33,6 +33,7 @@ import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.trycatch.ExcHandlerAttr; import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.ConstInlineVisitor; import jadx.core.dex.visitors.ConstInlineVisitor;
import jadx.core.dex.visitors.InitCodeVariables;
import jadx.core.dex.visitors.JadxVisitor; import jadx.core.dex.visitors.JadxVisitor;
import jadx.core.dex.visitors.ssa.SSATransform; import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.utils.Utils; import jadx.core.utils.Utils;
...@@ -131,7 +132,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor { ...@@ -131,7 +132,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
if (Consts.DEBUG) { if (Consts.DEBUG) {
if (ssaVar.getTypeInfo().getType().equals(candidateType)) { if (ssaVar.getTypeInfo().getType().equals(candidateType)) {
LOG.info("Same type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds); LOG.info("Same type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds);
} else { } else if (candidateType.isTypeKnown()) {
LOG.debug("Type set rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds); LOG.debug("Type set rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds);
} }
} }
...@@ -311,6 +312,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor { ...@@ -311,6 +312,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
for (InsnArg phiArg : phiInsn.getArguments()) { for (InsnArg phiArg : phiInsn.getArguments()) {
mergePhiBounds(((RegisterArg) phiArg).getSVar()); mergePhiBounds(((RegisterArg) phiArg).getSVar());
} }
InitCodeVariables.initCodeVar(newSsaVar);
return true; return true;
} }
} }
......
...@@ -246,7 +246,6 @@ public final class TypeUpdate { ...@@ -246,7 +246,6 @@ public final class TypeUpdate {
registry.put(InsnType.CONST, this::sameFirstArgListener); registry.put(InsnType.CONST, this::sameFirstArgListener);
registry.put(InsnType.MOVE, this::moveListener); registry.put(InsnType.MOVE, this::moveListener);
registry.put(InsnType.PHI, this::allSameListener); registry.put(InsnType.PHI, this::allSameListener);
registry.put(InsnType.MERGE, this::allSameListener);
registry.put(InsnType.AGET, this::arrayGetListener); registry.put(InsnType.AGET, this::arrayGetListener);
registry.put(InsnType.APUT, this::arrayPutListener); registry.put(InsnType.APUT, this::arrayPutListener);
registry.put(InsnType.IF, this::ifListener); registry.put(InsnType.IF, this::ifListener);
......
...@@ -107,9 +107,9 @@ public class DebugUtils { ...@@ -107,9 +107,9 @@ public class DebugUtils {
CodeWriter code = new CodeWriter(); CodeWriter code = new CodeWriter();
ig.makeInsn(insn, code); ig.makeInsn(insn, code);
String insnStr = code.toString().substring(CodeWriter.NL.length()); String insnStr = code.toString().substring(CodeWriter.NL.length());
LOG.debug("{}> {}\t{}", indent, insnStr, insn.getAttributesString()); LOG.debug("{}|> {}\t{}", indent, insnStr, insn.getAttributesString());
} catch (CodegenException e) { } catch (CodegenException e) {
LOG.debug("{}>!! {}", indent, insn); LOG.debug("{}|>!! {}", indent, insn);
} }
} }
} }
......
...@@ -16,6 +16,7 @@ import static jadx.core.dex.instructions.args.ArgType.NARROW; ...@@ -16,6 +16,7 @@ import static jadx.core.dex.instructions.args.ArgType.NARROW;
import static jadx.core.dex.instructions.args.ArgType.NARROW_INTEGRAL; import static jadx.core.dex.instructions.args.ArgType.NARROW_INTEGRAL;
import static jadx.core.dex.instructions.args.ArgType.OBJECT; import static jadx.core.dex.instructions.args.ArgType.OBJECT;
import static jadx.core.dex.instructions.args.ArgType.UNKNOWN; import static jadx.core.dex.instructions.args.ArgType.UNKNOWN;
import static jadx.core.dex.instructions.args.ArgType.UNKNOWN_ARRAY;
import static jadx.core.dex.instructions.args.ArgType.UNKNOWN_OBJECT; import static jadx.core.dex.instructions.args.ArgType.UNKNOWN_OBJECT;
import static jadx.core.dex.instructions.args.ArgType.array; import static jadx.core.dex.instructions.args.ArgType.array;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
...@@ -60,6 +61,8 @@ public class TypeCompareTest { ...@@ -60,6 +61,8 @@ public class TypeCompareTest {
firstIsNarrow(array(OBJECT), OBJECT); firstIsNarrow(array(OBJECT), OBJECT);
firstIsNarrow(array(OBJECT), array(UNKNOWN_OBJECT)); firstIsNarrow(array(OBJECT), array(UNKNOWN_OBJECT));
firstIsNarrow(UNKNOWN_ARRAY, OBJECT);
} }
@Test @Test
......
...@@ -17,7 +17,6 @@ import java.util.jar.JarOutputStream; ...@@ -17,7 +17,6 @@ import java.util.jar.JarOutputStream;
import jadx.api.JadxArgs; import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler; import jadx.api.JadxDecompiler;
import jadx.api.JadxInternalAccess; import jadx.api.JadxInternalAccess;
import jadx.core.Jadx;
import jadx.core.ProcessClass; import jadx.core.ProcessClass;
import jadx.core.codegen.CodeGen; import jadx.core.codegen.CodeGen;
import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AFlag;
...@@ -29,7 +28,6 @@ import jadx.core.dex.nodes.MethodNode; ...@@ -29,7 +28,6 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.DepthTraversal; import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor; import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.exceptions.JadxException;
import jadx.core.xmlgen.ResourceStorage; import jadx.core.xmlgen.ResourceStorage;
import jadx.core.xmlgen.entry.ResourceEntry; import jadx.core.xmlgen.entry.ResourceEntry;
import jadx.tests.api.compiler.DynamicCompiler; import jadx.tests.api.compiler.DynamicCompiler;
...@@ -141,34 +139,19 @@ public abstract class IntegrationTest extends TestUtils { ...@@ -141,34 +139,19 @@ public abstract class IntegrationTest extends TestUtils {
} }
protected void decompile(JadxDecompiler jadx, ClassNode cls) { protected void decompile(JadxDecompiler jadx, ClassNode cls) {
List<IDexTreeVisitor> passes = getPassesList(jadx); List<IDexTreeVisitor> passes = JadxInternalAccess.getPassList(jadx);
ProcessClass.process(cls, passes, new CodeGen()); ProcessClass.process(cls, passes, new CodeGen());
} }
protected void decompileWithoutUnload(JadxDecompiler jadx, ClassNode cls) { protected void decompileWithoutUnload(JadxDecompiler jadx, ClassNode cls) {
cls.load(); cls.load();
List<IDexTreeVisitor> passes = getPassesList(jadx); for (IDexTreeVisitor visitor : JadxInternalAccess.getPassList(jadx)) {
for (IDexTreeVisitor visitor : passes) {
DepthTraversal.visit(visitor, cls); DepthTraversal.visit(visitor, cls);
} }
generateClsCode(cls); generateClsCode(cls);
// don't unload class // don't unload class
} }
private List<IDexTreeVisitor> getPassesList(JadxDecompiler jadx) {
RootNode root = JadxInternalAccess.getRoot(jadx);
List<IDexTreeVisitor> passesList = Jadx.getPassesList(jadx.getArgs());
passesList.forEach(pass -> {
try {
pass.init(root);
} catch (JadxException e) {
e.printStackTrace();
fail(e.getMessage());
}
});
return passesList;
}
protected void generateClsCode(ClassNode cls) { protected void generateClsCode(ClassNode cls) {
try { try {
new CodeGen().visit(cls); new CodeGen().visit(cls);
...@@ -442,6 +425,11 @@ public abstract class IntegrationTest extends TestUtils { ...@@ -442,6 +425,11 @@ public abstract class IntegrationTest extends TestUtils {
protected void setOutputCFG() { protected void setOutputCFG() {
this.args.setCfgOutput(true); this.args.setCfgOutput(true);
this.args.setRawCFGOutput(true); this.args.setRawCFGOutput(true);
} // Use only for debug purpose
@Deprecated
protected void setOutputRawCFG() {
this.args.setRawCFGOutput(true);
} }
// Use only for debug purpose // Use only for debug purpose
......
...@@ -4,6 +4,10 @@ import jadx.core.codegen.CodeWriter; ...@@ -4,6 +4,10 @@ import jadx.core.codegen.CodeWriter;
public class TestUtils { public class TestUtils {
public static String indent() {
return CodeWriter.INDENT_STR;
}
public static String indent(int indent) { public static String indent(int indent) {
if (indent == 1) { if (indent == 1) {
return CodeWriter.INDENT_STR; return CodeWriter.INDENT_STR;
...@@ -24,5 +28,4 @@ public class TestUtils { ...@@ -24,5 +28,4 @@ public class TestUtils {
} }
return count; return count;
} }
} }
...@@ -32,8 +32,8 @@ public class TestFieldIncrement2 extends IntegrationTest { ...@@ -32,8 +32,8 @@ public class TestFieldIncrement2 extends IntegrationTest {
String code = cls.getCode().toString(); String code = cls.getCode().toString();
assertThat(code, containsString("this.a.f += n;")); assertThat(code, containsString("this.a.f += n;"));
assertThat(code, containsString("a.f *= n;")); assertThat(code, containsString("a2.f *= n;"));
// TODO // TODO:
// assertThat(code, containsString("this.a.f *= n;")); // assertThat(code, containsString("this.a.f *= n;"));
} }
} }
...@@ -6,19 +6,24 @@ import jadx.core.dex.nodes.ClassNode; ...@@ -6,19 +6,24 @@ import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest; import jadx.tests.api.IntegrationTest;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class TestTernary2 extends IntegrationTest { public class TestTernary2 extends IntegrationTest {
public static class TestCls { public static class TestCls {
public void test() { public void test() {
assertTrue(f(1, 0) == 0); checkFalse(f(1, 0) == 0);
} }
private int f(int a, int b) { private int f(int a, int b) {
return a + b; return a + b;
} }
private void checkFalse(boolean b) {
if (b) {
throw new AssertionError("Must be false");
}
}
} }
@Test @Test
...@@ -26,9 +31,8 @@ public class TestTernary2 extends IntegrationTest { ...@@ -26,9 +31,8 @@ public class TestTernary2 extends IntegrationTest {
ClassNode cls = getClassNode(TestCls.class); ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString(); String code = cls.getCode().toString();
assertEquals(1, count(code, "assertTrue"));
assertEquals(1, count(code, "f(1, 0)")); assertEquals(1, count(code, "f(1, 0)"));
// TODO: // TODO:
// assertThat(code, containsString("assertTrue(f(1, 0) == 0);")); // assertThat(code, containsString("checkFalse(f(1, 0) == 0);"));
} }
} }
...@@ -24,8 +24,8 @@ public class TestEnums4 extends IntegrationTest { ...@@ -24,8 +24,8 @@ public class TestEnums4 extends IntegrationTest {
private final String[] exts; private final String[] exts;
private ResType(String... exts) { private ResType(String... extensions) {
this.exts = exts; this.exts = extensions;
} }
public String[] getExts() { public String[] getExts() {
...@@ -44,7 +44,7 @@ public class TestEnums4 extends IntegrationTest { ...@@ -44,7 +44,7 @@ public class TestEnums4 extends IntegrationTest {
String code = cls.getCode().toString(); String code = cls.getCode().toString();
assertThat(code, containsOne("CODE(\".dex\", \".class\"),")); assertThat(code, containsOne("CODE(\".dex\", \".class\"),"));
assertThat(code, containsOne("ResType(String... exts) {")); assertThat(code, containsOne("ResType(String... extensions) {"));
// assertThat(code, not(containsString("private ResType"))); // assertThat(code, not(containsString("private ResType")));
} }
} }
...@@ -19,9 +19,9 @@ public class TestGenerics2 extends IntegrationTest { ...@@ -19,9 +19,9 @@ public class TestGenerics2 extends IntegrationTest {
private static class ItemReference<V> extends WeakReference<V> { private static class ItemReference<V> extends WeakReference<V> {
private Object id; private Object id;
public ItemReference(V item, Object id, ReferenceQueue<? super V> queue) { public ItemReference(V item, Object objId, ReferenceQueue<? super V> queue) {
super(item, queue); super(item, queue);
this.id = id; this.id = objId;
} }
} }
...@@ -43,7 +43,7 @@ public class TestGenerics2 extends IntegrationTest { ...@@ -43,7 +43,7 @@ public class TestGenerics2 extends IntegrationTest {
ClassNode cls = getClassNode(TestCls.class); ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString(); String code = cls.getCode().toString();
assertThat(code, containsString("public ItemReference(V item, Object id, ReferenceQueue<? super V> queue) {")); assertThat(code, containsString("public ItemReference(V item, Object objId, ReferenceQueue<? super V> queue) {"));
assertThat(code, containsString("public V get(Object id) {")); assertThat(code, containsString("public V get(Object id) {"));
assertThat(code, containsString("WeakReference<V> ref = ")); assertThat(code, containsString("WeakReference<V> ref = "));
assertThat(code, containsString("return ref.get();")); assertThat(code, containsString("return ref.get();"));
......
package jadx.tests.integration.trycatch; package jadx.tests.integration.trycatch;
import java.io.IOException;
import org.junit.Test; import org.junit.Test;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.IntegrationTest; import jadx.tests.api.IntegrationTest;
import static jadx.tests.api.utils.JadxMatchers.containsOne; import static jadx.tests.api.utils.JadxMatchers.containsOne;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
public class TestFinallyExtract extends IntegrationTest { public class TestFinallyExtract extends IntegrationTest {
public static class TestCls { public static class TestCls {
private int result = 0;
public String test() throws IOException { public String test() {
boolean success = false; boolean success = false;
try { try {
String value = test(); String value = call();
result++;
success = true; success = true;
return value; return value;
} finally { } finally {
if (!success) { if (!success) {
test(); result -= 2;
} }
} }
} }
private String call() {
return "call";
}
public void check() {
test();
assertEquals(result, 1);
}
} }
@Test @Test
...@@ -33,10 +45,38 @@ public class TestFinallyExtract extends IntegrationTest { ...@@ -33,10 +45,38 @@ public class TestFinallyExtract extends IntegrationTest {
ClassNode cls = getClassNode(TestCls.class); ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString(); String code = cls.getCode().toString();
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("success = true;"));
assertThat(code, containsOne("return value;")); assertThat(code, containsOne("return value;"));
assertThat(code, containsOne("try {"));
assertThat(code, containsOne("} finally {")); assertThat(code, containsOne("} finally {"));
assertThat(code, containsOne("if (!success) {")); assertThat(code, containsOne("if (!success) {"));
} }
@Test
public void testNoDebug() {
noDebugInfo();
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
// java compiler optimization: 'success' variable completely removed and no code duplication:
/*
public String test() {
try {
String call = call();
this.result++;
return call;
} catch (Throwable th) {
this.result -= 2;
throw th;
}
}
*/
assertThat(code, containsOne("this.result++;"));
assertThat(code, containsOne("} catch (Throwable th) {"));
assertThat(code, containsOne("this.result -= 2;"));
assertThat(code, containsOne("throw th;"));
}
} }
...@@ -24,13 +24,23 @@ public class TestTryCatch7 extends IntegrationTest { ...@@ -24,13 +24,23 @@ public class TestTryCatch7 extends IntegrationTest {
} }
@Test @Test
public void test() {
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
check(code, "e", "ex");
}
@Test
public void testNoDebug() { public void testNoDebug() {
noDebugInfo(); noDebugInfo();
ClassNode cls = getClassNode(TestCls.class); ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString(); String code = cls.getCode().toString();
String excVarName = "exception"; check(code, "e", "e2");
String catchExcVarName = "e"; }
private void check(String code, String excVarName, String catchExcVarName) {
assertThat(code, containsOne("Exception " + excVarName + " = new Exception();")); assertThat(code, containsOne("Exception " + excVarName + " = new Exception();"));
assertThat(code, containsOne("} catch (Exception " + catchExcVarName + ") {")); assertThat(code, containsOne("} catch (Exception " + catchExcVarName + ") {"));
assertThat(code, containsOne(excVarName + " = " + catchExcVarName + ";")); assertThat(code, containsOne(excVarName + " = " + catchExcVarName + ";"));
......
...@@ -31,8 +31,8 @@ public class TestTryCatch8 extends IntegrationTest { ...@@ -31,8 +31,8 @@ public class TestTryCatch8 extends IntegrationTest {
synchronized (this) { synchronized (this) {
try { try {
throw new MyException(); throw new MyException();
} catch (MyException e) { } catch (MyException myExc) {
this.e = e; this.e = myExc;
} catch (Exception x) { } catch (Exception x) {
this.e = new MyException("MyExc", x); this.e = new MyException("MyExc", x);
} }
...@@ -54,9 +54,19 @@ public class TestTryCatch8 extends IntegrationTest { ...@@ -54,9 +54,19 @@ public class TestTryCatch8 extends IntegrationTest {
assertThat(code, containsOne("synchronized (this) {")); assertThat(code, containsOne("synchronized (this) {"));
assertThat(code, containsOne("throw new MyException();")); assertThat(code, containsOne("throw new MyException();"));
assertThat(code, containsOne("} catch (MyException e) {")); assertThat(code, containsOne("} catch (MyException myExc) {"));
assertThat(code, containsOne("this.e = e;")); assertThat(code, containsOne("this.e = myExc;"));
assertThat(code, containsOne("} catch (Exception x) {")); assertThat(code, containsOne("} catch (Exception x) {"));
assertThat(code, containsOne("this.e = new MyException(\"MyExc\", x);")); assertThat(code, containsOne("this.e = new MyException(\"MyExc\", x);"));
} }
@Test
public void testNoDebug() {
noDebugInfo();
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, containsOne("synchronized (this) {"));
assertThat(code, containsOne("throw new MyException();"));
}
} }
...@@ -18,6 +18,7 @@ public class TestTryCatchFinally6 extends IntegrationTest { ...@@ -18,6 +18,7 @@ public class TestTryCatchFinally6 extends IntegrationTest {
public static void test() throws IOException { public static void test() throws IOException {
InputStream is = null; InputStream is = null;
try { try {
call();
is = new FileInputStream("1.txt"); is = new FileInputStream("1.txt");
} finally { } finally {
if (is != null) { if (is != null) {
...@@ -25,6 +26,9 @@ public class TestTryCatchFinally6 extends IntegrationTest { ...@@ -25,6 +26,9 @@ public class TestTryCatchFinally6 extends IntegrationTest {
} }
} }
} }
private static void call() {
}
} }
@Test @Test
...@@ -35,6 +39,7 @@ public class TestTryCatchFinally6 extends IntegrationTest { ...@@ -35,6 +39,7 @@ public class TestTryCatchFinally6 extends IntegrationTest {
assertThat(code, containsLines(2, assertThat(code, containsLines(2,
"InputStream is = null;", "InputStream is = null;",
"try {", "try {",
indent(1) + "call();",
indent(1) + "is = new FileInputStream(\"1.txt\");", indent(1) + "is = new FileInputStream(\"1.txt\");",
"} finally {", "} finally {",
indent(1) + "if (is != null) {", indent(1) + "if (is != null) {",
...@@ -43,4 +48,23 @@ public class TestTryCatchFinally6 extends IntegrationTest { ...@@ -43,4 +48,23 @@ public class TestTryCatchFinally6 extends IntegrationTest {
"}" "}"
)); ));
} }
@Test
public void testNoDebug() {
noDebugInfo();
ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString();
assertThat(code, containsLines(2,
"FileInputStream fileInputStream = null;",
"try {",
indent() + "call();",
indent() + "fileInputStream = new FileInputStream(\"1.txt\");",
"} finally {",
indent() + "if (fileInputStream != null) {",
indent() + indent() + "fileInputStream.close();",
indent() + "}",
"}"
));
}
} }
...@@ -11,7 +11,7 @@ import static org.junit.Assert.assertEquals; ...@@ -11,7 +11,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
public class TestTryCatch3 extends IntegrationTest { public class TestTryCatchFinally7 extends IntegrationTest {
public static class TestCls { public static class TestCls {
private int f = 0; private int f = 0;
......
...@@ -4,6 +4,7 @@ import java.io.File; ...@@ -4,6 +4,7 @@ import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
...@@ -12,7 +13,7 @@ import jadx.tests.api.IntegrationTest; ...@@ -12,7 +13,7 @@ import jadx.tests.api.IntegrationTest;
import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
public class TestTryCatch5 extends IntegrationTest { public class TestTryCatchFinally8 extends IntegrationTest {
public static class TestCls { public static class TestCls {
private Object test(Object obj) { private Object test(Object obj) {
...@@ -41,9 +42,9 @@ public class TestTryCatch5 extends IntegrationTest { ...@@ -41,9 +42,9 @@ public class TestTryCatch5 extends IntegrationTest {
} }
} }
@Ignore("Fix merged catch blocks (shared code between catches)")
@Test @Test
public void test() { public void test() {
disableCompilation();
ClassNode cls = getClassNode(TestCls.class); ClassNode cls = getClassNode(TestCls.class);
String code = cls.getCode().toString(); String code = cls.getCode().toString();
......
...@@ -8,7 +8,7 @@ import jadx.tests.api.SmaliTest; ...@@ -8,7 +8,7 @@ import jadx.tests.api.SmaliTest;
import static jadx.tests.api.utils.JadxMatchers.containsOne; import static jadx.tests.api.utils.JadxMatchers.containsOne;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
public class TestTryCatchNoMove extends SmaliTest { public class TestTryCatchNoMoveExc extends SmaliTest {
// private static void test(AutoCloseable closeable) { // private static void test(AutoCloseable closeable) {
// if (closeable != null) { // if (closeable != null) {
...@@ -21,7 +21,7 @@ public class TestTryCatchNoMove extends SmaliTest { ...@@ -21,7 +21,7 @@ public class TestTryCatchNoMove extends SmaliTest {
@Test @Test
public void test() { public void test() {
ClassNode cls = getClassNodeFromSmaliWithPath("trycatch", "TestTryCatchNoMove"); ClassNode cls = getClassNodeFromSmaliWithPkg("trycatch", "TestTryCatchNoMoveExc");
String code = cls.getCode().toString(); String code = cls.getCode().toString();
assertThat(code, containsOne("if (autoCloseable != null) {")); assertThat(code, containsOne("if (autoCloseable != null) {"));
......
...@@ -26,7 +26,7 @@ public class TestTryCatchNoMoveExc2 extends SmaliTest { ...@@ -26,7 +26,7 @@ public class TestTryCatchNoMoveExc2 extends SmaliTest {
@Test @Test
public void test() { public void test() {
ClassNode cls = getClassNodeFromSmaliWithPath("trycatch", "TestTryCatchNoMoveExc2"); ClassNode cls = getClassNodeFromSmaliWithPkg("trycatch", "TestTryCatchNoMoveExc2");
String code = cls.getCode().toString(); String code = cls.getCode().toString();
assertThat(code, containsOne("try {")); assertThat(code, containsOne("try {"));
......
.class public LTestTryCatchNoMove; .class public Ltrycatch/TestTryCatchNoMoveExc;
.super Ljava/lang/Object; .super Ljava/lang/Object;
.method private static test(Ljava/lang/AutoCloseable;)V .method private static test(Ljava/lang/AutoCloseable;)V
......
.class public LTestTryCatchNoMoveExc2; .class public Ltrycatch/TestTryCatchNoMoveExc2;
.super Ljava/lang/Object; .super Ljava/lang/Object;
.method private static test(Ljava/lang/AutoCloseable;)V .method private static test(Ljava/lang/AutoCloseable;)V
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment