feat: new implementation for type inference approach and variable declaration
BREAKING CHANGE: some parts of jadx was rewritten from scratch - type inference - variable declaration - `finish` block extraction
This commit is contained in:
@@ -29,3 +29,4 @@ jadx-output/
|
||||
*.dump
|
||||
*.log
|
||||
*.cfg
|
||||
*.orig
|
||||
|
||||
@@ -110,7 +110,7 @@ public class JadxCLIArgs {
|
||||
return process(jcw);
|
||||
}
|
||||
|
||||
private boolean process(JCommanderWrapper<?> jcw) {
|
||||
private boolean process(JCommanderWrapper<JadxCLIArgs> jcw) {
|
||||
if (printHelp) {
|
||||
jcw.printUsage();
|
||||
return false;
|
||||
|
||||
@@ -294,6 +294,10 @@ public final class JadxDecompiler {
|
||||
return root;
|
||||
}
|
||||
|
||||
List<IDexTreeVisitor> getPasses() {
|
||||
return passes;
|
||||
}
|
||||
|
||||
synchronized BinaryXMLParser getXmlParser() {
|
||||
if (xmlParser == null) {
|
||||
xmlParser = new BinaryXMLParser(root);
|
||||
|
||||
@@ -11,9 +11,8 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.visitors.ClassModifier;
|
||||
import jadx.core.dex.visitors.CodeShrinker;
|
||||
import jadx.core.dex.visitors.ConstInlineVisitor;
|
||||
import jadx.core.dex.visitors.DebugInfoVisitor;
|
||||
import jadx.core.dex.visitors.ConstructorVisitor;
|
||||
import jadx.core.dex.visitors.DependencyCollector;
|
||||
import jadx.core.dex.visitors.DotGraphVisitor;
|
||||
import jadx.core.dex.visitors.EnumVisitor;
|
||||
@@ -21,6 +20,8 @@ import jadx.core.dex.visitors.ExtractFieldInit;
|
||||
import jadx.core.dex.visitors.FallbackModeVisitor;
|
||||
import jadx.core.dex.visitors.FixAccessModifiers;
|
||||
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.ModVisitor;
|
||||
import jadx.core.dex.visitors.PrepareForCodeGen;
|
||||
@@ -28,20 +29,21 @@ import jadx.core.dex.visitors.ReSugarCode;
|
||||
import jadx.core.dex.visitors.RenameVisitor;
|
||||
import jadx.core.dex.visitors.SimplifyVisitor;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockExceptionHandler;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockFinallyExtract;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockFinish;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockProcessor;
|
||||
import jadx.core.dex.visitors.blocksmaker.BlockSplitter;
|
||||
import jadx.core.dex.visitors.debuginfo.DebugInfoApplyVisitor;
|
||||
import jadx.core.dex.visitors.debuginfo.DebugInfoParseVisitor;
|
||||
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.LoopRegionVisitor;
|
||||
import jadx.core.dex.visitors.regions.ProcessVariables;
|
||||
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
|
||||
import jadx.core.dex.visitors.regions.ReturnVisitor;
|
||||
import jadx.core.dex.visitors.ssa.EliminatePhiNodes;
|
||||
import jadx.core.dex.visitors.regions.variables.ProcessVariables;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||
import jadx.core.dex.visitors.typeinference.FinishTypeInference;
|
||||
import jadx.core.dex.visitors.typeinference.TypeInference;
|
||||
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||
|
||||
public class Jadx {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
|
||||
@@ -60,31 +62,32 @@ public class Jadx {
|
||||
if (args.isFallbackMode()) {
|
||||
passes.add(new FallbackModeVisitor());
|
||||
} else {
|
||||
passes.add(new BlockSplitter());
|
||||
passes.add(new BlockProcessor());
|
||||
passes.add(new BlockExceptionHandler());
|
||||
passes.add(new BlockFinallyExtract());
|
||||
passes.add(new BlockFinish());
|
||||
|
||||
passes.add(new SSATransform());
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoVisitor());
|
||||
passes.add(new DebugInfoParseVisitor());
|
||||
}
|
||||
passes.add(new TypeInference());
|
||||
|
||||
passes.add(new BlockSplitter());
|
||||
if (args.isRawCFGOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRaw());
|
||||
}
|
||||
|
||||
passes.add(new BlockProcessor());
|
||||
passes.add(new BlockExceptionHandler());
|
||||
passes.add(new BlockFinish());
|
||||
|
||||
passes.add(new SSATransform());
|
||||
passes.add(new ConstructorVisitor());
|
||||
passes.add(new InitCodeVariables());
|
||||
passes.add(new MarkFinallyVisitor());
|
||||
passes.add(new ConstInlineVisitor());
|
||||
passes.add(new FinishTypeInference());
|
||||
passes.add(new EliminatePhiNodes());
|
||||
passes.add(new TypeInferenceVisitor());
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoApplyVisitor());
|
||||
}
|
||||
|
||||
passes.add(new ModVisitor());
|
||||
|
||||
passes.add(new CodeShrinker());
|
||||
passes.add(new CodeShrinkVisitor());
|
||||
passes.add(new ReSugarCode());
|
||||
|
||||
if (args.isCfgOutput()) {
|
||||
passes.add(DotGraphVisitor.dump());
|
||||
}
|
||||
@@ -92,8 +95,9 @@ public class Jadx {
|
||||
passes.add(new RegionMakerVisitor());
|
||||
passes.add(new IfRegionVisitor());
|
||||
passes.add(new ReturnVisitor());
|
||||
passes.add(new CleanRegions());
|
||||
|
||||
passes.add(new CodeShrinker());
|
||||
passes.add(new CodeShrinkVisitor());
|
||||
passes.add(new SimplifyVisitor());
|
||||
passes.add(new CheckRegions());
|
||||
|
||||
@@ -102,16 +106,15 @@ public class Jadx {
|
||||
passes.add(new ClassModifier());
|
||||
passes.add(new MethodInlineVisitor());
|
||||
passes.add(new EnumVisitor());
|
||||
passes.add(new PrepareForCodeGen());
|
||||
passes.add(new LoopRegionVisitor());
|
||||
passes.add(new ProcessVariables());
|
||||
|
||||
passes.add(new ProcessVariables());
|
||||
passes.add(new PrepareForCodeGen());
|
||||
if (args.isCfgOutput()) {
|
||||
passes.add(DotGraphVisitor.dumpRegions());
|
||||
}
|
||||
|
||||
passes.add(new DependencyCollector());
|
||||
|
||||
passes.add(new RenameVisitor());
|
||||
}
|
||||
return passes;
|
||||
|
||||
@@ -45,6 +45,8 @@ public class ClsSet {
|
||||
|
||||
private static final String STRING_CHARSET = "US-ASCII";
|
||||
|
||||
private static final NClass[] EMPTY_NCLASS_ARRAY = new NClass[0];
|
||||
|
||||
private NClass[] classes;
|
||||
|
||||
public void load(RootNode root) {
|
||||
@@ -93,7 +95,11 @@ public class ClsSet {
|
||||
parents.add(c);
|
||||
}
|
||||
}
|
||||
return parents.toArray(new NClass[parents.size()]);
|
||||
int size = parents.size();
|
||||
if (size == 0) {
|
||||
return EMPTY_NCLASS_ARRAY;
|
||||
}
|
||||
return parents.toArray(new NClass[size]);
|
||||
}
|
||||
|
||||
private static NClass getCls(String fullName, Map<String, NClass> names) {
|
||||
|
||||
@@ -23,7 +23,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
public class ClspGraph {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
|
||||
|
||||
private final Map<String, Set<String>> ancestorCache = Collections.synchronizedMap(new WeakHashMap<String, Set<String>>());
|
||||
private final Map<String, Set<String>> ancestorCache = Collections.synchronizedMap(new WeakHashMap<>());
|
||||
private Map<String, NClass> nameMap;
|
||||
|
||||
private final Set<String> missingClasses = new HashSet<>();
|
||||
@@ -58,6 +58,10 @@ public class ClspGraph {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isClsKnown(String fullName) {
|
||||
return nameMap.containsKey(fullName);
|
||||
}
|
||||
|
||||
private NClass addClass(ClassNode cls) {
|
||||
String rawName = cls.getRawName();
|
||||
NClass nClass = new NClass(rawName, -1);
|
||||
@@ -65,11 +69,24 @@ public class ClspGraph {
|
||||
return nClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code clsName} instanceof {@code implClsName}
|
||||
*/
|
||||
public boolean isImplements(String clsName, String implClsName) {
|
||||
Set<String> anc = getAncestors(clsName);
|
||||
return anc.contains(implClsName);
|
||||
}
|
||||
|
||||
public List<String> getImplementations(String clsName) {
|
||||
List<String> list = new ArrayList<>();
|
||||
for (String cls : nameMap.keySet()) {
|
||||
if (isImplements(cls, clsName)) {
|
||||
list.add(cls);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public String getCommonAncestor(String clsName, String implClsName) {
|
||||
if (clsName.equals(implClsName)) {
|
||||
return clsName;
|
||||
@@ -100,7 +117,7 @@ public class ClspGraph {
|
||||
return null;
|
||||
}
|
||||
|
||||
private Set<String> getAncestors(String clsName) {
|
||||
public Set<String> getAncestors(String clsName) {
|
||||
Set<String> result = ancestorCache.get(clsName);
|
||||
if (result != null) {
|
||||
return result;
|
||||
|
||||
@@ -18,7 +18,6 @@ import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr;
|
||||
import jadx.core.dex.attributes.nodes.EnumClassAttr.EnumField;
|
||||
import jadx.core.dex.attributes.nodes.JadxError;
|
||||
import jadx.core.dex.attributes.nodes.JadxWarn;
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
@@ -263,10 +262,11 @@ public class ClassGen {
|
||||
addMethod(code, mth);
|
||||
} catch (Exception e) {
|
||||
code.newLine().add("/*");
|
||||
code.newLine().add(ErrorsCounter.methodError(mth, "Method generation error", e));
|
||||
code.newLine().add(Utils.getStackTrace(e));
|
||||
code.newLine().addMultiLine(ErrorsCounter.methodError(mth, "Method generation error", e));
|
||||
code.newLine().addMultiLine(Utils.getStackTrace(e));
|
||||
code.newLine().add("*/");
|
||||
code.setIndent(savedIndent);
|
||||
mth.addError("Method generation error: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -331,7 +331,6 @@ public class ClassGen {
|
||||
|
||||
private void insertDecompilationProblems(CodeWriter code, AttrNode node) {
|
||||
List<JadxError> errors = node.getAll(AType.JADX_ERROR);
|
||||
List<JadxWarn> warns = node.getAll(AType.JADX_WARN);
|
||||
if (!errors.isEmpty()) {
|
||||
errors.forEach(err -> {
|
||||
code.startLine("/* JADX ERROR: ").add(err.getError());
|
||||
@@ -344,8 +343,10 @@ public class ClassGen {
|
||||
code.add("*/");
|
||||
});
|
||||
}
|
||||
List<String> warns = node.getAll(AType.JADX_WARN);
|
||||
if (!warns.isEmpty()) {
|
||||
warns.forEach(warn -> code.startLine("/* JADX WARNING: ").addMultiLine(warn.getWarn()).add(" */"));
|
||||
warns.stream().distinct()
|
||||
.forEach(warn -> code.startLine("/* JADX WARNING: ").addMultiLine(warn).add(" */"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -35,6 +34,7 @@ import jadx.core.dex.instructions.InvokeType;
|
||||
import jadx.core.dex.instructions.NewArrayNode;
|
||||
import jadx.core.dex.instructions.SwitchNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.FieldArg;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
@@ -123,12 +123,16 @@ public class InsnGen {
|
||||
}
|
||||
|
||||
public void declareVar(CodeWriter code, RegisterArg arg) {
|
||||
if (arg.getSVar().contains(AFlag.FINAL)) {
|
||||
declareVar(code, arg.getSVar().getCodeVar());
|
||||
}
|
||||
|
||||
public void declareVar(CodeWriter code, CodeVar codeVar) {
|
||||
if (codeVar.isFinal()) {
|
||||
code.add("final ");
|
||||
}
|
||||
useType(code, arg.getType());
|
||||
useType(code, codeVar.getType());
|
||||
code.add(' ');
|
||||
code.add(mgen.getNameGen().assignArg(arg));
|
||||
code.add(mgen.getNameGen().assignArg(codeVar));
|
||||
}
|
||||
|
||||
private String lit(LiteralArg arg) {
|
||||
@@ -201,11 +205,11 @@ public class InsnGen {
|
||||
mgen.getClassGen().useType(code, type);
|
||||
}
|
||||
|
||||
public boolean makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
|
||||
return makeInsn(insn, code, null);
|
||||
public void makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
|
||||
makeInsn(insn, code, null);
|
||||
}
|
||||
|
||||
protected boolean makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
|
||||
protected void makeInsn(InsnNode insn, CodeWriter code, Flags flag) throws CodegenException {
|
||||
try {
|
||||
Set<Flags> state = EnumSet.noneOf(Flags.class);
|
||||
if (flag == Flags.BODY_ONLY || flag == Flags.BODY_ONLY_NOWRAP) {
|
||||
@@ -227,7 +231,6 @@ public class InsnGen {
|
||||
} catch (Exception th) {
|
||||
throw new CodegenException(mth, "Error generate insn: " + insn, th);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void makeInsnBody(CodeWriter code, InsnNode insn, Set<Flags> state) throws CodegenException {
|
||||
@@ -485,19 +488,7 @@ public class InsnGen {
|
||||
case FILL_ARRAY:
|
||||
fallbackOnlyInsn(insn);
|
||||
FillArrayNode arrayNode = (FillArrayNode) insn;
|
||||
Object data = arrayNode.getData();
|
||||
String arrStr;
|
||||
if (data instanceof int[]) {
|
||||
arrStr = Arrays.toString((int[]) data);
|
||||
} else if (data instanceof short[]) {
|
||||
arrStr = Arrays.toString((short[]) data);
|
||||
} else if (data instanceof byte[]) {
|
||||
arrStr = Arrays.toString((byte[]) data);
|
||||
} else if (data instanceof long[]) {
|
||||
arrStr = Arrays.toString((long[]) data);
|
||||
} else {
|
||||
arrStr = "?";
|
||||
}
|
||||
String arrStr = arrayNode.dataToString();
|
||||
code.add('{').add(arrStr.substring(1, arrStr.length() - 1)).add('}');
|
||||
break;
|
||||
|
||||
@@ -508,7 +499,6 @@ public class InsnGen {
|
||||
break;
|
||||
|
||||
case PHI:
|
||||
case MERGE:
|
||||
fallbackOnlyInsn(insn);
|
||||
code.add(insn.getType().toString()).add('(');
|
||||
for (InsnArg insnArg : insn.getArguments()) {
|
||||
@@ -610,11 +600,8 @@ public class InsnGen {
|
||||
|
||||
// inline method
|
||||
MethodNode callMthNode = mth.root().deepResolveMethod(callMth);
|
||||
if (callMthNode != null) {
|
||||
if (inlineMethod(callMthNode, insn, code)) {
|
||||
return;
|
||||
}
|
||||
callMth = callMthNode.getMethodInfo();
|
||||
if (callMthNode != null && inlineMethod(callMthNode, insn, code)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int k = 0;
|
||||
@@ -653,8 +640,10 @@ public class InsnGen {
|
||||
}
|
||||
if (callMthNode != null) {
|
||||
code.attachAnnotation(callMthNode);
|
||||
code.add(callMthNode.getAlias());
|
||||
} else {
|
||||
code.add(callMth.getAlias());
|
||||
}
|
||||
code.add(callMth.getAlias());
|
||||
generateMethodArguments(code, insn, k, callMthNode);
|
||||
}
|
||||
|
||||
@@ -675,7 +664,7 @@ public class InsnGen {
|
||||
do {
|
||||
ClassInfo nextClsInfo = nextParent.getClassInfo();
|
||||
if (nextClsInfo.equals(declClass)
|
||||
|| ArgType.isInstanceOf(mth.dex(), nextClsInfo.getType(), declClass.getType())) {
|
||||
|| ArgType.isInstanceOf(mth.root(), nextClsInfo.getType(), declClass.getType())) {
|
||||
if (nextParent == useCls) {
|
||||
return null;
|
||||
}
|
||||
@@ -737,7 +726,14 @@ public class InsnGen {
|
||||
* Add additional cast for overloaded method argument.
|
||||
*/
|
||||
private boolean processOverloadedArg(CodeWriter code, MethodNode callMth, InsnArg arg, int origPos) {
|
||||
ArgType origType = callMth.getMethodInfo().getArgumentsTypes().get(origPos);
|
||||
ArgType origType;
|
||||
List<RegisterArg> arguments = callMth.getArguments(false);
|
||||
if (arguments == null || arguments.isEmpty()) {
|
||||
mth.addComment("JADX INFO: used method not loaded: " + callMth + ", types can be incorrect");
|
||||
origType = callMth.getMethodInfo().getArgumentsTypes().get(origPos);
|
||||
} else {
|
||||
origType = arguments.get(origPos).getInitType();
|
||||
}
|
||||
if (!arg.getType().equals(origType)) {
|
||||
code.add('(');
|
||||
useType(code, origType);
|
||||
|
||||
@@ -11,10 +11,13 @@ import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.annotations.MethodParameters;
|
||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
@@ -22,7 +25,6 @@ import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.FallbackModeVisitor;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
@@ -112,11 +114,10 @@ public class MethodGen {
|
||||
} else if (args.size() > 2) {
|
||||
args = args.subList(2, args.size());
|
||||
} else {
|
||||
LOG.warn(ErrorsCounter.formatMsg(mth,
|
||||
"Incorrect number of args for enum constructor: " + args.size()
|
||||
+ " (expected >= 2)"
|
||||
));
|
||||
mth.addComment("JADX WARN: 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());
|
||||
}
|
||||
addMethodArguments(code, args);
|
||||
code.add(')');
|
||||
@@ -125,40 +126,48 @@ public class MethodGen {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void addMethodArguments(CodeWriter argsCode, List<RegisterArg> args) {
|
||||
private void addMethodArguments(CodeWriter code, List<RegisterArg> args) {
|
||||
MethodParameters paramsAnnotation = mth.get(AType.ANNOTATION_MTH_PARAMETERS);
|
||||
int i = 0;
|
||||
for (Iterator<RegisterArg> it = args.iterator(); it.hasNext(); ) {
|
||||
RegisterArg arg = it.next();
|
||||
Iterator<RegisterArg> it = args.iterator();
|
||||
while (it.hasNext()) {
|
||||
RegisterArg mthArg = it.next();
|
||||
SSAVar ssaVar = mthArg.getSVar();
|
||||
CodeVar var;
|
||||
if (ssaVar == null) {
|
||||
// null for abstract or interface methods
|
||||
var = CodeVar.fromMthArg(mthArg);
|
||||
} else {
|
||||
var = ssaVar.getCodeVar();
|
||||
}
|
||||
|
||||
// add argument annotation
|
||||
if (paramsAnnotation != null) {
|
||||
annotationGen.addForParameter(argsCode, paramsAnnotation, i);
|
||||
annotationGen.addForParameter(code, paramsAnnotation, i);
|
||||
}
|
||||
SSAVar argSVar = arg.getSVar();
|
||||
if (argSVar != null && argSVar.contains(AFlag.FINAL)) {
|
||||
argsCode.add("final ");
|
||||
if (var.isFinal()) {
|
||||
code.add("final ");
|
||||
}
|
||||
ArgType argType = var.getType();
|
||||
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
|
||||
// change last array argument to varargs
|
||||
ArgType type = arg.getType();
|
||||
if (type.isArray()) {
|
||||
ArgType elType = type.getArrayElement();
|
||||
classGen.useType(argsCode, elType);
|
||||
argsCode.add("...");
|
||||
if (argType.isArray()) {
|
||||
ArgType elType = argType.getArrayElement();
|
||||
classGen.useType(code, elType);
|
||||
code.add("...");
|
||||
} else {
|
||||
LOG.warn(ErrorsCounter.formatMsg(mth, "Last argument in varargs method not array"));
|
||||
classGen.useType(argsCode, arg.getType());
|
||||
mth.addComment("JADX INFO: Last argument in varargs method is not array: " + var);
|
||||
classGen.useType(code, argType);
|
||||
}
|
||||
} else {
|
||||
classGen.useType(argsCode, arg.getType());
|
||||
classGen.useType(code, argType);
|
||||
}
|
||||
argsCode.add(' ');
|
||||
argsCode.add(nameGen.assignArg(arg));
|
||||
code.add(' ');
|
||||
code.add(nameGen.assignArg(var));
|
||||
|
||||
i++;
|
||||
if (it.hasNext()) {
|
||||
argsCode.add(", ");
|
||||
code.add(", ");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,6 +201,7 @@ public class MethodGen {
|
||||
if (mth.getInstructions() == null) {
|
||||
// load original instructions
|
||||
try {
|
||||
mth.unload();
|
||||
mth.load();
|
||||
DepthTraversal.visit(new FallbackModeVisitor(), mth);
|
||||
} catch (DecodeException e) {
|
||||
@@ -213,29 +223,59 @@ public class MethodGen {
|
||||
|
||||
public static void addFallbackInsns(CodeWriter code, MethodNode mth, InsnNode[] insnArr, boolean addLabels) {
|
||||
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), true);
|
||||
InsnNode prevInsn = null;
|
||||
for (InsnNode insn : insnArr) {
|
||||
if (insn == null || insn.getType() == InsnType.NOP) {
|
||||
continue;
|
||||
}
|
||||
if (addLabels && (insn.contains(AType.JUMP) || insn.contains(AType.EXC_HANDLER))) {
|
||||
if (addLabels && needLabel(insn, prevInsn)) {
|
||||
code.decIndent();
|
||||
code.startLine(getLabelName(insn.getOffset()) + ':');
|
||||
code.incIndent();
|
||||
}
|
||||
try {
|
||||
if (insnGen.makeInsn(insn, code)) {
|
||||
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
|
||||
if (catchAttr != null) {
|
||||
code.add("\t " + catchAttr);
|
||||
code.startLine();
|
||||
RegisterArg resArg = insn.getResult();
|
||||
if (resArg != null) {
|
||||
ArgType varType = resArg.getInitType();
|
||||
if (varType.isTypeKnown()) {
|
||||
code.add(varType.toString()).add(' ');
|
||||
}
|
||||
}
|
||||
insnGen.makeInsn(insn, code, InsnGen.Flags.INLINE);
|
||||
CatchAttr catchAttr = insn.get(AType.CATCH_BLOCK);
|
||||
if (catchAttr != null) {
|
||||
code.add(" // " + catchAttr);
|
||||
}
|
||||
} catch (CodegenException e) {
|
||||
LOG.debug("Error generate fallback instruction: ", e.getCause());
|
||||
code.startLine("// error: " + insn);
|
||||
}
|
||||
prevInsn = insn;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean needLabel(InsnNode insn, InsnNode prevInsn) {
|
||||
if (insn.contains(AType.EXC_HANDLER)) {
|
||||
return true;
|
||||
}
|
||||
if (insn.contains(AType.JUMP)) {
|
||||
// don't add label for ifs else branch
|
||||
if (prevInsn != null && prevInsn.getType() == InsnType.IF) {
|
||||
List<JumpInfo> jumps = insn.getAll(AType.JUMP);
|
||||
if (jumps.size() == 1) {
|
||||
JumpInfo jump = jumps.get(0);
|
||||
if (jump.getSrc() == prevInsn.getOffset() && jump.getDest() == insn.getOffset()) {
|
||||
int target = ((IfNode) prevInsn).getTarget();
|
||||
return insn.getOffset() == target;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return fallback variant of method codegen
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -11,12 +12,15 @@ import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
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.MethodNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
@@ -44,22 +48,35 @@ public class NameGen {
|
||||
"java.lang.Byte", "b",
|
||||
"java.lang.Float", "f",
|
||||
"java.lang.Long", "l",
|
||||
"java.lang.Double", "d"
|
||||
"java.lang.Double", "d",
|
||||
"java.lang.StringBuilder", "sb",
|
||||
"java.lang.Exception", "exc"
|
||||
);
|
||||
}
|
||||
|
||||
public NameGen(MethodNode mth, boolean fallback) {
|
||||
this.mth = mth;
|
||||
this.fallback = fallback;
|
||||
addNamesUsedInClass();
|
||||
}
|
||||
|
||||
public String assignArg(RegisterArg arg) {
|
||||
String name = makeArgName(arg);
|
||||
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) {
|
||||
String name = makeArgName(var);
|
||||
if (fallback) {
|
||||
return name;
|
||||
}
|
||||
name = getUniqueVarName(name);
|
||||
arg.setName(name);
|
||||
var.setName(name);
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -99,39 +116,50 @@ public class NameGen {
|
||||
return r;
|
||||
}
|
||||
|
||||
private String makeArgName(RegisterArg arg) {
|
||||
private String makeArgName(CodeVar var) {
|
||||
if (fallback) {
|
||||
return getFallbackName(arg);
|
||||
return getFallbackName(var);
|
||||
}
|
||||
if (arg.isThis()) {
|
||||
if (var.isThis()) {
|
||||
return RegisterArg.THIS_ARG_NAME;
|
||||
}
|
||||
String name = arg.getName();
|
||||
String varName = name != null ? name : guessName(arg);
|
||||
String name = var.getName();
|
||||
String varName = name != null ? name : guessName(var);
|
||||
if (NameMapper.isReserved(varName)) {
|
||||
return varName + 'R';
|
||||
varName = varName + 'R';
|
||||
}
|
||||
if (!NameMapper.isValidIdentifier(varName)) {
|
||||
varName = getFallbackName(var);
|
||||
}
|
||||
return varName;
|
||||
}
|
||||
|
||||
private String getFallbackName(CodeVar var) {
|
||||
return getFallbackName(var.getSsaVars().get(0).getAssign());
|
||||
}
|
||||
|
||||
private String getFallbackName(RegisterArg arg) {
|
||||
return "r" + arg.getRegNum();
|
||||
}
|
||||
|
||||
private String guessName(RegisterArg arg) {
|
||||
SSAVar sVar = arg.getSVar();
|
||||
if (sVar != null && sVar.getName() == null) {
|
||||
RegisterArg assignArg = sVar.getAssign();
|
||||
InsnNode assignInsn = assignArg.getParentInsn();
|
||||
if (assignInsn != null) {
|
||||
String name = makeNameFromInsn(assignInsn);
|
||||
if (name != null && !NameMapper.isReserved(name)) {
|
||||
assignArg.setName(name);
|
||||
return name;
|
||||
private String guessName(CodeVar var) {
|
||||
List<SSAVar> ssaVars = var.getSsaVars();
|
||||
if (ssaVars != null && !ssaVars.isEmpty()) {
|
||||
// TODO: use all vars for better name generation
|
||||
SSAVar ssaVar = ssaVars.get(0);
|
||||
if (ssaVar != null && ssaVar.getName() == null) {
|
||||
RegisterArg assignArg = ssaVar.getAssign();
|
||||
InsnNode assignInsn = assignArg.getParentInsn();
|
||||
if (assignInsn != null) {
|
||||
String name = makeNameFromInsn(assignInsn);
|
||||
if (name != null && !NameMapper.isReserved(name)) {
|
||||
assignArg.setName(name);
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return makeNameForType(arg.getType());
|
||||
return makeNameForType(var.getType());
|
||||
}
|
||||
|
||||
private String makeNameForType(ArgType type) {
|
||||
@@ -237,6 +265,9 @@ public class NameGen {
|
||||
if ("forName".equals(name) && declType.equals(ArgType.CLASS)) {
|
||||
return OBJ_ALIAS.get(Consts.CLASS_CLASS);
|
||||
}
|
||||
if (name.startsWith("to")) {
|
||||
return fromName(name.substring(2));
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package jadx.core.codegen;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -14,6 +15,7 @@ import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.instructions.SwitchNode;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
@@ -35,6 +37,7 @@ import jadx.core.dex.regions.loops.ForLoop;
|
||||
import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.regions.loops.LoopType;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.exceptions.CodegenException;
|
||||
@@ -75,7 +78,7 @@ public class RegionGen extends InsnGen {
|
||||
private void declareVars(CodeWriter code, IContainer cont) {
|
||||
DeclareVariablesAttr declVars = cont.get(AType.DECLARE_VARIABLES);
|
||||
if (declVars != null) {
|
||||
for (RegisterArg v : declVars.getVars()) {
|
||||
for (CodeVar v : declVars.getVars()) {
|
||||
code.startLine();
|
||||
declareVar(code, v);
|
||||
code.add(';');
|
||||
@@ -97,8 +100,12 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
|
||||
private void makeSimpleBlock(IBlock block, CodeWriter code) throws CodegenException {
|
||||
if (block.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
if (!insn.contains(AFlag.SKIP)) {
|
||||
if (!insn.contains(AFlag.DONT_GENERATE)) {
|
||||
makeInsn(insn, code);
|
||||
}
|
||||
}
|
||||
@@ -232,7 +239,8 @@ public class RegionGen extends InsnGen {
|
||||
}
|
||||
|
||||
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);
|
||||
code.startLine("switch (");
|
||||
addArg(code, arg, false);
|
||||
@@ -323,7 +331,8 @@ public class RegionGen extends InsnGen {
|
||||
code.add(' ');
|
||||
InsnArg arg = handler.getArg();
|
||||
if (arg instanceof RegisterArg) {
|
||||
code.add(mgen.getNameGen().assignArg((RegisterArg) arg));
|
||||
RegisterArg reg = (RegisterArg) arg;
|
||||
code.add(mgen.getNameGen().assignArg(reg.getSVar().getCodeVar()));
|
||||
} else if (arg instanceof NamedArg) {
|
||||
code.add(mgen.getNameGen().assignNamedArg((NamedArg) arg));
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package jadx.core.codegen;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
@@ -38,18 +37,20 @@ public class TypeGen {
|
||||
return literalToString(lit, type, dexNode.root().getStringUtils());
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static String literalToString(long lit, ArgType type) {
|
||||
return literalToString(lit, type, new StringUtils(new JadxArgs()));
|
||||
}
|
||||
|
||||
private static String literalToString(long lit, ArgType type, StringUtils stringUtils) {
|
||||
public static String literalToString(long lit, ArgType type, StringUtils stringUtils) {
|
||||
if (type == null || !type.isTypeKnown()) {
|
||||
String n = Long.toString(lit);
|
||||
if (Math.abs(lit) > 100) {
|
||||
n += "; // 0x" + Long.toHexString(lit)
|
||||
+ " float:" + Float.intBitsToFloat((int) lit)
|
||||
+ " double:" + Double.longBitsToDouble(lit);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(n).append("(0x").append(Long.toHexString(lit));
|
||||
if (type == null || type.contains(PrimitiveType.FLOAT)) {
|
||||
sb.append(", float:").append(Float.intBitsToFloat((int) lit));
|
||||
}
|
||||
if (type == null || type.contains(PrimitiveType.DOUBLE)) {
|
||||
sb.append(", double:").append(Double.longBitsToDouble(lit));
|
||||
}
|
||||
sb.append(')');
|
||||
return sb.toString();
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -11,13 +11,13 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
@@ -226,8 +226,9 @@ public class Deobfuscator {
|
||||
clsInfo.rename(cls.dex().root(), fullName);
|
||||
}
|
||||
for (FieldNode field : cls.getFields()) {
|
||||
if (field.contains(AFlag.DONT_RENAME))
|
||||
continue;
|
||||
if (field.contains(AFlag.DONT_RENAME)) {
|
||||
continue;
|
||||
}
|
||||
renameField(field);
|
||||
}
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
|
||||
@@ -8,27 +8,39 @@ public enum AFlag {
|
||||
LOOP_END,
|
||||
|
||||
SYNTHETIC,
|
||||
FINAL, // SSAVar attribute for make var final
|
||||
|
||||
RETURN, // block contains only return instruction
|
||||
ORIG_RETURN,
|
||||
|
||||
DECLARE_VAR,
|
||||
DONT_WRAP,
|
||||
|
||||
DONT_SHRINK,
|
||||
DONT_INLINE,
|
||||
DONT_GENERATE,
|
||||
DONT_GENERATE, // process as usual, but don't output to generated code
|
||||
DONT_RENAME, // do not rename during deobfuscation
|
||||
SKIP,
|
||||
REMOVE,
|
||||
REMOVE, // can be completely removed
|
||||
ADDED_TO_REGION,
|
||||
|
||||
FINALLY_INSNS,
|
||||
|
||||
SKIP_FIRST_ARG,
|
||||
SKIP_ARG, // skip argument in invoke call
|
||||
ANONYMOUS_CONSTRUCTOR,
|
||||
ANONYMOUS_CLASS,
|
||||
|
||||
THIS,
|
||||
|
||||
/**
|
||||
* RegisterArg attribute for method arguments
|
||||
*/
|
||||
METHOD_ARGUMENT,
|
||||
|
||||
/**
|
||||
* Type of RegisterArg or SSAVar can't be changed
|
||||
*/
|
||||
IMMUTABLE_TYPE,
|
||||
|
||||
CUSTOM_DECLARE, // variable for this register don't need declaration
|
||||
DECLARE_VAR,
|
||||
|
||||
ELSE_IF_CHAIN,
|
||||
|
||||
WRAPPED,
|
||||
|
||||
@@ -10,12 +10,13 @@ import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
import jadx.core.dex.attributes.nodes.ForceReturnAttr;
|
||||
import jadx.core.dex.attributes.nodes.IgnoreEdgeAttr;
|
||||
import jadx.core.dex.attributes.nodes.JadxError;
|
||||
import jadx.core.dex.attributes.nodes.JadxWarn;
|
||||
import jadx.core.dex.attributes.nodes.JumpInfo;
|
||||
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
import jadx.core.dex.attributes.nodes.LoopLabelAttr;
|
||||
import jadx.core.dex.attributes.nodes.MethodInlineAttr;
|
||||
import jadx.core.dex.attributes.nodes.PhiListAttr;
|
||||
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.nodes.parser.FieldInitAttr;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
@@ -34,9 +35,9 @@ public class AType<T extends IAttribute> {
|
||||
public static final AType<AttrList<LoopInfo>> LOOP = new AType<>();
|
||||
public static final AType<AttrList<EdgeInsnAttr>> EDGE_INSN = new AType<>();
|
||||
|
||||
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>();
|
||||
public static final AType<AttrList<JadxWarn>> JADX_WARN = new AType<>();
|
||||
public static final AType<AttrList<String>> COMMENTS = new AType<>();
|
||||
public static final AType<AttrList<JadxError>> JADX_ERROR = new AType<>(); // code failed to decompile completely
|
||||
public static final AType<AttrList<String>> JADX_WARN = new AType<>(); // mark code as inconsistent (code can be viewed)
|
||||
public static final AType<AttrList<String>> COMMENTS = new AType<>(); // any additional info about decompilation
|
||||
|
||||
public static final AType<ExcHandlerAttr> EXC_HANDLER = new AType<>();
|
||||
public static final AType<CatchAttr> CATCH_BLOCK = new AType<>();
|
||||
@@ -54,4 +55,10 @@ public class AType<T extends IAttribute> {
|
||||
public static final AType<DeclareVariablesAttr> DECLARE_VARIABLES = new AType<>();
|
||||
public static final AType<LoopLabelAttr> LOOP_LABEL = new AType<>();
|
||||
public static final AType<IgnoreEdgeAttr> IGNORE_EDGE = new AType<>();
|
||||
|
||||
// method
|
||||
public static final AType<LocalVarsDebugInfoAttr> LOCAL_VARS_DEBUG_INFO = new AType<>();
|
||||
|
||||
// registers
|
||||
public static final AType<RegDebugInfoAttr> REG_DEBUG_INFO = new AType<>();
|
||||
}
|
||||
|
||||
@@ -25,6 +25,6 @@ public class AttrList<T> implements IAttribute {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Utils.listToString(list);
|
||||
return Utils.listToString(list, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,12 @@ public abstract class AttrNode implements IAttributeNode {
|
||||
return store;
|
||||
}
|
||||
|
||||
private void unloadIfEmpty() {
|
||||
if (storage.isEmpty() && storage != EMPTY_ATTR_STORAGE) {
|
||||
storage = EMPTY_ATTR_STORAGE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(AFlag flag) {
|
||||
return storage.contains(flag);
|
||||
@@ -70,21 +76,25 @@ public abstract class AttrNode implements IAttributeNode {
|
||||
@Override
|
||||
public void remove(AFlag flag) {
|
||||
storage.remove(flag);
|
||||
unloadIfEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends IAttribute> void remove(AType<T> type) {
|
||||
storage.remove(type);
|
||||
unloadIfEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttr(IAttribute attr) {
|
||||
storage.remove(attr);
|
||||
unloadIfEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAttributes() {
|
||||
storage.clear();
|
||||
unloadIfEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -121,6 +121,6 @@ public class AttributeStorage {
|
||||
if (list.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return "A:{" + Utils.listToString(list) + '}';
|
||||
return "A[" + Utils.listToString(list) + ']';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package jadx.core.dex.attributes;
|
||||
|
||||
public interface IAttribute {
|
||||
<T extends IAttribute> AType<T> getType();
|
||||
AType<? extends IAttribute> getType();
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import jadx.core.utils.Utils;
|
||||
|
||||
public class AnnotationsList implements IAttribute {
|
||||
|
||||
public static final AnnotationsList EMPTY = new AnnotationsList(Collections.<Annotation>emptyList());
|
||||
public static final AnnotationsList EMPTY = new AnnotationsList(Collections.emptyList());
|
||||
|
||||
private final Map<String, Annotation> map;
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
/**
|
||||
@@ -13,13 +13,13 @@ import jadx.core.utils.Utils;
|
||||
*/
|
||||
public class DeclareVariablesAttr implements IAttribute {
|
||||
|
||||
private final List<RegisterArg> vars = new LinkedList<>();
|
||||
private final List<CodeVar> vars = new ArrayList<>();
|
||||
|
||||
public Iterable<RegisterArg> getVars() {
|
||||
public Iterable<CodeVar> getVars() {
|
||||
return vars;
|
||||
}
|
||||
|
||||
public void addVar(RegisterArg arg) {
|
||||
public void addVar(CodeVar arg) {
|
||||
vars.add(arg);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ public class JadxError {
|
||||
str.append(cause.getClass());
|
||||
str.append(':');
|
||||
str.append(cause.getMessage());
|
||||
str.append("\n");
|
||||
str.append('\n');
|
||||
str.append(Utils.getStackTrace(cause));
|
||||
}
|
||||
return str.toString();
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class JadxWarn {
|
||||
|
||||
private final String warn;
|
||||
|
||||
public JadxWarn(String warn) {
|
||||
this.warn = Objects.requireNonNull(warn);
|
||||
}
|
||||
|
||||
public String getWarn() {
|
||||
return warn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JadxWarn: " + warn;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.visitors.debuginfo.LocalVar;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class LocalVarsDebugInfoAttr implements IAttribute {
|
||||
private final List<LocalVar> localVars;
|
||||
|
||||
public LocalVarsDebugInfoAttr(List<LocalVar> localVars) {
|
||||
this.localVars = localVars;
|
||||
}
|
||||
|
||||
public List<LocalVar> getLocalVars() {
|
||||
return localVars;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<LocalVarsDebugInfoAttr> getType() {
|
||||
return AType.LOCAL_VARS_DEBUG_INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Debug Info:\n " + Utils.listToString(localVars, "\n ");
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,9 @@ public class PhiListAttr implements IAttribute {
|
||||
for (PhiInsn phiInsn : list) {
|
||||
sb.append('r').append(phiInsn.getResult().getRegNum()).append(' ');
|
||||
}
|
||||
for (PhiInsn phiInsn : list) {
|
||||
sb.append("\n ").append(phiInsn).append(' ').append(phiInsn.getAttributesString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package jadx.core.dex.attributes.nodes;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttribute;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.visitors.debuginfo.LocalVar;
|
||||
|
||||
public class RegDebugInfoAttr implements IAttribute {
|
||||
|
||||
private final ArgType type;
|
||||
private final String name;
|
||||
|
||||
public RegDebugInfoAttr(LocalVar var) {
|
||||
this(var.getType(), var.getName());
|
||||
}
|
||||
|
||||
public RegDebugInfoAttr(ArgType type, String name) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public ArgType getRegType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AType<RegDebugInfoAttr> getType() {
|
||||
return AType.REG_DEBUG_INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
RegDebugInfoAttr that = (RegDebugInfoAttr) o;
|
||||
return Objects.equals(type, that.type) && Objects.equals(name, that.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(type, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "D('" + name + "' " + type + ')';
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
|
||||
@@ -9,43 +10,45 @@ import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
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 int size;
|
||||
private ArgType elemType;
|
||||
|
||||
public FillArrayNode(int resReg, FillArrayDataPayloadDecodedInstruction payload) {
|
||||
super(InsnType.FILL_ARRAY, 0);
|
||||
ArgType elType;
|
||||
switch (payload.getElementWidthUnit()) {
|
||||
case 1:
|
||||
elType = ArgType.unknown(PrimitiveType.BOOLEAN, PrimitiveType.BYTE);
|
||||
break;
|
||||
case 2:
|
||||
elType = ArgType.unknown(PrimitiveType.SHORT, PrimitiveType.CHAR);
|
||||
break;
|
||||
case 4:
|
||||
elType = ArgType.unknown(PrimitiveType.INT, PrimitiveType.FLOAT);
|
||||
break;
|
||||
case 8:
|
||||
elType = ArgType.unknown(PrimitiveType.LONG, PrimitiveType.DOUBLE);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new JadxRuntimeException("Unknown array element width: " + payload.getElementWidthUnit());
|
||||
}
|
||||
setResult(InsnArg.reg(resReg, ArgType.array(elType)));
|
||||
super(InsnType.FILL_ARRAY, 1);
|
||||
ArgType elType = getElementType(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:
|
||||
return ONE_BYTE_TYPE;
|
||||
case 2:
|
||||
return TWO_BYTES_TYPE;
|
||||
case 4:
|
||||
return FOUR_BYTES_TYPE;
|
||||
case 8:
|
||||
return EIGHT_BYTES_TYPE;
|
||||
default:
|
||||
throw new JadxRuntimeException("Unknown array element width: " + elementWidthUnit);
|
||||
}
|
||||
}
|
||||
|
||||
public Object getData() {
|
||||
return data;
|
||||
}
|
||||
@@ -58,34 +61,27 @@ public final class FillArrayNode extends InsnNode {
|
||||
return elemType;
|
||||
}
|
||||
|
||||
public void mergeElementType(DexNode dex, ArgType foundElemType) {
|
||||
ArgType r = ArgType.merge(dex, elemType, foundElemType);
|
||||
if (r != null) {
|
||||
elemType = r;
|
||||
}
|
||||
}
|
||||
|
||||
public List<LiteralArg> getLiteralArgs() {
|
||||
public List<LiteralArg> getLiteralArgs(ArgType type) {
|
||||
List<LiteralArg> list = new ArrayList<>(size);
|
||||
Object array = data;
|
||||
if (array instanceof int[]) {
|
||||
for (int b : (int[]) array) {
|
||||
list.add(InsnArg.lit(b, elemType));
|
||||
list.add(InsnArg.lit(b, type));
|
||||
}
|
||||
} else if (array instanceof byte[]) {
|
||||
for (byte b : (byte[]) array) {
|
||||
list.add(InsnArg.lit(b, elemType));
|
||||
list.add(InsnArg.lit(b, type));
|
||||
}
|
||||
} else if (array instanceof short[]) {
|
||||
for (short b : (short[]) array) {
|
||||
list.add(InsnArg.lit(b, elemType));
|
||||
list.add(InsnArg.lit(b, type));
|
||||
}
|
||||
} else if (array instanceof long[]) {
|
||||
for (long b : (long[]) array) {
|
||||
list.add(InsnArg.lit(b, elemType));
|
||||
list.add(InsnArg.lit(b, type));
|
||||
}
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unknown type: " + data.getClass() + ", expected: " + elemType);
|
||||
throw new JadxRuntimeException("Unknown type: " + data.getClass() + ", expected: " + type);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
@@ -101,4 +97,25 @@ public final class FillArrayNode extends InsnNode {
|
||||
FillArrayNode other = (FillArrayNode) obj;
|
||||
return elemType.equals(other.elemType) && data == other.data;
|
||||
}
|
||||
|
||||
public String dataToString() {
|
||||
if (data instanceof int[]) {
|
||||
return Arrays.toString((int[]) data);
|
||||
}
|
||||
if (data instanceof short[]) {
|
||||
return Arrays.toString((short[]) data);
|
||||
}
|
||||
if (data instanceof byte[]) {
|
||||
return Arrays.toString((byte[]) data);
|
||||
}
|
||||
if (data instanceof long[]) {
|
||||
return Arrays.toString((long[]) data);
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ", data: " + dataToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,21 +16,21 @@ import static jadx.core.utils.BlockUtils.selectOther;
|
||||
|
||||
public class IfNode extends GotoNode {
|
||||
|
||||
// change default types priority
|
||||
private static final ArgType ARG_TYPE = ArgType.unknown(
|
||||
PrimitiveType.INT,
|
||||
PrimitiveType.OBJECT, PrimitiveType.ARRAY,
|
||||
PrimitiveType.BOOLEAN, PrimitiveType.BYTE, PrimitiveType.SHORT, PrimitiveType.CHAR);
|
||||
|
||||
protected IfOp op;
|
||||
|
||||
private BlockNode thenBlock;
|
||||
private BlockNode elseBlock;
|
||||
|
||||
public IfNode(DecodedInstruction insn, IfOp op) {
|
||||
this(op, insn.getTarget(),
|
||||
InsnArg.reg(insn, 0, ARG_TYPE),
|
||||
insn.getRegisterCount() == 1 ? InsnArg.lit(0, ARG_TYPE) : InsnArg.reg(insn, 1, ARG_TYPE));
|
||||
super(InsnType.IF, insn.getTarget(), 2);
|
||||
this.op = op;
|
||||
ArgType argType = narrowTypeByOp(op);
|
||||
addArg(InsnArg.reg(insn, 0, argType));
|
||||
if (insn.getRegisterCount() == 1) {
|
||||
addArg(InsnArg.lit(0, argType));
|
||||
} else {
|
||||
addArg(InsnArg.reg(insn, 1, argType));
|
||||
}
|
||||
}
|
||||
|
||||
public IfNode(IfOp op, int targetOffset, InsnArg arg1, InsnArg arg2) {
|
||||
@@ -40,6 +40,22 @@ public class IfNode extends GotoNode {
|
||||
addArg(arg2);
|
||||
}
|
||||
|
||||
// change default types priority
|
||||
private static final ArgType WIDE_TYPE = ArgType.unknown(
|
||||
PrimitiveType.INT, PrimitiveType.BOOLEAN,
|
||||
PrimitiveType.OBJECT, PrimitiveType.ARRAY,
|
||||
PrimitiveType.BYTE, PrimitiveType.SHORT, PrimitiveType.CHAR);
|
||||
|
||||
private static final ArgType NUMBERS_TYPE = ArgType.unknown(
|
||||
PrimitiveType.INT, PrimitiveType.BYTE, PrimitiveType.SHORT, PrimitiveType.CHAR);
|
||||
|
||||
private static ArgType narrowTypeByOp(IfOp op) {
|
||||
if (op == IfOp.EQ || op == IfOp.NE) {
|
||||
return WIDE_TYPE;
|
||||
}
|
||||
return NUMBERS_TYPE;
|
||||
}
|
||||
|
||||
public IfOp getOp() {
|
||||
return op;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package jadx.core.dex.instructions;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class IndexInsnNode extends InsnNode {
|
||||
|
||||
@@ -30,11 +33,21 @@ public class IndexInsnNode extends InsnNode {
|
||||
return false;
|
||||
}
|
||||
IndexInsnNode other = (IndexInsnNode) obj;
|
||||
return index == null ? other.index == null : index.equals(other.index);
|
||||
return Objects.equals(index, other.index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ' ' + InsnUtils.indexToString(index);
|
||||
switch (insnType) {
|
||||
case CAST:
|
||||
case CHECK_CAST:
|
||||
return InsnUtils.formatOffset(offset) + ": "
|
||||
+ InsnUtils.insnTypeToString(insnType)
|
||||
+ getResult() + " = (" + InsnUtils.indexToString(index) + ") "
|
||||
+ Utils.listToString(getArguments());
|
||||
|
||||
default:
|
||||
return super.toString() + ' ' + InsnUtils.indexToString(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,10 @@ import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
@@ -101,15 +102,15 @@ public class InsnDecoder {
|
||||
case Opcodes.CONST_4:
|
||||
case Opcodes.CONST_16:
|
||||
case Opcodes.CONST_HIGH16:
|
||||
return insn(InsnType.CONST, InsnArg.reg(insn, 0, ArgType.NARROW),
|
||||
InsnArg.lit(insn, ArgType.NARROW));
|
||||
LiteralArg narrowLitArg = InsnArg.lit(insn, ArgType.NARROW);
|
||||
return insn(InsnType.CONST, InsnArg.reg(insn, 0, narrowLitArg.getType()), narrowLitArg);
|
||||
|
||||
case Opcodes.CONST_WIDE:
|
||||
case Opcodes.CONST_WIDE_16:
|
||||
case Opcodes.CONST_WIDE_32:
|
||||
case Opcodes.CONST_WIDE_HIGH16:
|
||||
return insn(InsnType.CONST, InsnArg.reg(insn, 0, ArgType.WIDE),
|
||||
InsnArg.lit(insn, ArgType.WIDE));
|
||||
LiteralArg wideLitArg = InsnArg.lit(insn, ArgType.WIDE);
|
||||
return insn(InsnType.CONST, InsnArg.reg(insn, 0, wideLitArg.getType()), wideLitArg);
|
||||
|
||||
case Opcodes.CONST_STRING:
|
||||
case Opcodes.CONST_STRING_JUMBO:
|
||||
@@ -403,12 +404,10 @@ public class InsnDecoder {
|
||||
return new GotoNode(insn.getTarget());
|
||||
|
||||
case Opcodes.THROW:
|
||||
return insn(InsnType.THROW, null,
|
||||
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)));
|
||||
return insn(InsnType.THROW, null, InsnArg.reg(insn, 0, ArgType.THROWABLE));
|
||||
|
||||
case Opcodes.MOVE_EXCEPTION:
|
||||
return insn(InsnType.MOVE_EXCEPTION,
|
||||
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)));
|
||||
return insn(InsnType.MOVE_EXCEPTION, InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT_NO_ARRAY));
|
||||
|
||||
case Opcodes.RETURN_VOID:
|
||||
return new InsnNode(InsnType.RETURN, 0);
|
||||
@@ -442,7 +441,7 @@ public class InsnDecoder {
|
||||
case Opcodes.IGET_OBJECT:
|
||||
FieldInfo igetFld = FieldInfo.fromDex(dex, insn.getIndex());
|
||||
InsnNode igetInsn = new IndexInsnNode(InsnType.IGET, igetFld, 1);
|
||||
igetInsn.setResult(InsnArg.reg(insn, 0, igetFld.getType()));
|
||||
igetInsn.setResult(InsnArg.reg(insn, 0, tryResolveFieldType(igetFld)));
|
||||
igetInsn.addArg(InsnArg.reg(insn, 1, igetFld.getDeclClass().getType()));
|
||||
return igetInsn;
|
||||
|
||||
@@ -455,7 +454,7 @@ public class InsnDecoder {
|
||||
case Opcodes.IPUT_OBJECT:
|
||||
FieldInfo iputFld = FieldInfo.fromDex(dex, insn.getIndex());
|
||||
InsnNode iputInsn = new IndexInsnNode(InsnType.IPUT, iputFld, 2);
|
||||
iputInsn.addArg(InsnArg.reg(insn, 0, iputFld.getType()));
|
||||
iputInsn.addArg(InsnArg.reg(insn, 0, tryResolveFieldType(iputFld)));
|
||||
iputInsn.addArg(InsnArg.reg(insn, 1, iputFld.getDeclClass().getType()));
|
||||
return iputInsn;
|
||||
|
||||
@@ -468,7 +467,7 @@ public class InsnDecoder {
|
||||
case Opcodes.SGET_OBJECT:
|
||||
FieldInfo sgetFld = FieldInfo.fromDex(dex, insn.getIndex());
|
||||
InsnNode sgetInsn = new IndexInsnNode(InsnType.SGET, sgetFld, 0);
|
||||
sgetInsn.setResult(InsnArg.reg(insn, 0, sgetFld.getType()));
|
||||
sgetInsn.setResult(InsnArg.reg(insn, 0, tryResolveFieldType(sgetFld)));
|
||||
return sgetInsn;
|
||||
|
||||
case Opcodes.SPUT:
|
||||
@@ -480,7 +479,7 @@ public class InsnDecoder {
|
||||
case Opcodes.SPUT_OBJECT:
|
||||
FieldInfo sputFld = FieldInfo.fromDex(dex, insn.getIndex());
|
||||
InsnNode sputInsn = new IndexInsnNode(InsnType.SPUT, sputFld, 1);
|
||||
sputInsn.addArg(InsnArg.reg(insn, 0, sputFld.getType()));
|
||||
sputInsn.addArg(InsnArg.reg(insn, 0, tryResolveFieldType(sputFld)));
|
||||
return sputInsn;
|
||||
|
||||
case Opcodes.ARRAY_LENGTH:
|
||||
@@ -490,7 +489,7 @@ public class InsnDecoder {
|
||||
return arrLenInsn;
|
||||
|
||||
case Opcodes.AGET:
|
||||
return arrayGet(insn, ArgType.NARROW);
|
||||
return arrayGet(insn, ArgType.INT_FLOAT);
|
||||
case Opcodes.AGET_BOOLEAN:
|
||||
return arrayGet(insn, ArgType.BOOLEAN);
|
||||
case Opcodes.AGET_BYTE:
|
||||
@@ -505,7 +504,7 @@ public class InsnDecoder {
|
||||
return arrayGet(insn, ArgType.UNKNOWN_OBJECT);
|
||||
|
||||
case Opcodes.APUT:
|
||||
return arrayPut(insn, ArgType.NARROW);
|
||||
return arrayPut(insn, ArgType.INT_FLOAT);
|
||||
case Opcodes.APUT_BOOLEAN:
|
||||
return arrayPut(insn, ArgType.BOOLEAN);
|
||||
case Opcodes.APUT_BYTE:
|
||||
@@ -544,14 +543,16 @@ public class InsnDecoder {
|
||||
return invoke(insn, offset, InvokeType.VIRTUAL, true);
|
||||
|
||||
case Opcodes.NEW_INSTANCE:
|
||||
return insn(InsnType.NEW_INSTANCE,
|
||||
InsnArg.reg(insn, 0, dex.getType(insn.getIndex())));
|
||||
ArgType clsType = dex.getType(insn.getIndex());
|
||||
IndexInsnNode newInstInsn = new IndexInsnNode(InsnType.NEW_INSTANCE, clsType, 0);
|
||||
newInstInsn.setResult(InsnArg.reg(insn, 0, clsType));
|
||||
return newInstInsn;
|
||||
|
||||
case Opcodes.NEW_ARRAY:
|
||||
ArgType arrType = dex.getType(insn.getIndex());
|
||||
return new NewArrayNode(arrType,
|
||||
InsnArg.reg(insn, 0, arrType),
|
||||
InsnArg.reg(insn, 1, ArgType.INT));
|
||||
InsnArg.typeImmutableReg(insn, 1, ArgType.INT));
|
||||
|
||||
case Opcodes.FILL_ARRAY_DATA:
|
||||
return fillArray(insn);
|
||||
@@ -582,6 +583,14 @@ public class InsnDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
private ArgType tryResolveFieldType(FieldInfo igetFld) {
|
||||
FieldNode fieldNode = dex.resolveField(igetFld);
|
||||
if (fieldNode != null) {
|
||||
return fieldNode.getType();
|
||||
}
|
||||
return igetFld.getType();
|
||||
}
|
||||
|
||||
private InsnNode decodeSwitch(DecodedInstruction insn, int offset, boolean packed) {
|
||||
int payloadOffset = insn.getTarget();
|
||||
DecodedInstruction payload = insnArr[payloadOffset];
|
||||
@@ -666,26 +675,34 @@ public class InsnDecoder {
|
||||
|
||||
private InsnNode arrayGet(DecodedInstruction insn, ArgType argType) {
|
||||
InsnNode inode = new InsnNode(InsnType.AGET, 2);
|
||||
inode.setResult(InsnArg.reg(insn, 0, argType));
|
||||
inode.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
|
||||
inode.addArg(InsnArg.reg(insn, 2, ArgType.INT));
|
||||
inode.setResult(InsnArg.typeImmutableIfKnownReg(insn, 0, argType));
|
||||
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType)));
|
||||
inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL));
|
||||
return inode;
|
||||
}
|
||||
|
||||
private InsnNode arrayPut(DecodedInstruction insn, ArgType argType) {
|
||||
InsnNode inode = new InsnNode(InsnType.APUT, 3);
|
||||
inode.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
|
||||
inode.addArg(InsnArg.reg(insn, 2, ArgType.INT));
|
||||
inode.addArg(InsnArg.reg(insn, 0, argType));
|
||||
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 1, ArgType.array(argType)));
|
||||
inode.addArg(InsnArg.reg(insn, 2, ArgType.NARROW_INTEGRAL));
|
||||
inode.addArg(InsnArg.typeImmutableIfKnownReg(insn, 0, argType));
|
||||
return inode;
|
||||
}
|
||||
|
||||
private InsnNode arith(DecodedInstruction insn, ArithOp op, ArgType type) {
|
||||
return new ArithNode(insn, op, type, false);
|
||||
return new ArithNode(insn, op, fixTypeForBitOps(op, type), false);
|
||||
}
|
||||
|
||||
private InsnNode arithLit(DecodedInstruction insn, ArithOp op, ArgType type) {
|
||||
return new ArithNode(insn, op, type, true);
|
||||
return new ArithNode(insn, op, fixTypeForBitOps(op, type), true);
|
||||
}
|
||||
|
||||
private ArgType fixTypeForBitOps(ArithOp op, ArgType type) {
|
||||
if (type == ArgType.INT
|
||||
&& (op == ArithOp.AND || op == ArithOp.OR || op == ArithOp.XOR)) {
|
||||
return ArgType.NARROW_NUMBERS_NO_FLOAT;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private InsnNode neg(DecodedInstruction insn, ArgType type) {
|
||||
|
||||
@@ -66,9 +66,6 @@ public enum InsnType {
|
||||
ONE_ARG,
|
||||
PHI,
|
||||
|
||||
// merge all arguments in one
|
||||
MERGE,
|
||||
|
||||
// TODO: now multidimensional arrays created using Array.newInstance function
|
||||
NEW_MULTIDIM_ARRAY
|
||||
}
|
||||
|
||||
@@ -24,10 +24,11 @@ public final class PhiInsn extends InsnNode {
|
||||
this.blockBinds = new LinkedHashMap<>(predecessors);
|
||||
setResult(InsnArg.reg(regNum, ArgType.UNKNOWN));
|
||||
add(AFlag.DONT_INLINE);
|
||||
add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
|
||||
public RegisterArg bindArg(BlockNode pred) {
|
||||
RegisterArg arg = InsnArg.reg(getResult().getRegNum(), getResult().getType());
|
||||
RegisterArg arg = InsnArg.reg(getResult().getRegNum(), getResult().getInitType());
|
||||
bindArg(arg, pred);
|
||||
return arg;
|
||||
}
|
||||
@@ -62,6 +63,7 @@ public final class PhiInsn extends InsnNode {
|
||||
RegisterArg reg = (RegisterArg) arg;
|
||||
if (super.removeArg(reg)) {
|
||||
blockBinds.remove(reg);
|
||||
reg.getSVar().removeUse(reg);
|
||||
InstructionRemover.fixUsedInPhiFlag(reg);
|
||||
return true;
|
||||
}
|
||||
@@ -78,7 +80,9 @@ public final class PhiInsn extends InsnNode {
|
||||
throw new JadxRuntimeException("Unknown predecessor block by arg " + from + " in PHI: " + this);
|
||||
}
|
||||
if (removeArg(from)) {
|
||||
bindArg((RegisterArg) to, pred);
|
||||
RegisterArg reg = (RegisterArg) to;
|
||||
bindArg(reg, pred);
|
||||
reg.getSVar().setUsedInPhi(this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.nodes.parser.SignatureParser;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public abstract class ArgType {
|
||||
|
||||
public static final ArgType INT = primitive(PrimitiveType.INT);
|
||||
public static final ArgType BOOLEAN = primitive(PrimitiveType.BOOLEAN);
|
||||
public static final ArgType BYTE = primitive(PrimitiveType.BYTE);
|
||||
@@ -28,9 +26,12 @@ public abstract class ArgType {
|
||||
public static final ArgType STRING = object(Consts.CLASS_STRING);
|
||||
public static final ArgType ENUM = object(Consts.CLASS_ENUM);
|
||||
public static final ArgType THROWABLE = object(Consts.CLASS_THROWABLE);
|
||||
public static final ArgType OBJECT_ARRAY = array(OBJECT);
|
||||
|
||||
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_NO_ARRAY = unknown(PrimitiveType.OBJECT);
|
||||
public static final ArgType UNKNOWN_ARRAY = array(UNKNOWN);
|
||||
|
||||
public static final ArgType NARROW = unknown(
|
||||
PrimitiveType.INT, PrimitiveType.FLOAT,
|
||||
@@ -38,11 +39,24 @@ public abstract class ArgType {
|
||||
PrimitiveType.OBJECT, PrimitiveType.ARRAY);
|
||||
|
||||
public static final ArgType NARROW_NUMBERS = unknown(
|
||||
PrimitiveType.BOOLEAN, PrimitiveType.INT, PrimitiveType.FLOAT,
|
||||
PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR);
|
||||
|
||||
public static final ArgType NARROW_INTEGRAL = unknown(
|
||||
PrimitiveType.INT, PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR);
|
||||
|
||||
public static final ArgType NARROW_NUMBERS_NO_BOOL = unknown(
|
||||
PrimitiveType.INT, PrimitiveType.FLOAT,
|
||||
PrimitiveType.BOOLEAN, PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR);
|
||||
PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR);
|
||||
|
||||
public static final ArgType NARROW_NUMBERS_NO_FLOAT = unknown(
|
||||
PrimitiveType.INT, PrimitiveType.BOOLEAN,
|
||||
PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR);
|
||||
|
||||
public static final ArgType WIDE = unknown(PrimitiveType.LONG, PrimitiveType.DOUBLE);
|
||||
|
||||
public static final ArgType INT_FLOAT = unknown(PrimitiveType.INT, PrimitiveType.FLOAT);
|
||||
|
||||
protected int hash;
|
||||
|
||||
private static ArgType primitive(PrimitiveType stype) {
|
||||
@@ -174,6 +188,8 @@ public abstract class ArgType {
|
||||
}
|
||||
|
||||
private static final class GenericType extends ObjectType {
|
||||
private List<ArgType> extendTypes;
|
||||
|
||||
public GenericType(String obj) {
|
||||
super(obj);
|
||||
}
|
||||
@@ -182,6 +198,16 @@ public abstract class ArgType {
|
||||
public boolean isGenericType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ArgType> getExtendTypes() {
|
||||
return extendTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExtendTypes(List<ArgType> extendTypes) {
|
||||
this.extendTypes = extendTypes;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class WildcardType extends ObjectType {
|
||||
@@ -275,7 +301,7 @@ public abstract class ArgType {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + '<' + Utils.arrayToString(generics) + '>';
|
||||
return super.toString() + '<' + Utils.arrayToStr(generics) + '>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,8 +355,9 @@ public abstract class ArgType {
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean internalEquals(Object obj) {
|
||||
return arrayElement.equals(((ArrayArg) obj).arrayElement);
|
||||
boolean internalEquals(Object other) {
|
||||
ArrayArg otherArr = (ArrayArg) other;
|
||||
return this.arrayElement.equals(otherArr.getArrayElement());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -369,14 +396,13 @@ public abstract class ArgType {
|
||||
|
||||
@Override
|
||||
public ArgType selectFirst() {
|
||||
PrimitiveType f = possibleTypes[0];
|
||||
if (contains(PrimitiveType.OBJECT)) {
|
||||
return OBJECT;
|
||||
} else if (contains(PrimitiveType.ARRAY)) {
|
||||
return array(OBJECT);
|
||||
} else {
|
||||
return primitive(f);
|
||||
}
|
||||
if (contains(PrimitiveType.ARRAY)) {
|
||||
return array(OBJECT);
|
||||
}
|
||||
return primitive(possibleTypes[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -389,7 +415,7 @@ public abstract class ArgType {
|
||||
if (possibleTypes.length == PrimitiveType.values().length) {
|
||||
return "?";
|
||||
} else {
|
||||
return '?' + Arrays.toString(possibleTypes);
|
||||
return "?[" + Utils.arrayToStr(possibleTypes) + ']';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -426,6 +452,13 @@ public abstract class ArgType {
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<ArgType> getExtendTypes() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public void setExtendTypes(List<ArgType> extendTypes) {
|
||||
}
|
||||
|
||||
public ArgType getWildcardType() {
|
||||
return null;
|
||||
}
|
||||
@@ -463,110 +496,6 @@ public abstract class ArgType {
|
||||
|
||||
public abstract PrimitiveType[] getPossibleTypes();
|
||||
|
||||
@Nullable
|
||||
public static ArgType merge(@Nullable DexNode dex, ArgType a, ArgType b) {
|
||||
if (a == null || b == null) {
|
||||
return null;
|
||||
}
|
||||
if (a.equals(b)) {
|
||||
return a;
|
||||
}
|
||||
ArgType res = mergeInternal(dex, a, b);
|
||||
if (res == null) {
|
||||
res = mergeInternal(dex, b, a); // swap
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private static ArgType mergeInternal(@Nullable DexNode dex, ArgType a, ArgType b) {
|
||||
if (a == UNKNOWN) {
|
||||
return b;
|
||||
}
|
||||
if (a.isArray()) {
|
||||
return mergeArrays(dex, (ArrayArg) a, b);
|
||||
} else if (b.isArray()) {
|
||||
return mergeArrays(dex, (ArrayArg) b, a);
|
||||
}
|
||||
if (!a.isTypeKnown()) {
|
||||
if (b.isTypeKnown()) {
|
||||
if (a.contains(b.getPrimitiveType())) {
|
||||
return b;
|
||||
}
|
||||
return null;
|
||||
} else {
|
||||
// both types unknown
|
||||
List<PrimitiveType> types = new ArrayList<>();
|
||||
for (PrimitiveType type : a.getPossibleTypes()) {
|
||||
if (b.contains(type)) {
|
||||
types.add(type);
|
||||
}
|
||||
}
|
||||
if (types.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (types.size() == 1) {
|
||||
PrimitiveType nt = types.get(0);
|
||||
if (nt == PrimitiveType.OBJECT || nt == PrimitiveType.ARRAY) {
|
||||
return unknown(nt);
|
||||
} else {
|
||||
return primitive(nt);
|
||||
}
|
||||
} else {
|
||||
return unknown(types.toArray(new PrimitiveType[types.size()]));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (a.isGenericType()) {
|
||||
return a;
|
||||
}
|
||||
if (b.isGenericType()) {
|
||||
return b;
|
||||
}
|
||||
|
||||
if (a.isObject() && b.isObject()) {
|
||||
String aObj = a.getObject();
|
||||
String bObj = b.getObject();
|
||||
if (aObj.equals(bObj)) {
|
||||
return a.getGenericTypes() != null ? a : b;
|
||||
}
|
||||
if (aObj.equals(Consts.CLASS_OBJECT)) {
|
||||
return b;
|
||||
}
|
||||
if (bObj.equals(Consts.CLASS_OBJECT)) {
|
||||
return a;
|
||||
}
|
||||
if (dex == null) {
|
||||
return null;
|
||||
}
|
||||
String obj = dex.root().getClsp().getCommonAncestor(aObj, bObj);
|
||||
return obj == null ? null : object(obj);
|
||||
}
|
||||
if (a.isPrimitive() && b.isPrimitive() && a.getRegCount() == b.getRegCount()) {
|
||||
return primitive(PrimitiveType.getSmaller(a.getPrimitiveType(), b.getPrimitiveType()));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ArgType mergeArrays(DexNode dex, ArrayArg array, ArgType b) {
|
||||
if (b.isArray()) {
|
||||
ArgType ea = array.getArrayElement();
|
||||
ArgType eb = b.getArrayElement();
|
||||
if (ea.isPrimitive() && eb.isPrimitive()) {
|
||||
return OBJECT;
|
||||
}
|
||||
ArgType res = merge(dex, ea, eb);
|
||||
return res == null ? null : array(res);
|
||||
}
|
||||
if (b.contains(PrimitiveType.ARRAY)) {
|
||||
return array;
|
||||
}
|
||||
if (b.equals(OBJECT)) {
|
||||
return OBJECT;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isCastNeeded(DexNode dex, ArgType from, ArgType to) {
|
||||
if (from.equals(to)) {
|
||||
return false;
|
||||
@@ -578,14 +507,62 @@ public abstract class ArgType {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isInstanceOf(DexNode dex, ArgType type, ArgType of) {
|
||||
public static boolean isInstanceOf(RootNode root, ArgType type, ArgType of) {
|
||||
if (type.equals(of)) {
|
||||
return true;
|
||||
}
|
||||
if (!type.isObject() || !of.isObject()) {
|
||||
return false;
|
||||
}
|
||||
return dex.root().getClsp().isImplements(type.getObject(), of.getObject());
|
||||
return root.getClsp().isImplements(type.getObject(), of.getObject());
|
||||
}
|
||||
|
||||
public static boolean isClsKnown(RootNode root, ArgType cls) {
|
||||
if (cls.isObject()) {
|
||||
return root.getClsp().isClsKnown(cls.getObject());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean canBeObject() {
|
||||
return isObject() || (!isTypeKnown() && contains(PrimitiveType.OBJECT));
|
||||
}
|
||||
|
||||
public boolean canBeArray() {
|
||||
return isArray() || (!isTypeKnown() && contains(PrimitiveType.ARRAY));
|
||||
}
|
||||
|
||||
public boolean canBePrimitive(PrimitiveType primitiveType) {
|
||||
return (isPrimitive() && getPrimitiveType() == primitiveType)
|
||||
|| (!isTypeKnown() && contains(primitiveType));
|
||||
}
|
||||
|
||||
public static ArgType convertFromPrimitiveType(PrimitiveType primitiveType) {
|
||||
switch (primitiveType) {
|
||||
case BOOLEAN:
|
||||
return BOOLEAN;
|
||||
case CHAR:
|
||||
return CHAR;
|
||||
case BYTE:
|
||||
return BYTE;
|
||||
case SHORT:
|
||||
return SHORT;
|
||||
case INT:
|
||||
return INT;
|
||||
case FLOAT:
|
||||
return FLOAT;
|
||||
case LONG:
|
||||
return LONG;
|
||||
case DOUBLE:
|
||||
return DOUBLE;
|
||||
case OBJECT:
|
||||
return OBJECT;
|
||||
case ARRAY:
|
||||
return OBJECT_ARRAY;
|
||||
case VOID:
|
||||
return ArgType.VOID;
|
||||
}
|
||||
return OBJECT;
|
||||
}
|
||||
|
||||
public static ArgType parse(String type) {
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class CodeVar {
|
||||
private String name;
|
||||
private ArgType type; // before type inference can be null and set only for immutable types
|
||||
private List<SSAVar> ssaVars = new ArrayList<>(3);
|
||||
|
||||
private boolean isFinal;
|
||||
private boolean isThis;
|
||||
private boolean isDeclared;
|
||||
|
||||
public static CodeVar fromMthArg(RegisterArg mthArg) {
|
||||
CodeVar var = new CodeVar();
|
||||
var.setType(mthArg.getInitType());
|
||||
var.setName(mthArg.getName());
|
||||
var.setDeclared(true);
|
||||
var.setThis(mthArg.isThis());
|
||||
var.setSsaVars(Collections.singletonList(new SSAVar(mthArg.getRegNum(), 0, mthArg)));
|
||||
return var;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public ArgType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(ArgType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public List<SSAVar> getSsaVars() {
|
||||
return ssaVars;
|
||||
}
|
||||
|
||||
public void addSsaVar(SSAVar ssaVar) {
|
||||
if (!ssaVars.contains(ssaVar)) {
|
||||
ssaVars.add(ssaVar);
|
||||
}
|
||||
}
|
||||
|
||||
public void setSsaVars(List<SSAVar> ssaVars) {
|
||||
this.ssaVars = ssaVars;
|
||||
}
|
||||
|
||||
public boolean isFinal() {
|
||||
return isFinal;
|
||||
}
|
||||
|
||||
public void setFinal(boolean aFinal) {
|
||||
isFinal = aFinal;
|
||||
}
|
||||
|
||||
public boolean isThis() {
|
||||
return isThis;
|
||||
}
|
||||
|
||||
public void setThis(boolean aThis) {
|
||||
isThis = aThis;
|
||||
}
|
||||
|
||||
public boolean isDeclared() {
|
||||
return isDeclared;
|
||||
}
|
||||
|
||||
public void setDeclared(boolean declared) {
|
||||
isDeclared = declared;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge flags with OR operator
|
||||
*/
|
||||
public void mergeFlagsFrom(CodeVar other) {
|
||||
if (other.isDeclared()) {
|
||||
setDeclared(true);
|
||||
}
|
||||
if (other.isThis()) {
|
||||
setThis(true);
|
||||
}
|
||||
if (other.isFinal()) {
|
||||
setFinal(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (isFinal ? "final " : "") + type + ' ' + name;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package jadx.core.dex.instructions.args;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
// TODO: don't extend RegisterArg (now used as a result of instruction)
|
||||
public final class FieldArg extends RegisterArg {
|
||||
@@ -13,7 +14,7 @@ public final class FieldArg extends RegisterArg {
|
||||
private final InsnArg instArg;
|
||||
|
||||
public FieldArg(FieldInfo field, @Nullable InsnArg reg) {
|
||||
super(-1);
|
||||
super(-1, field.getType());
|
||||
this.instArg = reg;
|
||||
this.field = field;
|
||||
}
|
||||
@@ -41,8 +42,18 @@ public final class FieldArg extends RegisterArg {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setType(ArgType type) {
|
||||
this.type = type;
|
||||
public ArgType getType() {
|
||||
return this.field.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgType getInitType() {
|
||||
return this.field.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setType(ArgType newType) {
|
||||
throw new JadxRuntimeException("Can't set type for FieldArg");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -31,12 +31,27 @@ public abstract class InsnArg extends Typed {
|
||||
return reg(InsnUtils.getArg(insn, argNum), type);
|
||||
}
|
||||
|
||||
public static TypeImmutableArg typeImmutableReg(int regNum, ArgType type) {
|
||||
return new TypeImmutableArg(regNum, type);
|
||||
public static RegisterArg typeImmutableIfKnownReg(DecodedInstruction insn, int argNum, ArgType type) {
|
||||
if (type.isTypeKnown()) {
|
||||
return typeImmutableReg(InsnUtils.getArg(insn, argNum), type);
|
||||
}
|
||||
return reg(InsnUtils.getArg(insn, argNum), type);
|
||||
}
|
||||
|
||||
public static RegisterArg typeImmutableReg(DecodedInstruction insn, int argNum, ArgType type) {
|
||||
return typeImmutableReg(InsnUtils.getArg(insn, argNum), type);
|
||||
}
|
||||
|
||||
public static RegisterArg typeImmutableReg(int regNum, ArgType type) {
|
||||
return reg(regNum, type, true);
|
||||
}
|
||||
|
||||
public static RegisterArg reg(int regNum, ArgType type, boolean typeImmutable) {
|
||||
return typeImmutable ? new TypeImmutableArg(regNum, type) : new RegisterArg(regNum, type);
|
||||
RegisterArg reg = new RegisterArg(regNum, type);
|
||||
if (typeImmutable) {
|
||||
reg.add(AFlag.IMMUTABLE_TYPE);
|
||||
}
|
||||
return reg;
|
||||
}
|
||||
|
||||
public static LiteralArg lit(long literal, ArgType type) {
|
||||
@@ -142,4 +157,8 @@ public abstract class InsnArg extends Typed {
|
||||
public boolean isThis() {
|
||||
return contains(AFlag.THIS);
|
||||
}
|
||||
|
||||
public InsnArg duplicate() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.core.codegen.TypeGen;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public final class LiteralArg extends InsnArg {
|
||||
@@ -17,9 +19,10 @@ public final class LiteralArg extends InsnArg {
|
||||
} else if (!type.isTypeKnown()
|
||||
&& !type.contains(PrimitiveType.LONG)
|
||||
&& !type.contains(PrimitiveType.DOUBLE)) {
|
||||
ArgType m = ArgType.merge(null, type, ArgType.NARROW_NUMBERS);
|
||||
if (m != null) {
|
||||
type = m;
|
||||
if (value != 1) {
|
||||
type = ArgType.NARROW_NUMBERS_NO_BOOL;
|
||||
} else {
|
||||
type = ArgType.NARROW_NUMBERS;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,10 +65,12 @@ public final class LiteralArg extends InsnArg {
|
||||
return literal == that.literal && getType().equals(that.getType());
|
||||
}
|
||||
|
||||
private static final StringUtils DEF_STRING_UTILS = new StringUtils(new JadxArgs());
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
try {
|
||||
String value = TypeGen.literalToString(literal, getType());
|
||||
String value = TypeGen.literalToString(literal, getType(), DEF_STRING_UTILS);
|
||||
if (getType().equals(ArgType.BOOLEAN) && (value.equals("true") || value.equals("false"))) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -3,27 +3,26 @@ package jadx.core.dex.instructions.args;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.PhiInsn;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class RegisterArg extends InsnArg implements Named {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RegisterArg.class);
|
||||
public static final String THIS_ARG_NAME = "this";
|
||||
|
||||
protected final int regNum;
|
||||
// not null after SSATransform pass
|
||||
private SSAVar sVar;
|
||||
|
||||
public RegisterArg(int rn) {
|
||||
this.regNum = rn;
|
||||
}
|
||||
|
||||
public RegisterArg(int rn, ArgType type) {
|
||||
this.type = type;
|
||||
this.type = type; // initial type, not changing, can be unknown
|
||||
this.regNum = rn;
|
||||
}
|
||||
|
||||
@@ -36,12 +35,50 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setType(ArgType newType) {
|
||||
if (sVar == null) {
|
||||
throw new JadxRuntimeException("Can't change type for register without SSA variable: " + this);
|
||||
}
|
||||
if (contains(AFlag.IMMUTABLE_TYPE)) {
|
||||
if (!type.isTypeKnown()) {
|
||||
throw new JadxRuntimeException("Unknown immutable type '" + type + "' in " + this);
|
||||
}
|
||||
if (!type.equals(newType)) {
|
||||
LOG.warn("JADX WARNING: Can't change immutable type from '{}' to '{}' for {}", type, newType, this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
sVar.setType(newType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArgType getType() {
|
||||
if (sVar != null) {
|
||||
return sVar.getTypeInfo().getType();
|
||||
}
|
||||
LOG.warn("Register type unknown, SSA variable not initialized: r{}", regNum);
|
||||
return type;
|
||||
}
|
||||
|
||||
public ArgType getInitType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTypeImmutable() {
|
||||
return contains(AFlag.IMMUTABLE_TYPE) || (sVar != null && sVar.contains(AFlag.IMMUTABLE_TYPE));
|
||||
}
|
||||
|
||||
public SSAVar getSVar() {
|
||||
return sVar;
|
||||
}
|
||||
|
||||
void setSVar(@NotNull SSAVar sVar) {
|
||||
this.sVar = sVar;
|
||||
if (contains(AFlag.IMMUTABLE_TYPE)) {
|
||||
sVar.add(AFlag.IMMUTABLE_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
@@ -68,39 +105,13 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
return n.equals(((Named) arg).getName());
|
||||
}
|
||||
|
||||
public void mergeName(InsnArg arg) {
|
||||
if (arg instanceof Named) {
|
||||
Named otherArg = (Named) arg;
|
||||
String otherName = otherArg.getName();
|
||||
String name = getName();
|
||||
if (!Objects.equals(name, otherName)) {
|
||||
if (name == null) {
|
||||
setName(otherName);
|
||||
} else if (otherName == null) {
|
||||
otherArg.setName(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setType(ArgType type) {
|
||||
if (sVar != null) {
|
||||
sVar.setType(type);
|
||||
}
|
||||
}
|
||||
|
||||
public void mergeDebugInfo(ArgType type, String name) {
|
||||
setType(type);
|
||||
setName(name);
|
||||
}
|
||||
|
||||
public RegisterArg duplicate() {
|
||||
return duplicate(getRegNum(), sVar);
|
||||
}
|
||||
|
||||
public RegisterArg duplicate(int regNum, SSAVar sVar) {
|
||||
RegisterArg dup = new RegisterArg(regNum, getType());
|
||||
public RegisterArg duplicate(int regNum, @Nullable SSAVar sVar) {
|
||||
RegisterArg dup = new RegisterArg(regNum, getInitType());
|
||||
if (sVar != null) {
|
||||
dup.setSVar(sVar);
|
||||
}
|
||||
@@ -110,8 +121,6 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
|
||||
/**
|
||||
* Return constant value from register assign or null if not constant
|
||||
*
|
||||
* @return LiteralArg, String or ArgType
|
||||
*/
|
||||
public Object getConstValue(DexNode dex) {
|
||||
InsnNode parInsn = getAssignInsn();
|
||||
@@ -121,6 +130,7 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
return InsnUtils.getConstValueByInsn(dex, parInsn);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public InsnNode getAssignInsn() {
|
||||
if (sVar == null) {
|
||||
return null;
|
||||
@@ -128,22 +138,23 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
return sVar.getAssign().getParentInsn();
|
||||
}
|
||||
|
||||
public InsnNode getPhiAssignInsn() {
|
||||
PhiInsn usePhi = sVar.getUsedInPhi();
|
||||
if (usePhi != null) {
|
||||
return usePhi;
|
||||
}
|
||||
InsnNode parent = sVar.getAssign().getParentInsn();
|
||||
if (parent != null && parent.getType() == InsnType.PHI) {
|
||||
return parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean equalRegisterAndType(RegisterArg arg) {
|
||||
return regNum == arg.regNum && type.equals(arg.type);
|
||||
}
|
||||
|
||||
public boolean sameRegAndSVar(InsnArg arg) {
|
||||
if (!arg.isRegister()) {
|
||||
return false;
|
||||
}
|
||||
RegisterArg reg = (RegisterArg) arg;
|
||||
return regNum == reg.getRegNum()
|
||||
&& Objects.equals(sVar, reg.getSVar());
|
||||
}
|
||||
|
||||
public boolean sameCodeVar(RegisterArg arg) {
|
||||
return this.getSVar().getCodeVar() == arg.getSVar().getCodeVar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return regNum;
|
||||
@@ -159,7 +170,6 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
}
|
||||
RegisterArg other = (RegisterArg) obj;
|
||||
return regNum == other.regNum
|
||||
&& type.equals(other.type)
|
||||
&& Objects.equals(sVar, other.getSVar());
|
||||
}
|
||||
|
||||
@@ -169,13 +179,19 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
sb.append("(r");
|
||||
sb.append(regNum);
|
||||
if (sVar != null) {
|
||||
sb.append('_').append(sVar.getVersion());
|
||||
sb.append('v').append(sVar.getVersion());
|
||||
}
|
||||
if (getName() != null) {
|
||||
sb.append(" '").append(getName()).append('\'');
|
||||
}
|
||||
sb.append(' ');
|
||||
sb.append(type);
|
||||
ArgType type = sVar != null ? getType() : null;
|
||||
if (type != null) {
|
||||
sb.append(' ').append(type);
|
||||
}
|
||||
ArgType initType = getInitType();
|
||||
if (type == null || (!type.equals(initType) && !type.isTypeKnown())) {
|
||||
sb.append(" I:").append(initType);
|
||||
}
|
||||
if (!isAttrStorageEmpty()) {
|
||||
sb.append(' ').append(getAttributesString());
|
||||
}
|
||||
|
||||
@@ -1,31 +1,36 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||
import jadx.core.dex.instructions.PhiInsn;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.visitors.typeinference.TypeInfo;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class SSAVar extends AttrNode {
|
||||
|
||||
private final int regNum;
|
||||
private final int version;
|
||||
private VarName varName;
|
||||
|
||||
private int startUseAddr;
|
||||
private int endUseAddr;
|
||||
|
||||
@NotNull
|
||||
private RegisterArg assign;
|
||||
private final List<RegisterArg> useList = new ArrayList<>(2);
|
||||
@Nullable
|
||||
private PhiInsn usedInPhi;
|
||||
|
||||
private ArgType type;
|
||||
private boolean typeImmutable;
|
||||
private TypeInfo typeInfo = new TypeInfo();
|
||||
|
||||
@Nullable("Set in InitCodeVariables pass")
|
||||
private CodeVar codeVar;
|
||||
|
||||
public SSAVar(int regNum, int v, @NotNull RegisterArg assign) {
|
||||
this.regNum = regNum;
|
||||
@@ -33,48 +38,6 @@ public class SSAVar extends AttrNode {
|
||||
this.assign = assign;
|
||||
|
||||
assign.setSVar(this);
|
||||
startUseAddr = -1;
|
||||
endUseAddr = -1;
|
||||
}
|
||||
|
||||
public int getStartAddr() {
|
||||
if (startUseAddr == -1) {
|
||||
calcUsageAddrRange();
|
||||
}
|
||||
return startUseAddr;
|
||||
}
|
||||
|
||||
public int getEndAddr() {
|
||||
if (endUseAddr == -1) {
|
||||
calcUsageAddrRange();
|
||||
}
|
||||
return endUseAddr;
|
||||
}
|
||||
|
||||
private void calcUsageAddrRange() {
|
||||
int start = Integer.MAX_VALUE;
|
||||
int end = Integer.MIN_VALUE;
|
||||
|
||||
if (assign.getParentInsn() != null) {
|
||||
int insnAddr = assign.getParentInsn().getOffset();
|
||||
if (insnAddr >= 0) {
|
||||
start = Math.min(insnAddr, start);
|
||||
end = Math.max(insnAddr, end);
|
||||
}
|
||||
}
|
||||
for (RegisterArg arg : useList) {
|
||||
if (arg.getParentInsn() != null) {
|
||||
int insnAddr = arg.getParentInsn().getOffset();
|
||||
if (insnAddr >= 0) {
|
||||
start = Math.min(insnAddr, start);
|
||||
end = Math.max(insnAddr, end);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (start != Integer.MAX_VALUE && end != Integer.MIN_VALUE) {
|
||||
startUseAddr = start;
|
||||
endUseAddr = end;
|
||||
}
|
||||
}
|
||||
|
||||
public int getRegNum() {
|
||||
@@ -102,6 +65,14 @@ public class SSAVar extends AttrNode {
|
||||
return useList.size();
|
||||
}
|
||||
|
||||
// must be used only from RegisterArg#setType()
|
||||
void setType(ArgType type) {
|
||||
typeInfo.setType(type);
|
||||
if (codeVar != null) {
|
||||
codeVar.setType(type);
|
||||
}
|
||||
}
|
||||
|
||||
public void use(RegisterArg arg) {
|
||||
if (arg.getSVar() != null) {
|
||||
arg.getSVar().removeUse(arg);
|
||||
@@ -111,12 +82,7 @@ public class SSAVar extends AttrNode {
|
||||
}
|
||||
|
||||
public void removeUse(RegisterArg arg) {
|
||||
for (int i = 0, useListSize = useList.size(); i < useListSize; i++) {
|
||||
if (useList.get(i) == arg) {
|
||||
useList.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
useList.removeIf(registerArg -> registerArg == arg);
|
||||
}
|
||||
|
||||
public void setUsedInPhi(@Nullable PhiInsn usedInPhi) {
|
||||
@@ -139,52 +105,41 @@ public class SSAVar extends AttrNode {
|
||||
return useList.size() + usedInPhi.getResult().getSVar().getUseCount();
|
||||
}
|
||||
|
||||
public void setType(ArgType type) {
|
||||
ArgType acceptedType;
|
||||
if (typeImmutable) {
|
||||
// don't change type, just update types in useList
|
||||
acceptedType = this.type;
|
||||
} else {
|
||||
acceptedType = type;
|
||||
this.type = acceptedType;
|
||||
}
|
||||
assign.type = acceptedType;
|
||||
for (int i = 0, useListSize = useList.size(); i < useListSize; i++) {
|
||||
useList.get(i).type = acceptedType;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTypeImmutable(ArgType type) {
|
||||
setType(type);
|
||||
this.typeImmutable = true;
|
||||
}
|
||||
|
||||
public boolean isTypeImmutable() {
|
||||
return typeImmutable;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
if (name != null) {
|
||||
if (varName == null) {
|
||||
varName = new VarName();
|
||||
if (codeVar == null) {
|
||||
throw new JadxRuntimeException("CodeVar not initialized for name set in SSAVar: " + this);
|
||||
}
|
||||
varName.setName(name);
|
||||
codeVar.setName(name);
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
if (varName == null) {
|
||||
if (codeVar == null) {
|
||||
return null;
|
||||
}
|
||||
return varName.getName();
|
||||
return codeVar.getName();
|
||||
}
|
||||
|
||||
public VarName getVarName() {
|
||||
return varName;
|
||||
public TypeInfo getTypeInfo() {
|
||||
return typeInfo;
|
||||
}
|
||||
|
||||
public void setVarName(VarName varName) {
|
||||
this.varName = varName;
|
||||
@NotNull
|
||||
public CodeVar getCodeVar() {
|
||||
if (codeVar == null) {
|
||||
throw new JadxRuntimeException("Code variable not set in " + this);
|
||||
}
|
||||
return codeVar;
|
||||
}
|
||||
|
||||
public void setCodeVar(@NotNull CodeVar codeVar) {
|
||||
this.codeVar = codeVar;
|
||||
codeVar.addSsaVar(this);
|
||||
}
|
||||
|
||||
public boolean isCodeVarSet() {
|
||||
return codeVar != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -204,8 +159,54 @@ public class SSAVar extends AttrNode {
|
||||
return 31 * regNum + version;
|
||||
}
|
||||
|
||||
public String toShortString() {
|
||||
return "r" + regNum + 'v' + version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "r" + regNum + '_' + version;
|
||||
return toShortString()
|
||||
+ (StringUtils.notEmpty(getName()) ? " '" + getName() + "' " : "")
|
||||
+ ' ' + typeInfo.getType();
|
||||
}
|
||||
|
||||
public String getDetailedVarInfo(MethodNode mth) {
|
||||
Set<ArgType> types = new HashSet<>();
|
||||
Set<String> names = Collections.emptySet();
|
||||
|
||||
List<RegisterArg> useArgs = new ArrayList<>(1 + useList.size());
|
||||
useArgs.add(assign);
|
||||
useArgs.addAll(useList);
|
||||
|
||||
if (mth.contains(AType.LOCAL_VARS_DEBUG_INFO)) {
|
||||
names = new HashSet<>();
|
||||
for (RegisterArg arg : useArgs) {
|
||||
RegDebugInfoAttr debugInfoAttr = arg.get(AType.REG_DEBUG_INFO);
|
||||
if (debugInfoAttr != null) {
|
||||
names.add(debugInfoAttr.getName());
|
||||
types.add(debugInfoAttr.getRegType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (RegisterArg arg : useArgs) {
|
||||
ArgType initType = arg.getInitType();
|
||||
if (initType.isTypeKnown()) {
|
||||
types.add(initType);
|
||||
}
|
||||
ArgType type = arg.getType();
|
||||
if (type.isTypeKnown()) {
|
||||
types.add(type);
|
||||
}
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('r').append(regNum).append('v').append(version);
|
||||
if (!names.isEmpty()) {
|
||||
sb.append(", names: ").append(names);
|
||||
}
|
||||
if (!types.isEmpty()) {
|
||||
sb.append(", types: ").append(types);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class TypeImmutableArg extends RegisterArg {
|
||||
|
||||
public TypeImmutableArg(int rn, ArgType type) {
|
||||
super(rn, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTypeImmutable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setType(ArgType type) {
|
||||
// not allowed
|
||||
}
|
||||
|
||||
@Override
|
||||
void setSVar(@NotNull SSAVar sVar) {
|
||||
sVar.setTypeImmutable(type);
|
||||
super.setSVar(sVar);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package jadx.core.dex.instructions.args;
|
||||
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
|
||||
public abstract class Typed extends AttrNode {
|
||||
|
||||
@@ -18,17 +17,4 @@ public abstract class Typed extends AttrNode {
|
||||
public boolean isTypeImmutable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean merge(DexNode dex, ArgType newType) {
|
||||
ArgType m = ArgType.merge(dex, type, newType);
|
||||
if (m != null && !m.equals(type)) {
|
||||
setType(m);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean merge(DexNode dex, InsnArg arg) {
|
||||
return merge(dex, arg.getType());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,11 +46,10 @@ public class ConstructorInsn extends InsnNode implements CallMthInterface {
|
||||
instanceArg.getSVar().setAssign(instanceArg);
|
||||
}
|
||||
instanceArg.getSVar().removeUse(instanceArg);
|
||||
for (int i = 1; i < invoke.getArgsCount(); i++) {
|
||||
int argsCount = invoke.getArgsCount();
|
||||
for (int i = 1; i < argsCount; i++) {
|
||||
addArg(invoke.getArg(i));
|
||||
}
|
||||
offset = invoke.getOffset();
|
||||
setSourceLine(invoke.getSourceLine());
|
||||
}
|
||||
|
||||
public ConstructorInsn(MethodInfo callMth, CallType callType, RegisterArg instanceArg) {
|
||||
|
||||
@@ -15,10 +15,6 @@ public final class TernaryInsn extends InsnNode {
|
||||
|
||||
private IfCondition condition;
|
||||
|
||||
public TernaryInsn(IfCondition condition, RegisterArg result) {
|
||||
this(condition, result, LiteralArg.TRUE, LiteralArg.FALSE);
|
||||
}
|
||||
|
||||
public TernaryInsn(IfCondition condition, RegisterArg result, InsnArg th, InsnArg els) {
|
||||
super(InsnType.TERNARY, 2);
|
||||
setResult(result);
|
||||
|
||||
@@ -231,7 +231,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
return;
|
||||
}
|
||||
if (fileName.contains("$")
|
||||
&& fileName.endsWith("$" + name)) {
|
||||
&& fileName.endsWith('$' + name)) {
|
||||
return;
|
||||
}
|
||||
ClassInfo parentClass = clsInfo.getTopParentClass();
|
||||
|
||||
@@ -98,6 +98,10 @@ public class DexNode implements IDexNode {
|
||||
|
||||
@Nullable
|
||||
public ClassNode resolveClass(ClassInfo clsInfo) {
|
||||
ClassNode classNode = resolveClassLocal(clsInfo);
|
||||
if (classNode != null) {
|
||||
return classNode;
|
||||
}
|
||||
return root.resolveClass(clsInfo);
|
||||
}
|
||||
|
||||
@@ -109,7 +113,6 @@ public class DexNode implements IDexNode {
|
||||
return resolveClass(ClassInfo.fromType(root, type));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Nullable
|
||||
public MethodNode resolveMethod(@NotNull MethodInfo mth) {
|
||||
ClassNode cls = resolveClass(mth.getDeclClass());
|
||||
@@ -149,7 +152,6 @@ public class DexNode implements IDexNode {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Nullable
|
||||
public FieldNode resolveField(FieldInfo field) {
|
||||
ClassNode cls = resolveClass(field.getDeclClass());
|
||||
@@ -194,10 +196,16 @@ public class DexNode implements IDexNode {
|
||||
// DexBuffer wrappers
|
||||
|
||||
public String getString(int index) {
|
||||
if (index == DexNode.NO_INDEX) {
|
||||
return null;
|
||||
}
|
||||
return dexBuf.strings().get(index);
|
||||
}
|
||||
|
||||
public ArgType getType(int index) {
|
||||
if (index == DexNode.NO_INDEX) {
|
||||
return null;
|
||||
}
|
||||
return ArgType.parse(getString(dexBuf.typeIds().get(index)));
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ public class FieldNode extends LineAttrNode implements ICodeNode {
|
||||
private final FieldInfo fieldInfo;
|
||||
private AccessInfo accFlags;
|
||||
|
||||
private ArgType type; // store signature
|
||||
private ArgType type;
|
||||
|
||||
public FieldNode(ClassNode cls, Field field) {
|
||||
this(cls, FieldInfo.fromDex(cls.dex(), field.getFieldIndex()),
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.Objects;
|
||||
|
||||
import com.android.dx.io.instructions.DecodedInstruction;
|
||||
import com.rits.cloning.Cloner;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
@@ -19,6 +20,7 @@ import jadx.core.dex.instructions.args.NamedArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.InstructionRemover;
|
||||
import jadx.core.utils.Utils;
|
||||
|
||||
public class InsnNode extends LineAttrNode {
|
||||
@@ -53,16 +55,27 @@ public class InsnNode extends LineAttrNode {
|
||||
return insn;
|
||||
}
|
||||
|
||||
public void setResult(RegisterArg res) {
|
||||
public void setResult(@Nullable RegisterArg res) {
|
||||
if (res != null) {
|
||||
res.setParentInsn(this);
|
||||
SSAVar ssaVar = res.getSVar();
|
||||
if (ssaVar != null) {
|
||||
ssaVar.setAssign(res);
|
||||
}
|
||||
}
|
||||
this.result = res;
|
||||
}
|
||||
|
||||
public void addArg(InsnArg arg) {
|
||||
arg.setParentInsn(this);
|
||||
arguments.add(arg);
|
||||
arg.setParentInsn(this);
|
||||
if (arg.isRegister()) {
|
||||
RegisterArg reg = (RegisterArg) arg;
|
||||
SSAVar ssaVar = reg.getSVar();
|
||||
if (ssaVar != null) {
|
||||
ssaVar.use(reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public InsnType getType() {
|
||||
@@ -110,6 +123,7 @@ public class InsnNode extends LineAttrNode {
|
||||
for (int i = 0; i < count; i++) {
|
||||
InsnArg arg = arguments.get(i);
|
||||
if (arg == from) {
|
||||
InstructionRemover.unbindArgUsage(null, arg);
|
||||
setArg(i, to);
|
||||
return true;
|
||||
}
|
||||
@@ -125,10 +139,7 @@ public class InsnNode extends LineAttrNode {
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (arg == arguments.get(i)) {
|
||||
arguments.remove(i);
|
||||
if (arg instanceof RegisterArg) {
|
||||
RegisterArg reg = (RegisterArg) arg;
|
||||
reg.getSVar().removeUse(reg);
|
||||
}
|
||||
InstructionRemover.unbindArgUsage(null, arg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.instructions.args.TypeImmutableArg;
|
||||
import jadx.core.dex.nodes.parser.SignatureParser;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||
@@ -54,17 +53,18 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
private AccessInfo accFlags;
|
||||
|
||||
private final Method methodData;
|
||||
private final boolean methodIsVirtual;
|
||||
|
||||
private boolean noCode;
|
||||
private int regsCount;
|
||||
private InsnNode[] instructions;
|
||||
private int codeSize;
|
||||
private int debugInfoOffset;
|
||||
private boolean noCode;
|
||||
private boolean methodIsVirtual;
|
||||
|
||||
private ArgType retType;
|
||||
private RegisterArg thisArg;
|
||||
private List<RegisterArg> argsList;
|
||||
private List<SSAVar> sVars = Collections.emptyList();
|
||||
private List<SSAVar> sVars;
|
||||
private Map<ArgType, List<ArgType>> genericMap;
|
||||
|
||||
private List<BlockNode> blocks;
|
||||
@@ -72,8 +72,8 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
private List<BlockNode> exitBlocks;
|
||||
|
||||
private Region region;
|
||||
private List<ExceptionHandler> exceptionHandlers = Collections.emptyList();
|
||||
private List<LoopInfo> loops = Collections.emptyList();
|
||||
private List<ExceptionHandler> exceptionHandlers;
|
||||
private List<LoopInfo> loops;
|
||||
|
||||
public MethodNode(ClassNode classNode, Method mthData, boolean isVirtual) {
|
||||
this.mthInfo = MethodInfo.fromDex(classNode.dex(), mthData.getMethodIndex());
|
||||
@@ -82,6 +82,26 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
this.noCode = mthData.getCodeOffset() == 0;
|
||||
this.methodData = noCode ? null : mthData;
|
||||
this.methodIsVirtual = isVirtual;
|
||||
unload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
if (noCode) {
|
||||
return;
|
||||
}
|
||||
retType = null;
|
||||
thisArg = null;
|
||||
argsList = Collections.emptyList();
|
||||
sVars = Collections.emptyList();
|
||||
genericMap = null;
|
||||
instructions = null;
|
||||
blocks = null;
|
||||
enterBlock = null;
|
||||
exitBlocks = null;
|
||||
region = null;
|
||||
exceptionHandlers = Collections.emptyList();
|
||||
loops = Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -148,21 +168,6 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
if (noCode) {
|
||||
return;
|
||||
}
|
||||
instructions = null;
|
||||
blocks = null;
|
||||
enterBlock = null;
|
||||
exitBlocks = null;
|
||||
exceptionHandlers = Collections.emptyList();
|
||||
sVars.clear();
|
||||
region = null;
|
||||
loops = Collections.emptyList();
|
||||
}
|
||||
|
||||
private boolean parseSignature() {
|
||||
SignatureParser sp = SignatureParser.fromNode(this);
|
||||
if (sp == null) {
|
||||
@@ -214,8 +219,9 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
if (accFlags.isStatic()) {
|
||||
thisArg = null;
|
||||
} else {
|
||||
TypeImmutableArg arg = InsnArg.typeImmutableReg(pos - 1, parentClass.getClassInfo().getType());
|
||||
RegisterArg arg = InsnArg.reg(pos - 1, parentClass.getClassInfo().getType());
|
||||
arg.add(AFlag.THIS);
|
||||
arg.add(AFlag.IMMUTABLE_TYPE);
|
||||
thisArg = arg;
|
||||
}
|
||||
if (args.isEmpty()) {
|
||||
@@ -224,7 +230,10 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
}
|
||||
argsList = new ArrayList<>(args.size());
|
||||
for (ArgType arg : args) {
|
||||
argsList.add(InsnArg.typeImmutableReg(pos, arg));
|
||||
RegisterArg regArg = InsnArg.reg(pos, arg);
|
||||
regArg.add(AFlag.METHOD_ARGUMENT);
|
||||
regArg.add(AFlag.IMMUTABLE_TYPE);
|
||||
argsList.add(regArg);
|
||||
pos += arg.getRegCount();
|
||||
}
|
||||
}
|
||||
@@ -239,9 +248,8 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
return argsList;
|
||||
}
|
||||
|
||||
public RegisterArg removeFirstArgument() {
|
||||
public void skipFirstArgument() {
|
||||
this.add(AFlag.SKIP_FIRST_ARG);
|
||||
return argsList.remove(0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -554,7 +562,7 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
&& !parentClass.getAccessFlags().isStatic()) {
|
||||
ClassNode outerCls = parentClass.getParentClass();
|
||||
if (argsList != null && !argsList.isEmpty()
|
||||
&& argsList.get(0).getType().equals(outerCls.getClassInfo().getType())) {
|
||||
&& argsList.get(0).getInitType().equals(outerCls.getClassInfo().getType())) {
|
||||
defaultArgCount = 1;
|
||||
}
|
||||
}
|
||||
@@ -575,6 +583,10 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
return debugInfoOffset;
|
||||
}
|
||||
|
||||
public SSAVar makeNewSVar(int regNum, @NotNull RegisterArg assignArg) {
|
||||
return makeNewSVar(regNum, getNextSVarVersion(regNum), assignArg);
|
||||
}
|
||||
|
||||
public SSAVar makeNewSVar(int regNum, int version, @NotNull RegisterArg assignArg) {
|
||||
SSAVar var = new SSAVar(regNum, version, assignArg);
|
||||
if (sVars.isEmpty()) {
|
||||
@@ -636,8 +648,13 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
return "method";
|
||||
}
|
||||
|
||||
public void addWarn(String errStr) {
|
||||
ErrorsCounter.methodWarn(this, errStr);
|
||||
public void addWarn(String warnStr) {
|
||||
ErrorsCounter.methodWarn(this, warnStr);
|
||||
}
|
||||
|
||||
public void addComment(String commentStr) {
|
||||
addAttr(AType.COMMENTS, commentStr);
|
||||
LOG.info("{} in {}", commentStr, this);
|
||||
}
|
||||
|
||||
public void addError(String errStr, Exception e) {
|
||||
|
||||
@@ -18,6 +18,7 @@ import jadx.core.dex.info.ConstStorage;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.InfoStorage;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.visitors.typeinference.TypeUpdate;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.android.AndroidResourcesUtils;
|
||||
@@ -35,6 +36,7 @@ public class RootNode {
|
||||
private final StringUtils stringUtils;
|
||||
private final ConstStorage constValues;
|
||||
private final InfoStorage infoStorage = new InfoStorage();
|
||||
private final TypeUpdate typeUpdate;
|
||||
|
||||
private ClspGraph clsp;
|
||||
private List<DexNode> dexNodes;
|
||||
@@ -47,6 +49,7 @@ public class RootNode {
|
||||
this.args = args;
|
||||
this.stringUtils = new StringUtils(args);
|
||||
this.constValues = new ConstStorage(args);
|
||||
this.typeUpdate = new TypeUpdate(this);
|
||||
}
|
||||
|
||||
public void load(List<InputFile> inputFiles) {
|
||||
@@ -222,4 +225,8 @@ public class RootNode {
|
||||
public JadxArgs getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
public TypeUpdate getTypeUpdate() {
|
||||
return typeUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ public class SignatureParser {
|
||||
list.add(type);
|
||||
}
|
||||
} while (type != null && !lookAhead('>'));
|
||||
return list.toArray(new ArgType[list.size()]);
|
||||
return list.toArray(new ArgType[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -64,7 +64,9 @@ public final class SwitchRegion extends AbstractRegion implements IBranchRegion
|
||||
public List<IContainer> getBranches() {
|
||||
List<IContainer> branches = new ArrayList<>(cases.size() + 1);
|
||||
branches.addAll(cases);
|
||||
branches.add(defCase);
|
||||
if (defCase != null) {
|
||||
branches.add(defCase);
|
||||
}
|
||||
return Collections.unmodifiableList(branches);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public final class IfCondition {
|
||||
@@ -54,10 +55,11 @@ public final class IfCondition {
|
||||
}
|
||||
|
||||
public static IfCondition fromIfBlock(BlockNode header) {
|
||||
if (header == null) {
|
||||
InsnNode lastInsn = BlockUtils.getLastInsn(header);
|
||||
if (lastInsn == null) {
|
||||
return null;
|
||||
}
|
||||
return fromIfNode((IfNode) header.getInstructions().get(0));
|
||||
return fromIfNode((IfNode) lastInsn);
|
||||
}
|
||||
|
||||
public static IfCondition fromIfNode(IfNode insn) {
|
||||
|
||||
@@ -8,8 +8,9 @@ import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IBranchRegion;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
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 {
|
||||
|
||||
@@ -21,9 +22,6 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
|
||||
|
||||
public IfRegion(IRegion parent, BlockNode header) {
|
||||
super(parent);
|
||||
if (header.getInstructions().size() != 1) {
|
||||
throw new JadxRuntimeException("Expected only one instruction in 'if' header");
|
||||
}
|
||||
this.header = header;
|
||||
this.condition = IfCondition.fromIfBlock(header);
|
||||
}
|
||||
@@ -74,10 +72,8 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
|
||||
}
|
||||
|
||||
public int getSourceLine() {
|
||||
if (header.getInstructions().isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
return header.getInstructions().get(0).getSourceLine();
|
||||
InsnNode lastInsn = BlockUtils.getLastInsn(header);
|
||||
return lastInsn == null ? 0 : lastInsn.getSourceLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -130,6 +126,6 @@ public final class IfRegion extends AbstractRegion implements IBranchRegion {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IF " + header + " then (" + thenRegion + ") else (" + elseRegion + ')';
|
||||
return "IF " + header + " THEN:" + thenRegion + " ELSE:" + elseRegion;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ public final class ForEachLoop extends LoopType {
|
||||
public ForEachLoop(RegisterArg varArg, InsnArg iterableArg) {
|
||||
this.varArg = varArg;
|
||||
this.iterableArg = iterableArg;
|
||||
|
||||
// will be declared at codegen
|
||||
varArg.getSVar().getCodeVar().setDeclared(true);
|
||||
}
|
||||
|
||||
public RegisterArg getVarArg() {
|
||||
|
||||
@@ -15,6 +15,7 @@ import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.regions.AbstractRegion;
|
||||
import jadx.core.dex.regions.conditions.IfCondition;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
|
||||
public final class LoopRegion extends AbstractRegion {
|
||||
|
||||
@@ -76,7 +77,7 @@ public final class LoopRegion extends AbstractRegion {
|
||||
}
|
||||
|
||||
private IfNode getIfInsn() {
|
||||
return (IfNode) conditionBlock.getInstructions().get(0);
|
||||
return (IfNode) BlockUtils.getLastInsn(conditionBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,13 +133,8 @@ public final class LoopRegion extends AbstractRegion {
|
||||
}
|
||||
|
||||
public int getConditionSourceLine() {
|
||||
if (conditionBlock != null) {
|
||||
List<InsnNode> condInsns = conditionBlock.getInstructions();
|
||||
if (!condInsns.isEmpty()) {
|
||||
return condInsns.get(0).getSourceLine();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
InsnNode lastInsn = BlockUtils.getLastInsn(conditionBlock);
|
||||
return lastInsn == null ? 0 : lastInsn.getSourceLine();
|
||||
}
|
||||
|
||||
public LoopType getType() {
|
||||
|
||||
@@ -39,6 +39,7 @@ public class ExceptionHandler {
|
||||
|
||||
/**
|
||||
* Add exception type to catch block
|
||||
*
|
||||
* @param type - null for 'all' or 'Throwable' handler
|
||||
*/
|
||||
public void addCatchType(@Nullable ClassInfo type) {
|
||||
|
||||
@@ -70,7 +70,7 @@ public class TryCatchBlock {
|
||||
for (BlockNode block : handler.getBlocks()) {
|
||||
// skip synthetic loop exit blocks
|
||||
BlockUtils.skipPredSyntheticPaths(block);
|
||||
block.add(AFlag.SKIP);
|
||||
block.add(AFlag.REMOVE);
|
||||
ExcHandlerAttr excHandlerAttr = block.get(AType.EXC_HANDLER);
|
||||
if (excHandlerAttr != null
|
||||
&& excHandlerAttr.getHandler().equals(handler)) {
|
||||
|
||||
@@ -47,10 +47,7 @@ public class ClassModifier extends AbstractVisitor {
|
||||
for (ClassNode inner : cls.getInnerClasses()) {
|
||||
visit(inner);
|
||||
}
|
||||
if (cls.getAccessFlags().isSynthetic()
|
||||
&& cls.getFields().isEmpty()
|
||||
&& cls.getMethods().isEmpty()
|
||||
&& cls.getInnerClasses().isEmpty()) {
|
||||
if (isEmptySyntheticClass(cls)) {
|
||||
cls.add(AFlag.DONT_GENERATE);
|
||||
return false;
|
||||
}
|
||||
@@ -61,6 +58,13 @@ public class ClassModifier extends AbstractVisitor {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isEmptySyntheticClass(ClassNode cls) {
|
||||
return cls.getAccessFlags().isSynthetic()
|
||||
&& cls.getFields().isEmpty()
|
||||
&& cls.getMethods().isEmpty()
|
||||
&& cls.getInnerClasses().isEmpty();
|
||||
}
|
||||
|
||||
private void markAnonymousClass(ClassNode cls) {
|
||||
if (cls.isAnonymous()) {
|
||||
cls.add(AFlag.ANONYMOUS_CLASS);
|
||||
@@ -126,7 +130,7 @@ public class ClassModifier extends AbstractVisitor {
|
||||
if (!fieldInfo.equals(field.getFieldInfo()) || !putInsn.getArg(0).equals(arg)) {
|
||||
return false;
|
||||
}
|
||||
mth.removeFirstArgument();
|
||||
mth.skipFirstArgument();
|
||||
InstructionRemover.remove(mth, block, insn);
|
||||
// other arg usage -> wrap with IGET insn
|
||||
if (arg.getSVar().getUseCount() != 0) {
|
||||
@@ -180,7 +184,7 @@ public class ClassModifier extends AbstractVisitor {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (argCls.contains(AFlag.DONT_GENERATE)) {
|
||||
if (argCls.contains(AFlag.DONT_GENERATE) || isEmptySyntheticClass(argCls)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -302,15 +306,34 @@ public class ClassModifier extends AbstractVisitor {
|
||||
|
||||
private static void removeEmptyMethods(MethodNode mth) {
|
||||
AccessInfo af = mth.getAccessFlags();
|
||||
// remove public empty constructors
|
||||
// remove public empty constructors (static or default)
|
||||
if (af.isConstructor()
|
||||
&& (af.isPublic() || af.isStatic())
|
||||
&& mth.getArguments(false).isEmpty()
|
||||
&& !mth.contains(AType.JADX_ERROR)) {
|
||||
&& mth.getArguments(false).isEmpty()) {
|
||||
List<BlockNode> bb = mth.getBasicBlocks();
|
||||
if (bb == null || bb.isEmpty() || BlockUtils.isAllBlocksEmpty(bb)) {
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
if (af.isStatic() && mth.getMethodInfo().isClassInit()) {
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
} else {
|
||||
// don't remove default constructor if other constructors exists
|
||||
if (mth.isDefaultConstructor() && !isNonDefaultConstructorExists(mth)) {
|
||||
mth.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isNonDefaultConstructorExists(MethodNode defCtor) {
|
||||
ClassNode parentClass = defCtor.getParentClass();
|
||||
for (MethodNode mth : parentClass.getMethods()) {
|
||||
if (mth != defCtor
|
||||
&& mth.getAccessFlags().isConstructor()
|
||||
&& mth.getMethodInfo().isConstructor()
|
||||
&& !mth.isDefaultConstructor()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
@@ -16,14 +15,24 @@ import jadx.core.dex.instructions.args.PrimitiveType;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.visitors.typeinference.PostTypeInference;
|
||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||
import jadx.core.utils.InstructionRemover;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "Constants Inline",
|
||||
desc = "Inline constant registers into instructions",
|
||||
runAfter = {
|
||||
SSATransform.class,
|
||||
MarkFinallyVisitor.class
|
||||
},
|
||||
runBefore = TypeInferenceVisitor.class
|
||||
)
|
||||
public class ConstInlineVisitor extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
@@ -35,21 +44,23 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
toRemove.clear();
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
if (checkInsn(mth, insn)) {
|
||||
toRemove.add(insn);
|
||||
}
|
||||
checkInsn(mth, insn, toRemove);
|
||||
}
|
||||
InstructionRemover.removeAll(mth, block, toRemove);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean checkInsn(MethodNode mth, InsnNode insn) {
|
||||
if (insn.getType() != InsnType.CONST || insn.contains(AFlag.DONT_INLINE)) {
|
||||
return false;
|
||||
private static void checkInsn(MethodNode mth, InsnNode insn, List<InsnNode> toRemove) {
|
||||
if (insn.contains(AFlag.DONT_INLINE) || insn.contains(AFlag.DONT_GENERATE)) {
|
||||
return;
|
||||
}
|
||||
InsnType insnType = insn.getType();
|
||||
if (insnType != InsnType.CONST && insnType != InsnType.MOVE) {
|
||||
return;
|
||||
}
|
||||
InsnArg arg = insn.getArg(0);
|
||||
if (!arg.isLiteral()) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
long lit = ((LiteralArg) arg).getLiteral();
|
||||
|
||||
@@ -61,14 +72,36 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
||||
assignInsn.add(AFlag.DONT_INLINE);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkForFinallyBlock(sVar)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// all check passed, run replace
|
||||
replaceConst(mth, insn, lit, toRemove);
|
||||
}
|
||||
|
||||
private static boolean checkForFinallyBlock(SSAVar sVar) {
|
||||
List<SSAVar> ssaVars = sVar.getCodeVar().getSsaVars();
|
||||
if (ssaVars.size() <= 1) {
|
||||
return false;
|
||||
}
|
||||
ArgType resType = insn.getResult().getType();
|
||||
// make sure arg has correct type
|
||||
if (!arg.getType().isTypeKnown()) {
|
||||
arg.merge(mth.dex(), resType);
|
||||
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 replaceConst(mth, insn, lit);
|
||||
return countFinallyInsns != 0 && countFinallyInsns != countInsns;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,145 +131,61 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean replaceConst(MethodNode mth, InsnNode constInsn, long literal) {
|
||||
SSAVar sVar = constInsn.getResult().getSVar();
|
||||
List<RegisterArg> use = new ArrayList<>(sVar.getUseList());
|
||||
private static int replaceConst(MethodNode mth, InsnNode constInsn, long literal, List<InsnNode> toRemove) {
|
||||
SSAVar ssaVar = constInsn.getResult().getSVar();
|
||||
List<RegisterArg> useList = new ArrayList<>(ssaVar.getUseList());
|
||||
int replaceCount = 0;
|
||||
for (RegisterArg arg : use) {
|
||||
InsnNode useInsn = arg.getParentInsn();
|
||||
if (useInsn == null
|
||||
|| useInsn.getType() == InsnType.PHI
|
||||
|| useInsn.getType() == InsnType.MERGE) {
|
||||
continue;
|
||||
}
|
||||
LiteralArg litArg;
|
||||
ArgType argType = arg.getType();
|
||||
if (argType.isObject() && literal != 0) {
|
||||
argType = ArgType.NARROW_NUMBERS;
|
||||
}
|
||||
if (use.size() == 1 || arg.isTypeImmutable()) {
|
||||
// arg used only in one place
|
||||
litArg = InsnArg.lit(literal, argType);
|
||||
} else if (useInsn.getType() == InsnType.MOVE
|
||||
&& !useInsn.getResult().getType().isTypeKnown()) {
|
||||
// save type for 'move' instructions (hard to find type in chains of 'move')
|
||||
litArg = InsnArg.lit(literal, argType);
|
||||
} else {
|
||||
// in most cases type not equal arg.getType()
|
||||
// just set unknown type and run type fixer
|
||||
litArg = InsnArg.lit(literal, ArgType.UNKNOWN);
|
||||
}
|
||||
if (useInsn.replaceArg(arg, litArg)) {
|
||||
fixTypes(mth, useInsn, litArg);
|
||||
for (RegisterArg arg : useList) {
|
||||
if (replaceArg(mth, arg, literal, constInsn, toRemove)) {
|
||||
replaceCount++;
|
||||
if (useInsn.getType() == InsnType.RETURN) {
|
||||
useInsn.setSourceLine(constInsn.getSourceLine());
|
||||
}
|
||||
|
||||
FieldNode f = null;
|
||||
ArgType litArgType = litArg.getType();
|
||||
if (litArgType.isTypeKnown()) {
|
||||
f = mth.getParentClass().getConstFieldByLiteralArg(litArg);
|
||||
} else if (litArgType.contains(PrimitiveType.INT)) {
|
||||
f = mth.getParentClass().getConstField((int) literal, false);
|
||||
}
|
||||
if (f != null) {
|
||||
litArg.wrapInstruction(new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
return replaceCount == use.size();
|
||||
if (replaceCount == useList.size()) {
|
||||
toRemove.add(constInsn);
|
||||
}
|
||||
return replaceCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is method similar to PostTypeInference.process method,
|
||||
* but contains some expensive operations needed only after constant inline
|
||||
*/
|
||||
private static void fixTypes(MethodNode mth, InsnNode insn, LiteralArg litArg) {
|
||||
DexNode dex = mth.dex();
|
||||
PostTypeInference.process(mth, insn);
|
||||
switch (insn.getType()) {
|
||||
case CONST:
|
||||
insn.getArg(0).merge(dex, insn.getResult());
|
||||
break;
|
||||
|
||||
case MOVE:
|
||||
insn.getResult().merge(dex, insn.getArg(0));
|
||||
insn.getArg(0).merge(dex, insn.getResult());
|
||||
break;
|
||||
|
||||
case IPUT:
|
||||
case SPUT:
|
||||
IndexInsnNode node = (IndexInsnNode) insn;
|
||||
insn.getArg(0).merge(dex, ((FieldInfo) node.getIndex()).getType());
|
||||
break;
|
||||
|
||||
case IF:
|
||||
InsnArg firstArg = insn.getArg(0);
|
||||
InsnArg secondArg = insn.getArg(1);
|
||||
if (firstArg == litArg) {
|
||||
firstArg.merge(dex, secondArg);
|
||||
} else {
|
||||
secondArg.merge(dex, firstArg);
|
||||
}
|
||||
break;
|
||||
|
||||
case CMP_G:
|
||||
case CMP_L:
|
||||
InsnArg arg0 = insn.getArg(0);
|
||||
InsnArg arg1 = insn.getArg(1);
|
||||
if (arg0 == litArg) {
|
||||
arg0.merge(dex, arg1);
|
||||
} else {
|
||||
arg1.merge(dex, arg0);
|
||||
}
|
||||
break;
|
||||
|
||||
case RETURN:
|
||||
if (insn.getArgsCount() != 0) {
|
||||
insn.getArg(0).merge(dex, mth.getReturnType());
|
||||
}
|
||||
break;
|
||||
|
||||
case INVOKE:
|
||||
InvokeNode inv = (InvokeNode) insn;
|
||||
List<ArgType> types = inv.getCallMth().getArgumentsTypes();
|
||||
int count = insn.getArgsCount();
|
||||
int k = types.size() == count ? 0 : -1;
|
||||
for (int i = 0; i < count; i++) {
|
||||
InsnArg arg = insn.getArg(i);
|
||||
if (!arg.getType().isTypeKnown()) {
|
||||
ArgType type;
|
||||
if (k >= 0) {
|
||||
type = types.get(k);
|
||||
} else {
|
||||
type = mth.getParentClass().getClassInfo().getType();
|
||||
}
|
||||
arg.merge(dex, type);
|
||||
}
|
||||
k++;
|
||||
}
|
||||
break;
|
||||
|
||||
case ARITH:
|
||||
litArg.merge(dex, insn.getResult());
|
||||
break;
|
||||
|
||||
case APUT:
|
||||
case AGET:
|
||||
if (litArg == insn.getArg(1)) {
|
||||
litArg.merge(dex, ArgType.INT);
|
||||
}
|
||||
break;
|
||||
|
||||
case NEW_ARRAY:
|
||||
if (litArg == insn.getArg(0)) {
|
||||
litArg.merge(dex, ArgType.INT);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
private static boolean replaceArg(MethodNode mth, RegisterArg arg, long literal, InsnNode constInsn, List<InsnNode> toRemove) {
|
||||
InsnNode useInsn = arg.getParentInsn();
|
||||
if (useInsn == null) {
|
||||
return false;
|
||||
}
|
||||
InsnType insnType = useInsn.getType();
|
||||
if (insnType == InsnType.PHI) {
|
||||
return false;
|
||||
}
|
||||
ArgType argType = arg.getInitType();
|
||||
if (argType.isObject() && literal != 0) {
|
||||
argType = ArgType.NARROW_NUMBERS;
|
||||
}
|
||||
LiteralArg litArg = InsnArg.lit(literal, argType);
|
||||
if (!useInsn.replaceArg(arg, litArg)) {
|
||||
return false;
|
||||
}
|
||||
// arg replaced, made some optimizations
|
||||
litArg.setType(arg.getInitType());
|
||||
|
||||
FieldNode fieldNode = null;
|
||||
ArgType litArgType = litArg.getType();
|
||||
if (litArgType.isTypeKnown()) {
|
||||
fieldNode = mth.getParentClass().getConstFieldByLiteralArg(litArg);
|
||||
} else if (litArgType.contains(PrimitiveType.INT)) {
|
||||
fieldNode = mth.getParentClass().getConstField((int) literal, false);
|
||||
}
|
||||
if (fieldNode != null) {
|
||||
litArg.wrapInstruction(new IndexInsnNode(InsnType.SGET, fieldNode.getFieldInfo(), 0));
|
||||
}
|
||||
|
||||
if (insnType == InsnType.RETURN) {
|
||||
useInsn.setSourceLine(constInsn.getSourceLine());
|
||||
} else if (insnType == InsnType.MOVE) {
|
||||
try {
|
||||
replaceConst(mth, useInsn, literal, toRemove);
|
||||
} catch (StackOverflowError e) {
|
||||
throw new JadxOverflowException("Stack overflow at const inline visitor");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import jadx.core.codegen.TypeGen;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
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.visitors.ssa.SSATransform;
|
||||
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.InstructionRemover;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "ConstructorVisitor",
|
||||
desc = "Replace invoke with constructor call",
|
||||
runAfter = SSATransform.class,
|
||||
runBefore = TypeInferenceVisitor.class
|
||||
)
|
||||
public class ConstructorVisitor extends AbstractVisitor {
|
||||
@Override
|
||||
public void visit(MethodNode mth) {
|
||||
if (mth.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
replaceInvoke(mth);
|
||||
}
|
||||
|
||||
private static void replaceInvoke(MethodNode mth) {
|
||||
InstructionRemover remover = new InstructionRemover(mth);
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
remover.setBlock(block);
|
||||
int size = block.getInstructions().size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
InsnNode insn = block.getInstructions().get(i);
|
||||
if (insn.getType() == InsnType.INVOKE) {
|
||||
processInvoke(mth, block, i, remover);
|
||||
}
|
||||
}
|
||||
remover.perform();
|
||||
}
|
||||
}
|
||||
|
||||
private static void processInvoke(MethodNode mth, BlockNode block, int indexInBlock, InstructionRemover remover) {
|
||||
ClassNode parentClass = mth.getParentClass();
|
||||
InsnNode insn = block.getInstructions().get(indexInBlock);
|
||||
InvokeNode inv = (InvokeNode) insn;
|
||||
MethodInfo callMth = inv.getCallMth();
|
||||
if (!callMth.isConstructor()) {
|
||||
return;
|
||||
}
|
||||
InsnNode instArgAssignInsn = ((RegisterArg) inv.getArg(0)).getAssignInsn();
|
||||
ConstructorInsn co = new ConstructorInsn(mth, inv);
|
||||
boolean remove = false;
|
||||
if (co.isSuper() && (co.getArgsCount() == 0 || parentClass.isEnum())) {
|
||||
remove = true;
|
||||
} else if (co.isThis() && co.getArgsCount() == 0) {
|
||||
MethodNode defCo = parentClass.searchMethodByShortId(callMth.getShortId());
|
||||
if (defCo == null || defCo.isNoCode()) {
|
||||
// default constructor not implemented
|
||||
remove = true;
|
||||
}
|
||||
}
|
||||
// remove super() call in instance initializer
|
||||
if (parentClass.isAnonymous() && mth.isDefaultConstructor() && co.isSuper()) {
|
||||
remove = true;
|
||||
}
|
||||
if (remove) {
|
||||
remover.add(insn);
|
||||
return;
|
||||
}
|
||||
if (co.isNewInstance()) {
|
||||
InsnNode newInstInsn = removeAssignChain(mth, instArgAssignInsn, remover, InsnType.NEW_INSTANCE);
|
||||
if (newInstInsn != null) {
|
||||
RegisterArg instArg = newInstInsn.getResult();
|
||||
RegisterArg resultArg = co.getResult();
|
||||
if (!resultArg.equals(instArg)) {
|
||||
// replace all usages of 'instArg' with result of this constructor instruction
|
||||
for (RegisterArg useArg : new ArrayList<>(instArg.getSVar().getUseList())) {
|
||||
RegisterArg dup = resultArg.duplicate();
|
||||
InsnNode parentInsn = useArg.getParentInsn();
|
||||
parentInsn.replaceArg(useArg, dup);
|
||||
dup.setParentInsn(parentInsn);
|
||||
resultArg.getSVar().use(dup);
|
||||
}
|
||||
}
|
||||
newInstInsn.setResult(null); // don't unbind result arg on remove
|
||||
}
|
||||
}
|
||||
ConstructorInsn replace = processConstructor(mth, co);
|
||||
if (replace != null) {
|
||||
co = replace;
|
||||
}
|
||||
BlockUtils.replaceInsn(block, indexInBlock, co);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace call of synthetic constructor
|
||||
*/
|
||||
private static ConstructorInsn processConstructor(MethodNode mth, ConstructorInsn co) {
|
||||
MethodNode callMth = mth.dex().resolveMethod(co.getCallMth());
|
||||
if (callMth == null
|
||||
|| !callMth.getAccessFlags().isSynthetic()
|
||||
|| !allArgsNull(co)) {
|
||||
return null;
|
||||
}
|
||||
ClassNode classNode = mth.dex().resolveClass(callMth.getParentClass().getClassInfo());
|
||||
if (classNode == null) {
|
||||
return null;
|
||||
}
|
||||
RegisterArg instanceArg = co.getInstanceArg();
|
||||
boolean passThis = instanceArg.isThis();
|
||||
String ctrId = "<init>(" + (passThis ? TypeGen.signature(instanceArg.getInitType()) : "") + ")V";
|
||||
MethodNode defCtr = classNode.searchMethodByShortId(ctrId);
|
||||
if (defCtr == null) {
|
||||
return null;
|
||||
}
|
||||
ConstructorInsn newInsn = new ConstructorInsn(defCtr.getMethodInfo(), co.getCallType(), instanceArg);
|
||||
newInsn.setResult(co.getResult());
|
||||
return newInsn;
|
||||
}
|
||||
|
||||
private static boolean allArgsNull(ConstructorInsn insn) {
|
||||
for (InsnArg insnArg : insn.getArguments()) {
|
||||
if (insnArg.isLiteral()) {
|
||||
LiteralArg lit = (LiteralArg) insnArg;
|
||||
if (lit.getLiteral() != 0) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove instructions on 'move' chain until instruction with type 'insnType'
|
||||
*/
|
||||
private static InsnNode removeAssignChain(MethodNode mth, InsnNode insn, InstructionRemover remover, InsnType insnType) {
|
||||
if (insn == null) {
|
||||
return null;
|
||||
}
|
||||
if (insn.isAttrStorageEmpty()) {
|
||||
remover.add(insn);
|
||||
} else {
|
||||
BlockUtils.replaceInsn(mth, insn, new InsnNode(InsnType.NOP, 0));
|
||||
}
|
||||
InsnType type = insn.getType();
|
||||
if (type == insnType) {
|
||||
return insn;
|
||||
}
|
||||
if (type == InsnType.MOVE) {
|
||||
RegisterArg arg = (RegisterArg) insn.getArg(0);
|
||||
return removeAssignChain(mth, arg.getAssignInsn(), remover, insnType);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
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.nodes.parser.DebugInfoParser;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
public class DebugInfoVisitor extends AbstractVisitor {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DebugInfoVisitor.class);
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
try {
|
||||
int debugOffset = mth.getDebugInfoOffset();
|
||||
if (debugOffset > 0 && mth.dex().checkOffset(debugOffset)) {
|
||||
processDebugInfo(mth, debugOffset);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error in debug info parser: {}", ErrorsCounter.formatMsg(mth, e.getMessage()), e);
|
||||
} finally {
|
||||
mth.unloadInsnArr();
|
||||
}
|
||||
}
|
||||
|
||||
private void processDebugInfo(MethodNode mth, int debugOffset) throws DecodeException {
|
||||
InsnNode[] insnArr = mth.getInstructions();
|
||||
DebugInfoParser debugInfoParser = new DebugInfoParser(mth, debugOffset, insnArr);
|
||||
debugInfoParser.process();
|
||||
|
||||
if (insnArr.length != 0) {
|
||||
setMethodSourceLine(mth, insnArr);
|
||||
}
|
||||
if (!mth.getReturnType().equals(ArgType.VOID)) {
|
||||
setLineForReturn(mth, insnArr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix debug info for splitter 'return' instructions
|
||||
*/
|
||||
private void setLineForReturn(MethodNode mth, InsnNode[] insnArr) {
|
||||
for (BlockNode exit : mth.getExitBlocks()) {
|
||||
InsnNode ret = BlockUtils.getLastInsn(exit);
|
||||
if (ret != null) {
|
||||
InsnNode oldRet = insnArr[ret.getOffset()];
|
||||
if (oldRet != ret) {
|
||||
RegisterArg oldArg = (RegisterArg) oldRet.getArg(0);
|
||||
RegisterArg newArg = (RegisterArg) ret.getArg(0);
|
||||
newArg.mergeDebugInfo(oldArg.getType(), oldArg.getName());
|
||||
ret.setSourceLine(oldRet.getSourceLine());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set method source line from first instruction
|
||||
*/
|
||||
private void setMethodSourceLine(MethodNode mth, InsnNode[] insnArr) {
|
||||
for (InsnNode insn : insnArr) {
|
||||
if (insn != null) {
|
||||
int line = insn.getSourceLine();
|
||||
if (line != 0) {
|
||||
mth.setSourceLine(line - 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,8 +96,8 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
dot.startLine("MethodNode[shape=record,label=\"{");
|
||||
dot.add(escape(mth.getAccessFlags().makeString()));
|
||||
dot.add(escape(mth.getReturnType() + " "
|
||||
+ mth.getParentClass() + "." + mth.getName()
|
||||
+ "(" + Utils.listToString(mth.getArguments(true)) + ") "));
|
||||
+ mth.getParentClass() + '.' + mth.getName()
|
||||
+ '(' + Utils.listToString(mth.getArguments(true)) + ") "));
|
||||
|
||||
String attrs = attributesString(mth);
|
||||
if (!attrs.isEmpty()) {
|
||||
@@ -185,9 +185,9 @@ public class DotGraphVisitor extends AbstractVisitor {
|
||||
dot.add("}\"];");
|
||||
|
||||
BlockNode falsePath = null;
|
||||
List<InsnNode> list = block.getInstructions();
|
||||
if (!list.isEmpty() && list.get(0).getType() == InsnType.IF) {
|
||||
falsePath = ((IfNode) list.get(0)).getElseBlock();
|
||||
InsnNode lastInsn = BlockUtils.getLastInsn(block);
|
||||
if (lastInsn != null && lastInsn.getType() == InsnType.IF) {
|
||||
falsePath = ((IfNode) lastInsn).getElseBlock();
|
||||
}
|
||||
for (BlockNode next : block.getSuccessors()) {
|
||||
String style = next == falsePath ? "[style=dashed]" : "";
|
||||
|
||||
@@ -26,6 +26,7 @@ import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
@@ -33,7 +34,7 @@ import jadx.core.utils.exceptions.JadxException;
|
||||
@JadxVisitor(
|
||||
name = "EnumVisitor",
|
||||
desc = "Restore enum classes",
|
||||
runAfter = {CodeShrinker.class, ModVisitor.class}
|
||||
runAfter = {CodeShrinkVisitor.class, ModVisitor.class}
|
||||
)
|
||||
public class EnumVisitor extends AbstractVisitor {
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.PhiInsn;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "InitCodeVariables",
|
||||
desc = "Initialize code variables",
|
||||
runAfter = SSATransform.class
|
||||
)
|
||||
public class InitCodeVariables extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
if (mth.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
initCodeVars(mth);
|
||||
}
|
||||
|
||||
private static void initCodeVars(MethodNode mth) {
|
||||
for (RegisterArg mthArg : mth.getArguments(true)) {
|
||||
initCodeVar(mthArg.getSVar());
|
||||
}
|
||||
for (SSAVar ssaVar : mth.getSVars()) {
|
||||
initCodeVar(ssaVar);
|
||||
}
|
||||
}
|
||||
|
||||
public static void initCodeVar(SSAVar ssaVar) {
|
||||
if (ssaVar.isCodeVarSet()) {
|
||||
return;
|
||||
}
|
||||
CodeVar codeVar = new CodeVar();
|
||||
RegisterArg assignArg = ssaVar.getAssign();
|
||||
if (assignArg.contains(AFlag.THIS)) {
|
||||
codeVar.setName(RegisterArg.THIS_ARG_NAME);
|
||||
codeVar.setThis(true);
|
||||
}
|
||||
if (assignArg.contains(AFlag.METHOD_ARGUMENT) || assignArg.contains(AFlag.CUSTOM_DECLARE)) {
|
||||
codeVar.setDeclared(true);
|
||||
}
|
||||
|
||||
setCodeVar(ssaVar, codeVar);
|
||||
}
|
||||
|
||||
private static void setCodeVar(SSAVar ssaVar, CodeVar codeVar) {
|
||||
PhiInsn usedInPhi = ssaVar.getUsedInPhi();
|
||||
if (usedInPhi != null) {
|
||||
Set<SSAVar> vars = new LinkedHashSet<>();
|
||||
vars.add(ssaVar);
|
||||
collectConnectedVars(usedInPhi, vars);
|
||||
setCodeVarType(codeVar, vars);
|
||||
vars.forEach(var -> {
|
||||
if (var.isCodeVarSet()) {
|
||||
codeVar.mergeFlagsFrom(var.getCodeVar());
|
||||
}
|
||||
var.setCodeVar(codeVar);
|
||||
});
|
||||
} else {
|
||||
ssaVar.setCodeVar(codeVar);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setCodeVarType(CodeVar codeVar, Set<SSAVar> vars) {
|
||||
if (vars.size() > 1) {
|
||||
List<ArgType> imTypes = vars.stream()
|
||||
.filter(var -> var.contains(AFlag.IMMUTABLE_TYPE))
|
||||
.map(var -> var.getTypeInfo().getType())
|
||||
.filter(ArgType::isTypeKnown)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
int imCount = imTypes.size();
|
||||
if (imCount == 1) {
|
||||
codeVar.setType(imTypes.get(0));
|
||||
} else if (imCount > 1) {
|
||||
throw new JadxRuntimeException("Several immutable types in one variable: " + imTypes + ", vars: " + vars);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void collectConnectedVars(PhiInsn phiInsn, Set<SSAVar> vars) {
|
||||
if (phiInsn == null) {
|
||||
return;
|
||||
}
|
||||
SSAVar resultVar = phiInsn.getResult().getSVar();
|
||||
if (vars.add(resultVar)) {
|
||||
collectConnectedVars(resultVar.getUsedInPhi(), vars);
|
||||
}
|
||||
phiInsn.getArguments().forEach(arg -> {
|
||||
SSAVar sVar = ((RegisterArg) arg).getSVar();
|
||||
if (vars.add(sVar)) {
|
||||
collectConnectedVars(sVar.getUsedInPhi(), vars);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,435 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
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;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
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.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.visitors.ssa.SSATransform;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "MarkFinallyVisitor",
|
||||
desc = "Search and mark duplicate code generated for finally block",
|
||||
runAfter = SSATransform.class,
|
||||
runBefore = ConstInlineVisitor.class
|
||||
)
|
||||
public class MarkFinallyVisitor extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MarkFinallyVisitor.class);
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) {
|
||||
if (mth.isNoCode() || mth.isNoExceptionHandlers()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
for (ExceptionHandler excHandler : mth.getExceptionHandlers()) {
|
||||
processExceptionHandler(mth, excHandler);
|
||||
}
|
||||
} 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 ee) {
|
||||
LOG.error("Undo finally extract failed, mth: {}", mth, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean processExceptionHandler(MethodNode mth, ExceptionHandler excHandler) {
|
||||
// check if handler has exit edge to block not from this handler
|
||||
boolean noExitNode = true;
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (noExitNode && reThrowInsn != null) {
|
||||
boolean extracted = extractFinally(mth, excHandler);
|
||||
if (extracted) {
|
||||
reThrowInsn.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
return extracted;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search and mark common code from 'try' block and 'handlers'.
|
||||
*/
|
||||
private static boolean extractFinally(MethodNode mth, ExceptionHandler allHandler) {
|
||||
List<BlockNode> handlerBlocks = new ArrayList<>();
|
||||
for (BlockNode block : allHandler.getBlocks()) {
|
||||
InsnNode lastInsn = BlockUtils.getLastInsn(block);
|
||||
if (lastInsn != null) {
|
||||
InsnType insnType = lastInsn.getType();
|
||||
if (insnType != InsnType.MOVE_EXCEPTION && insnType != InsnType.THROW) {
|
||||
handlerBlocks.add(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (handlerBlocks.isEmpty()) {
|
||||
// remove empty catch
|
||||
allHandler.getTryBlock().removeHandler(mth, allHandler);
|
||||
return true;
|
||||
}
|
||||
|
||||
BlockNode startBlock = handlerBlocks.get(0);
|
||||
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()) {
|
||||
if (otherHandler == allHandler) {
|
||||
continue;
|
||||
}
|
||||
for (BlockNode checkBlock : otherHandler.getBlocks()) {
|
||||
if (searchDuplicateInsns(checkBlock, extractInfo)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (extractInfo.getDuplicateSlices().size() != tryBlock.getHandlersCount() - 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 (dominated by splitter block)
|
||||
boolean found = false;
|
||||
for (BlockNode splitter : splitters) {
|
||||
BlockNode start = splitter.getCleanSuccessors().get(0);
|
||||
List<BlockNode> list = BlockUtils.collectBlocksDominatedBy(splitter, start);
|
||||
for (BlockNode block : list) {
|
||||
if (searchDuplicateInsns(block, extractInfo)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
if (!checkSlices(extractInfo)) {
|
||||
mth.addComment("JADX INFO: finally extract failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 'finally' extract confirmed, apply
|
||||
apply(extractInfo);
|
||||
allHandler.setFinally(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean checkSlices(FinallyExtractInfo extractInfo) {
|
||||
InsnsSlice finallySlice = extractInfo.getFinallyInsnsSlice();
|
||||
List<InsnNode> finallyInsnsList = finallySlice.getInsnsList();
|
||||
|
||||
for (InsnsSlice dupSlice : extractInfo.getDuplicateSlices()) {
|
||||
List<InsnNode> dupInsnsList = dupSlice.getInsnsList();
|
||||
if (dupInsnsList.size() != finallyInsnsList.size()) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Incorrect finally slice size: {}, expected: {}", dupSlice, finallySlice);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < finallyInsnsList.size(); i++) {
|
||||
InsnNode finallyInsn = finallyInsnsList.get(i);
|
||||
for (InsnsSlice dupSlice : extractInfo.getDuplicateSlices()) {
|
||||
List<InsnNode> insnsList = dupSlice.getInsnsList();
|
||||
InsnNode dupInsn = insnsList.get(i);
|
||||
if (finallyInsn.getType() != dupInsn.getType()) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Incorrect finally slice insn: {}, expected: {}", dupInsn, finallyInsn);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void apply(FinallyExtractInfo extractInfo) {
|
||||
markSlice(extractInfo.getFinallyInsnsSlice(), AFlag.FINALLY_INSNS);
|
||||
for (InsnsSlice dupSlice : extractInfo.getDuplicateSlices()) {
|
||||
markSlice(dupSlice, AFlag.DONT_GENERATE);
|
||||
}
|
||||
InsnsSlice finallySlice = extractInfo.getFinallyInsnsSlice();
|
||||
List<InsnNode> finallyInsnsList = finallySlice.getInsnsList();
|
||||
for (int i = 0; i < finallyInsnsList.size(); i++) {
|
||||
InsnNode finallyInsn = finallyInsnsList.get(i);
|
||||
for (InsnsSlice dupSlice : extractInfo.getDuplicateSlices()) {
|
||||
InsnNode dupInsn = dupSlice.getInsnsList().get(i);
|
||||
copyCodeVars(finallyInsn, dupInsn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void markSlice(InsnsSlice slice, AFlag flag) {
|
||||
List<InsnNode> insnsList = slice.getInsnsList();
|
||||
for (InsnNode insn : insnsList) {
|
||||
insn.add(flag);
|
||||
}
|
||||
for (BlockNode block : slice.getBlocks()) {
|
||||
boolean allInsnMarked = true;
|
||||
for (InsnNode insn : block.getInstructions()) {
|
||||
if (!insn.contains(flag)) {
|
||||
allInsnMarked = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allInsnMarked) {
|
||||
block.add(flag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void copyCodeVars(InsnNode fromInsn, InsnNode toInsn) {
|
||||
copyCodeVars(fromInsn.getResult(), toInsn.getResult());
|
||||
int argsCount = fromInsn.getArgsCount();
|
||||
for (int i = 0; i < argsCount; i++) {
|
||||
copyCodeVars(fromInsn.getArg(i), toInsn.getArg(i));
|
||||
}
|
||||
}
|
||||
|
||||
private static void copyCodeVars(InsnArg fromArg, InsnArg toArg) {
|
||||
if (fromArg == null || toArg == null
|
||||
|| !fromArg.isRegister() || !toArg.isRegister()) {
|
||||
return;
|
||||
}
|
||||
SSAVar fromSsaVar = ((RegisterArg) fromArg).getSVar();
|
||||
SSAVar toSsaVar = ((RegisterArg) toArg).getSVar();
|
||||
toSsaVar.setCodeVar(fromSsaVar.getCodeVar());
|
||||
}
|
||||
|
||||
private static boolean searchDuplicateInsns(BlockNode checkBlock, FinallyExtractInfo extractInfo) {
|
||||
boolean isNew = extractInfo.getCheckedBlocks().add(checkBlock);
|
||||
if (!isNew) {
|
||||
return false;
|
||||
}
|
||||
BlockNode startBlock = extractInfo.getStartBlock();
|
||||
InsnsSlice dupSlice = searchFromFirstBlock(checkBlock, startBlock, extractInfo);
|
||||
if (dupSlice == null) {
|
||||
return false;
|
||||
}
|
||||
extractInfo.getDuplicateSlices().add(dupSlice);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static InsnsSlice searchFromFirstBlock(BlockNode dupBlock, BlockNode startBlock, FinallyExtractInfo extractInfo) {
|
||||
InsnsSlice dupSlice = isStartBlock(dupBlock, startBlock, extractInfo);
|
||||
if (dupSlice == null) {
|
||||
return null;
|
||||
}
|
||||
if (dupSlice.isComplete()) {
|
||||
return dupSlice;
|
||||
}
|
||||
if (!checkBlocksTree(dupBlock, startBlock, dupSlice, extractInfo)) {
|
||||
return null;
|
||||
}
|
||||
return dupSlice;
|
||||
}
|
||||
|
||||
/**
|
||||
* 'Finally' instructions can start in the middle of the first block.
|
||||
*/
|
||||
private static InsnsSlice isStartBlock(BlockNode dupBlock, BlockNode finallyBlock, FinallyExtractInfo extractInfo) {
|
||||
List<InsnNode> dupInsns = dupBlock.getInstructions();
|
||||
List<InsnNode> finallyInsns = finallyBlock.getInstructions();
|
||||
if (dupInsns.size() < finallyInsns.size()) {
|
||||
return null;
|
||||
}
|
||||
int startPos = dupInsns.size() - finallyInsns.size();
|
||||
int endPos = 0;
|
||||
// fast check from end of block
|
||||
if (!checkInsns(dupInsns, finallyInsns, startPos)) {
|
||||
// check from block start
|
||||
if (checkInsns(dupInsns, finallyInsns, 0)) {
|
||||
startPos = 0;
|
||||
endPos = finallyInsns.size();
|
||||
} else {
|
||||
// search start insn
|
||||
boolean found = false;
|
||||
for (int i = 1; i < startPos; i++) {
|
||||
if (checkInsns(dupInsns, finallyInsns, i)) {
|
||||
startPos = i;
|
||||
endPos = finallyInsns.size() + i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// put instructions into slices
|
||||
boolean complete;
|
||||
InsnsSlice slice = new InsnsSlice();
|
||||
int endIndex;
|
||||
if (endPos != 0) {
|
||||
endIndex = endPos + 1;
|
||||
// both slices completed
|
||||
complete = true;
|
||||
} else {
|
||||
endIndex = dupInsns.size();
|
||||
complete = false;
|
||||
}
|
||||
|
||||
// fill dup insns slice
|
||||
for (int i = startPos; i < endIndex; i++) {
|
||||
slice.addInsn(dupInsns.get(i), dupBlock);
|
||||
}
|
||||
|
||||
// fill finally insns slice
|
||||
InsnsSlice finallySlice = extractInfo.getFinallyInsnsSlice();
|
||||
if (finallySlice.isComplete()) {
|
||||
// compare slices
|
||||
if (finallySlice.getInsnsList().size() != slice.getInsnsList().size()) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Another duplicated slice has different insns count: {}, finally: {}", slice, finallySlice);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// TODO: add additional slices checks
|
||||
// and try to extract common part if found difference
|
||||
} else {
|
||||
for (InsnNode finallyInsn : finallyInsns) {
|
||||
finallySlice.addInsn(finallyInsn, finallyBlock);
|
||||
}
|
||||
}
|
||||
|
||||
if (complete) {
|
||||
slice.setComplete(true);
|
||||
finallySlice.setComplete(true);
|
||||
}
|
||||
return slice;
|
||||
}
|
||||
|
||||
private static boolean checkInsns(List<InsnNode> remInsns, List<InsnNode> finallyInsns, int delta) {
|
||||
for (int i = finallyInsns.size() - 1; i >= 0; i--) {
|
||||
InsnNode startInsn = finallyInsns.get(i);
|
||||
InsnNode remInsn = remInsns.get(delta + i);
|
||||
if (!sameInsns(remInsn, startInsn)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean checkBlocksTree(BlockNode dupBlock, BlockNode finallyBlock,
|
||||
InsnsSlice dupSlice, FinallyExtractInfo extractInfo) {
|
||||
InsnsSlice finallySlice = extractInfo.getFinallyInsnsSlice();
|
||||
|
||||
List<BlockNode> finallyCS = finallyBlock.getCleanSuccessors();
|
||||
List<BlockNode> dupCS = dupBlock.getCleanSuccessors();
|
||||
if (finallyCS.size() == dupCS.size()) {
|
||||
for (int i = 0; i < finallyCS.size(); i++) {
|
||||
BlockNode finSBlock = finallyCS.get(i);
|
||||
BlockNode dupSBlock = dupCS.get(i);
|
||||
if (extractInfo.getAllHandlerBlocks().contains(finSBlock)) {
|
||||
if (!compareBlocks(dupSBlock, finSBlock, dupSlice, extractInfo)) {
|
||||
return false;
|
||||
}
|
||||
if (!checkBlocksTree(dupSBlock, finSBlock, dupSlice, extractInfo)) {
|
||||
return false;
|
||||
}
|
||||
dupSlice.addBlock(dupSBlock);
|
||||
finallySlice.addBlock(finSBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
dupSlice.setComplete(true);
|
||||
finallySlice.setComplete(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean compareBlocks(BlockNode dupBlock, BlockNode finallyBlock, InsnsSlice dupSlice, FinallyExtractInfo extractInfo) {
|
||||
List<InsnNode> dupInsns = dupBlock.getInstructions();
|
||||
List<InsnNode> finallyInsns = finallyBlock.getInstructions();
|
||||
if (dupInsns.size() < finallyInsns.size()) {
|
||||
return false;
|
||||
}
|
||||
int size = finallyInsns.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (!sameInsns(dupInsns.get(i), finallyInsns.get(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (dupInsns.size() > finallyInsns.size()) {
|
||||
dupSlice.addInsns(dupBlock, 0, finallyInsns.size());
|
||||
dupSlice.setComplete(true);
|
||||
InsnsSlice finallyInsnsSlice = extractInfo.getFinallyInsnsSlice();
|
||||
finallyInsnsSlice.addBlock(finallyBlock);
|
||||
finallyInsnsSlice.setComplete(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean sameInsns(InsnNode remInsn, InsnNode fInsn) {
|
||||
if (!remInsn.isSame(fInsn)) {
|
||||
return false;
|
||||
}
|
||||
// TODO: check instance arg in ConstructorInsn
|
||||
// TODO: compare literals
|
||||
for (int i = 0; i < remInsn.getArgsCount(); i++) {
|
||||
InsnArg remArg = remInsn.getArg(i);
|
||||
InsnArg fArg = fInsn.getArg(i);
|
||||
if (remArg.isRegister() != fArg.isRegister()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,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.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@JadxVisitor(
|
||||
@@ -77,7 +78,7 @@ public class MethodInlineVisitor extends AbstractVisitor {
|
||||
&& get.getResult().equalRegisterAndType((RegisterArg) retArg)) {
|
||||
RegisterArg retReg = (RegisterArg) retArg;
|
||||
retReg.getSVar().removeUse(retReg);
|
||||
CodeShrinker.shrinkMethod(mth);
|
||||
CodeShrinkVisitor.shrinkMethod(mth);
|
||||
|
||||
insnList = firstBlock.getInstructions();
|
||||
if (insnList.size() == 1) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
@@ -9,8 +8,6 @@ import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.codegen.TypeGen;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.FieldReplaceAttr;
|
||||
@@ -24,7 +21,6 @@ import jadx.core.dex.instructions.FillArrayNode;
|
||||
import jadx.core.dex.instructions.FilledNewArrayNode;
|
||||
import jadx.core.dex.instructions.IndexInsnNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.InvokeNode;
|
||||
import jadx.core.dex.instructions.NewArrayNode;
|
||||
import jadx.core.dex.instructions.SwitchNode;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
@@ -41,11 +37,14 @@ import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.ExcHandlerAttr;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.InstructionRemover;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.utils.BlockUtils.replaceInsn;
|
||||
|
||||
/**
|
||||
* Visitor for modify method instructions
|
||||
* (remove, replace, process exception handlers)
|
||||
@@ -53,7 +52,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
@JadxVisitor(
|
||||
name = "ModVisitor",
|
||||
desc = "Modify method instructions",
|
||||
runBefore = CodeShrinker.class
|
||||
runBefore = CodeShrinkVisitor.class
|
||||
)
|
||||
public class ModVisitor extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ModVisitor.class);
|
||||
@@ -67,8 +66,6 @@ public class ModVisitor extends AbstractVisitor {
|
||||
InstructionRemover remover = new InstructionRemover(mth);
|
||||
replaceStep(mth, remover);
|
||||
removeStep(mth, remover);
|
||||
|
||||
checkArgsNames(mth);
|
||||
}
|
||||
|
||||
private static void replaceStep(MethodNode mth, InstructionRemover remover) {
|
||||
@@ -79,8 +76,8 @@ public class ModVisitor extends AbstractVisitor {
|
||||
for (int i = 0; i < size; i++) {
|
||||
InsnNode insn = block.getInstructions().get(i);
|
||||
switch (insn.getType()) {
|
||||
case INVOKE:
|
||||
processInvoke(mth, block, i, remover);
|
||||
case CONSTRUCTOR:
|
||||
processAnonymousConstructor(mth, ((ConstructorInsn) insn));
|
||||
break;
|
||||
|
||||
case CONST:
|
||||
@@ -115,24 +112,20 @@ public class ModVisitor extends AbstractVisitor {
|
||||
break;
|
||||
|
||||
case NEW_ARRAY:
|
||||
// create array in 'fill-array' instruction
|
||||
// replace with filled array if 'fill-array' is next instruction
|
||||
int next = i + 1;
|
||||
if (next < size) {
|
||||
InsnNode ni = block.getInstructions().get(next);
|
||||
if (ni.getType() == InsnType.FILL_ARRAY) {
|
||||
ni.getResult().merge(mth.dex(), insn.getResult());
|
||||
ArgType arrType = ((NewArrayNode) insn).getArrayType();
|
||||
((FillArrayNode) ni).mergeElementType(mth.dex(), arrType.getArrayElement());
|
||||
remover.add(insn);
|
||||
InsnNode filledArr = makeFilledArrayInsn(mth, (NewArrayNode) insn, (FillArrayNode) ni);
|
||||
if (filledArr != null) {
|
||||
replaceInsn(block, i, filledArr);
|
||||
remover.add(ni);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case FILL_ARRAY:
|
||||
InsnNode filledArr = makeFilledArrayInsn(mth, (FillArrayNode) insn);
|
||||
replaceInsn(block, i, filledArr);
|
||||
break;
|
||||
|
||||
case MOVE_EXCEPTION:
|
||||
processMoveException(block, insn, remover);
|
||||
break;
|
||||
@@ -151,6 +144,18 @@ public class ModVisitor extends AbstractVisitor {
|
||||
}
|
||||
break;
|
||||
|
||||
case CHECK_CAST:
|
||||
InsnArg castArg = insn.getArg(0);
|
||||
ArgType castType = (ArgType) ((IndexInsnNode) insn).getIndex();
|
||||
if (!ArgType.isCastNeeded(mth.dex(), castArg.getType(), castType)
|
||||
|| isCastDuplicate((IndexInsnNode) insn)) {
|
||||
InsnNode insnNode = new InsnNode(InsnType.MOVE, 1);
|
||||
insnNode.setResult(insn.getResult());
|
||||
insnNode.addArg(castArg);
|
||||
replaceInsn(block, i, insnNode);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -159,6 +164,21 @@ public class ModVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isCastDuplicate(IndexInsnNode castInsn) {
|
||||
InsnArg arg = castInsn.getArg(0);
|
||||
if (arg.isRegister()) {
|
||||
SSAVar sVar = ((RegisterArg) arg).getSVar();
|
||||
if (sVar != null && sVar.getUseCount() == 1 && !sVar.isUsedInPhi()) {
|
||||
InsnNode assignInsn = sVar.getAssign().getParentInsn();
|
||||
if (assignInsn != null && assignInsn.getType() == InsnType.CHECK_CAST) {
|
||||
ArgType assignCastType = (ArgType) ((IndexInsnNode) assignInsn).getIndex();
|
||||
return assignCastType.equals(castInsn.getIndex());
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unnecessary instructions
|
||||
*/
|
||||
@@ -181,60 +201,6 @@ public class ModVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private static void processInvoke(MethodNode mth, BlockNode block, int insnNumber, InstructionRemover remover) {
|
||||
ClassNode parentClass = mth.getParentClass();
|
||||
InsnNode insn = block.getInstructions().get(insnNumber);
|
||||
InvokeNode inv = (InvokeNode) insn;
|
||||
MethodInfo callMth = inv.getCallMth();
|
||||
if (!callMth.isConstructor()) {
|
||||
return;
|
||||
}
|
||||
InsnNode instArgAssignInsn = ((RegisterArg) inv.getArg(0)).getAssignInsn();
|
||||
ConstructorInsn co = new ConstructorInsn(mth, inv);
|
||||
boolean remove = false;
|
||||
if (co.isSuper() && (co.getArgsCount() == 0 || parentClass.isEnum())) {
|
||||
remove = true;
|
||||
} else if (co.isThis() && co.getArgsCount() == 0) {
|
||||
MethodNode defCo = parentClass.searchMethodByShortId(callMth.getShortId());
|
||||
if (defCo == null || defCo.isNoCode()) {
|
||||
// default constructor not implemented
|
||||
remove = true;
|
||||
}
|
||||
}
|
||||
// remove super() call in instance initializer
|
||||
if (parentClass.isAnonymous() && mth.isDefaultConstructor() && co.isSuper()) {
|
||||
remove = true;
|
||||
}
|
||||
if (remove) {
|
||||
remover.add(insn);
|
||||
return;
|
||||
}
|
||||
if (co.isNewInstance()) {
|
||||
InsnNode newInstInsn = removeAssignChain(instArgAssignInsn, remover, InsnType.NEW_INSTANCE);
|
||||
if (newInstInsn != null) {
|
||||
RegisterArg instArg = newInstInsn.getResult();
|
||||
RegisterArg resultArg = co.getResult();
|
||||
if (!resultArg.equals(instArg)) {
|
||||
// replace all usages of 'instArg' with result of this constructor instruction
|
||||
for (RegisterArg useArg : new ArrayList<>(instArg.getSVar().getUseList())) {
|
||||
RegisterArg dup = resultArg.duplicate();
|
||||
InsnNode parentInsn = useArg.getParentInsn();
|
||||
parentInsn.replaceArg(useArg, dup);
|
||||
dup.setParentInsn(parentInsn);
|
||||
resultArg.getSVar().use(dup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ConstructorInsn replace = processConstructor(mth, co);
|
||||
if (replace != null) {
|
||||
co = replace;
|
||||
}
|
||||
replaceInsn(block, insnNumber, co);
|
||||
|
||||
processAnonymousConstructor(mth, co);
|
||||
}
|
||||
|
||||
private static void processAnonymousConstructor(MethodNode mth, ConstructorInsn co) {
|
||||
MethodInfo callMth = co.getCallMth();
|
||||
MethodNode callMthNode = mth.dex().resolveMethod(callMth);
|
||||
@@ -270,7 +236,7 @@ public class ModVisitor extends AbstractVisitor {
|
||||
RegisterArg reg = (RegisterArg) arg;
|
||||
SSAVar sVar = reg.getSVar();
|
||||
if (sVar != null) {
|
||||
sVar.add(AFlag.FINAL);
|
||||
sVar.getCodeVar().setFinal(true);
|
||||
sVar.add(AFlag.DONT_INLINE);
|
||||
}
|
||||
reg.add(AFlag.SKIP_ARG);
|
||||
@@ -331,33 +297,8 @@ public class ModVisitor extends AbstractVisitor {
|
||||
return parentInsn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace call of synthetic constructor
|
||||
*/
|
||||
private static ConstructorInsn processConstructor(MethodNode mth, ConstructorInsn co) {
|
||||
MethodNode callMth = mth.dex().resolveMethod(co.getCallMth());
|
||||
if (callMth == null
|
||||
|| !callMth.getAccessFlags().isSynthetic()
|
||||
|| !allArgsNull(co)) {
|
||||
return null;
|
||||
}
|
||||
ClassNode classNode = mth.dex().resolveClass(callMth.getParentClass().getClassInfo());
|
||||
if (classNode == null) {
|
||||
return null;
|
||||
}
|
||||
boolean passThis = co.getArgsCount() >= 1 && co.getArg(0).isThis();
|
||||
String ctrId = "<init>(" + (passThis ? TypeGen.signature(co.getArg(0).getType()) : "") + ")V";
|
||||
MethodNode defCtr = classNode.searchMethodByShortId(ctrId);
|
||||
if (defCtr == null) {
|
||||
return null;
|
||||
}
|
||||
ConstructorInsn newInsn = new ConstructorInsn(defCtr.getMethodInfo(), co.getCallType(), co.getInstanceArg());
|
||||
newInsn.setResult(co.getResult());
|
||||
return newInsn;
|
||||
}
|
||||
|
||||
private static InsnNode makeFilledArrayInsn(MethodNode mth, FillArrayNode insn) {
|
||||
ArgType insnArrayType = insn.getResult().getType();
|
||||
private static InsnNode makeFilledArrayInsn(MethodNode mth, NewArrayNode newArrayNode, FillArrayNode insn) {
|
||||
ArgType insnArrayType = newArrayNode.getArrayType();
|
||||
ArgType insnElementType = insnArrayType.getArrayElement();
|
||||
ArgType elType = insn.getElementType();
|
||||
if (!elType.isTypeKnown()
|
||||
@@ -378,12 +319,10 @@ public class ModVisitor extends AbstractVisitor {
|
||||
throw new JadxRuntimeException("Null array element type");
|
||||
}
|
||||
}
|
||||
insn.mergeElementType(mth.dex(), elType);
|
||||
elType = insn.getElementType();
|
||||
|
||||
List<LiteralArg> list = insn.getLiteralArgs();
|
||||
List<LiteralArg> list = insn.getLiteralArgs(elType);
|
||||
InsnNode filledArr = new FilledNewArrayNode(elType, list.size());
|
||||
filledArr.setResult(insn.getResult());
|
||||
filledArr.setResult(newArrayNode.getResult());
|
||||
for (LiteralArg arg : list) {
|
||||
FieldNode f = mth.getParentClass().getConstFieldByLiteralArg(arg);
|
||||
if (f != null) {
|
||||
@@ -396,39 +335,6 @@ public class ModVisitor extends AbstractVisitor {
|
||||
return filledArr;
|
||||
}
|
||||
|
||||
private static boolean allArgsNull(ConstructorInsn insn) {
|
||||
for (InsnArg insnArg : insn.getArguments()) {
|
||||
if (insnArg.isLiteral()) {
|
||||
LiteralArg lit = (LiteralArg) insnArg;
|
||||
if (lit.getLiteral() != 0) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove instructions on 'move' chain until instruction with type 'insnType'
|
||||
*/
|
||||
private static InsnNode removeAssignChain(InsnNode insn, InstructionRemover remover, InsnType insnType) {
|
||||
if (insn == null) {
|
||||
return null;
|
||||
}
|
||||
remover.add(insn);
|
||||
InsnType type = insn.getType();
|
||||
if (type == insnType) {
|
||||
return insn;
|
||||
}
|
||||
if (type == InsnType.MOVE) {
|
||||
RegisterArg arg = (RegisterArg) insn.getArg(0);
|
||||
return removeAssignChain(arg.getAssignInsn(), remover, insnType);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void processMoveException(BlockNode block, InsnNode insn, InstructionRemover remover) {
|
||||
ExcHandlerAttr excHandlerAttr = block.get(AType.EXC_HANDLER);
|
||||
if (excHandlerAttr == null) {
|
||||
@@ -457,25 +363,4 @@ public class ModVisitor extends AbstractVisitor {
|
||||
replaceInsn(block, 0, moveInsn);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace insn by index i in block,
|
||||
* for proper copy attributes, assume attributes are not overlap
|
||||
*/
|
||||
private static void replaceInsn(BlockNode block, int i, InsnNode insn) {
|
||||
InsnNode prevInsn = block.getInstructions().get(i);
|
||||
insn.copyAttributesFrom(prevInsn);
|
||||
insn.setSourceLine(prevInsn.getSourceLine());
|
||||
block.getInstructions().set(i, insn);
|
||||
}
|
||||
|
||||
private static void checkArgsNames(MethodNode mth) {
|
||||
for (RegisterArg arg : mth.getArguments(false)) {
|
||||
String name = arg.getName();
|
||||
if (name != null && NameMapper.isReserved(name)) {
|
||||
name = name + '_';
|
||||
arg.getSVar().setName(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.visitors.regions.variables.ProcessVariables;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
/**
|
||||
@@ -24,7 +26,7 @@ import jadx.core.utils.exceptions.JadxException;
|
||||
@JadxVisitor(
|
||||
name = "PrepareForCodeGen",
|
||||
desc = "Prepare instructions for code generation pass",
|
||||
runAfter = {CodeShrinker.class, ClassModifier.class}
|
||||
runAfter = {CodeShrinkVisitor.class, ClassModifier.class, ProcessVariables.class}
|
||||
)
|
||||
public class PrepareForCodeGen extends AbstractVisitor {
|
||||
|
||||
@@ -35,6 +37,9 @@ public class PrepareForCodeGen extends AbstractVisitor {
|
||||
return;
|
||||
}
|
||||
for (BlockNode block : blocks) {
|
||||
if (block.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
removeInstructions(block);
|
||||
checkInline(block);
|
||||
// removeParenthesis(block);
|
||||
@@ -141,11 +146,11 @@ public class PrepareForCodeGen extends AbstractVisitor {
|
||||
replace = true;
|
||||
} else if (arg.isRegister()) {
|
||||
RegisterArg regArg = (RegisterArg) arg;
|
||||
replace = res.equalRegisterAndType(regArg);
|
||||
replace = res.sameCodeVar(regArg);
|
||||
}
|
||||
if (replace) {
|
||||
insn.add(AFlag.ARITH_ONEARG);
|
||||
insn.getResult().mergeName(arg);
|
||||
// insn.getResult().mergeName(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.core.dex.visitors;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
@@ -21,19 +22,25 @@ import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.InsnWrapArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.utils.InsnList;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
import jadx.core.utils.InstructionRemover;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "ReSugarCode",
|
||||
desc = "Simplify synthetic or verbose code",
|
||||
runAfter = CodeShrinker.class
|
||||
runAfter = CodeShrinkVisitor.class
|
||||
)
|
||||
public class ReSugarCode extends AbstractVisitor {
|
||||
|
||||
@@ -56,60 +63,119 @@ public class ReSugarCode extends AbstractVisitor {
|
||||
List<InsnNode> instructions = block.getInstructions();
|
||||
int size = instructions.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
InsnNode replacedInsn = process(mth, instructions, i, remover);
|
||||
if (replacedInsn != null) {
|
||||
instructions.set(i, replacedInsn);
|
||||
}
|
||||
process(mth, instructions, i, remover);
|
||||
}
|
||||
remover.perform();
|
||||
}
|
||||
}
|
||||
|
||||
private static InsnNode process(MethodNode mth, List<InsnNode> instructions, int i, InstructionRemover remover) {
|
||||
private static void process(MethodNode mth, List<InsnNode> instructions, int i, InstructionRemover remover) {
|
||||
InsnNode insn = instructions.get(i);
|
||||
if (insn.contains(AFlag.REMOVE)) {
|
||||
return;
|
||||
}
|
||||
switch (insn.getType()) {
|
||||
case NEW_ARRAY:
|
||||
return processNewArray(mth, instructions, i, remover);
|
||||
processNewArray(mth, instructions, i, remover);
|
||||
break;
|
||||
|
||||
case SWITCH:
|
||||
processEnumSwitch(mth, (SwitchNode) insn);
|
||||
return null;
|
||||
break;
|
||||
|
||||
default:
|
||||
return null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace new array and sequence of array-put to new filled-array instruction.
|
||||
*/
|
||||
private static InsnNode processNewArray(MethodNode mth, List<InsnNode> instructions, int i,
|
||||
InstructionRemover remover) {
|
||||
private static void processNewArray(MethodNode mth, List<InsnNode> instructions, int i,
|
||||
InstructionRemover remover) {
|
||||
NewArrayNode newArrayInsn = (NewArrayNode) instructions.get(i);
|
||||
InsnArg arg = newArrayInsn.getArg(0);
|
||||
if (!arg.isLiteral()) {
|
||||
return null;
|
||||
InsnArg arrLenArg = newArrayInsn.getArg(0);
|
||||
if (!arrLenArg.isLiteral()) {
|
||||
return;
|
||||
}
|
||||
int len = (int) ((LiteralArg) arg).getLiteral();
|
||||
int size = instructions.size();
|
||||
if (len <= 0
|
||||
|| i + len >= size
|
||||
|| instructions.get(i + len).getType() != InsnType.APUT) {
|
||||
return null;
|
||||
int len = (int) ((LiteralArg) arrLenArg).getLiteral();
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
RegisterArg arrArg = newArrayInsn.getResult();
|
||||
SSAVar ssaVar = arrArg.getSVar();
|
||||
List<RegisterArg> useList = ssaVar.getUseList();
|
||||
if (useList.size() < len) {
|
||||
return;
|
||||
}
|
||||
// check sequential array put with increasing index
|
||||
int putIndex = 0;
|
||||
for (RegisterArg useArg : useList) {
|
||||
InsnNode insn = useArg.getParentInsn();
|
||||
if (checkPutInsn(mth, insn, arrArg, putIndex)) {
|
||||
putIndex++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (putIndex != len) {
|
||||
return;
|
||||
}
|
||||
List<InsnNode> arrPuts = useList.subList(0, len).stream().map(InsnArg::getParentInsn).collect(Collectors.toList());
|
||||
// check that all puts in current block
|
||||
for (InsnNode arrPut : arrPuts) {
|
||||
int index = InsnList.getIndex(instructions, arrPut);
|
||||
if (index == -1) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("TODO: APUT found in different block: {}, mth: {}", arrPut, mth);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// checks complete, apply
|
||||
ArgType arrType = newArrayInsn.getArrayType();
|
||||
InsnNode filledArr = new FilledNewArrayNode(arrType.getArrayElement(), len);
|
||||
filledArr.setResult(newArrayInsn.getResult());
|
||||
for (int j = 0; j < len; j++) {
|
||||
InsnNode put = instructions.get(i + 1 + j);
|
||||
if (put.getType() != InsnType.APUT) {
|
||||
LOG.debug("Not a APUT in expected new filled array: {}, method: {}", put, mth);
|
||||
return null;
|
||||
}
|
||||
filledArr.addArg(put.getArg(2));
|
||||
remover.add(put);
|
||||
filledArr.setResult(arrArg.duplicate());
|
||||
for (InsnNode put : arrPuts) {
|
||||
filledArr.addArg(put.getArg(2).duplicate());
|
||||
remover.addAndUnbind(mth, put);
|
||||
}
|
||||
return filledArr;
|
||||
remover.addAndUnbind(mth, newArrayInsn);
|
||||
|
||||
int replaceIndex = InsnList.getIndex(instructions, Utils.last(arrPuts));
|
||||
instructions.set(replaceIndex, filledArr);
|
||||
}
|
||||
|
||||
private static boolean checkPutInsn(MethodNode mth, InsnNode insn, RegisterArg arrArg, int putIndex) {
|
||||
if (insn == null || insn.getType() != InsnType.APUT) {
|
||||
return false;
|
||||
}
|
||||
if (!arrArg.sameRegAndSVar(insn.getArg(0))) {
|
||||
return false;
|
||||
}
|
||||
InsnArg indexArg = insn.getArg(1);
|
||||
int index = -1;
|
||||
if (indexArg.isLiteral()) {
|
||||
index = (int) ((LiteralArg) indexArg).getLiteral();
|
||||
} else if (indexArg.isRegister()) {
|
||||
RegisterArg reg = (RegisterArg) indexArg;
|
||||
index = getIntConst(reg.getConstValue(mth.dex()));
|
||||
} else if (indexArg.isInsnWrap()) {
|
||||
InsnNode constInsn = ((InsnWrapArg) indexArg).getWrapInsn();
|
||||
index = getIntConst(InsnUtils.getConstValueByInsn(mth.dex(), constInsn));
|
||||
}
|
||||
return index == putIndex;
|
||||
}
|
||||
|
||||
private static int getIntConst(Object value) {
|
||||
if (value instanceof Integer) {
|
||||
return (Integer) value;
|
||||
}
|
||||
if (value instanceof Long) {
|
||||
return ((Long) value).intValue();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static void processEnumSwitch(MethodNode mth, SwitchNode insn) {
|
||||
|
||||
@@ -76,6 +76,7 @@ public class RenameVisitor extends AbstractVisitor {
|
||||
String newShortName = fixClsShortName(clsName);
|
||||
if (!newShortName.equals(clsName)) {
|
||||
classInfo.rename(cls.root(), alias.makeFullClsName(newShortName, true));
|
||||
alias = classInfo.getAlias();
|
||||
}
|
||||
if (alias.getPackage().isEmpty()) {
|
||||
String fullName = alias.makeFullClsName(alias.getShortName(), true);
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.FieldInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.ArithNode;
|
||||
@@ -52,6 +53,9 @@ public class SimplifyVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static InsnNode simplifyInsn(MethodNode mth, InsnNode insn) {
|
||||
if (insn.contains(AFlag.DONT_GENERATE)) {
|
||||
return null;
|
||||
}
|
||||
for (InsnArg arg : insn.getArguments()) {
|
||||
if (arg.isInsnWrap()) {
|
||||
InsnNode ni = simplifyInsn(mth, ((InsnWrapArg) arg).getWrapInsn());
|
||||
@@ -187,7 +191,8 @@ public class SimplifyVisitor extends AbstractVisitor {
|
||||
if (constrIndex != -1) { // If we found a CONSTRUCTOR, is it a StringBuilder?
|
||||
ConstructorInsn constr = (ConstructorInsn) chain.get(constrIndex);
|
||||
if (constr.getClassType().getFullName().equals(Consts.CLASS_STRING_BUILDER)) {
|
||||
int len = chain.size(), argInd = 1;
|
||||
int len = chain.size();
|
||||
int argInd = 1;
|
||||
InsnNode concatInsn = new InsnNode(InsnType.STR_CONCAT, len - 1);
|
||||
InsnNode argInsn;
|
||||
if (constrIndex > 0) { // There was an arg to the StringBuilder constr
|
||||
@@ -302,20 +307,16 @@ public class SimplifyVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
FieldArg fArg = new FieldArg(field, reg);
|
||||
if (reg != null) {
|
||||
fArg.setType(get.getArg(0).getType());
|
||||
}
|
||||
if (wrapType == InsnType.ARITH) {
|
||||
ArithNode ar = (ArithNode) wrap;
|
||||
return new ArithNode(ar.getOp(), fArg, ar.getArg(1));
|
||||
} else {
|
||||
int argsCount = wrap.getArgsCount();
|
||||
InsnNode concat = new InsnNode(InsnType.STR_CONCAT, argsCount - 1);
|
||||
for (int i = 1; i < argsCount; i++) {
|
||||
concat.addArg(wrap.getArg(i));
|
||||
}
|
||||
return new ArithNode(ArithOp.ADD, fArg, InsnArg.wrapArg(concat));
|
||||
}
|
||||
int argsCount = wrap.getArgsCount();
|
||||
InsnNode concat = new InsnNode(InsnType.STR_CONCAT, argsCount - 1);
|
||||
for (int i = 1; i < argsCount; i++) {
|
||||
concat.addArg(wrap.getArg(i));
|
||||
}
|
||||
return new ArithNode(ArithOp.ADD, fArg, InsnArg.wrapArg(concat));
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Can't convert field arith insn: {}, mth: {}", insn, mth, e);
|
||||
}
|
||||
|
||||
+11
-11
@@ -49,17 +49,17 @@ public class BlockExceptionHandler extends AbstractVisitor {
|
||||
}
|
||||
ExceptionHandler excHandler = handlerAttr.getHandler();
|
||||
ArgType argType = excHandler.getArgType();
|
||||
if (!block.getInstructions().isEmpty()) {
|
||||
InsnNode me = block.getInstructions().get(0);
|
||||
if (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);
|
||||
excHandler.setArg(resArg);
|
||||
return;
|
||||
}
|
||||
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));
|
||||
|
||||
@@ -1,821 +0,0 @@
|
||||
package jadx.core.dex.visitors.blocksmaker;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
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.attributes.nodes.IgnoreEdgeAttr;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.trycatch.CatchAttr;
|
||||
import jadx.core.dex.trycatch.ExceptionHandler;
|
||||
import jadx.core.dex.trycatch.SplitterBlockAttr;
|
||||
import jadx.core.dex.trycatch.TryCatchBlock;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.dex.visitors.IDexTreeVisitor;
|
||||
import jadx.core.dex.visitors.blocksmaker.helpers.BlocksPair;
|
||||
import jadx.core.dex.visitors.blocksmaker.helpers.BlocksRemoveInfo;
|
||||
import jadx.core.dex.visitors.ssa.LiveVarAnalysis;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.dex.visitors.blocksmaker.BlockSplitter.connect;
|
||||
import static jadx.core.dex.visitors.blocksmaker.BlockSplitter.insertBlockBetween;
|
||||
import static jadx.core.dex.visitors.blocksmaker.BlockSplitter.removeConnection;
|
||||
|
||||
public class BlockFinallyExtract extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BlockFinallyExtract.class);
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) {
|
||||
if (mth.isNoCode() || mth.isNoExceptionHandlers()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
boolean reloadBlocks = false;
|
||||
for (ExceptionHandler excHandler : mth.getExceptionHandlers()) {
|
||||
if (processExceptionHandler(mth, excHandler)) {
|
||||
reloadBlocks = true;
|
||||
}
|
||||
}
|
||||
if (reloadBlocks) {
|
||||
mergeReturnBlocks(mth);
|
||||
BlockProcessor.rerun(mth);
|
||||
}
|
||||
} 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 BlockFinallyExtract) {
|
||||
break;
|
||||
}
|
||||
DepthTraversal.visit(visitor, mth);
|
||||
}
|
||||
} catch (Exception ee) {
|
||||
LOG.error("Undo finally extract failed, mth: {}", mth, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean processExceptionHandler(MethodNode mth, ExceptionHandler excHandler) {
|
||||
// check if handler has exit edge to block not from this handler
|
||||
boolean noExitNode = true;
|
||||
boolean reThrowRemoved = false;
|
||||
|
||||
for (BlockNode excBlock : excHandler.getBlocks()) {
|
||||
if (noExitNode) {
|
||||
noExitNode = excHandler.getBlocks().containsAll(excBlock.getCleanSuccessors());
|
||||
}
|
||||
List<InsnNode> insns = excBlock.getInstructions();
|
||||
int size = insns.size();
|
||||
if (excHandler.isCatchAll()
|
||||
&& size != 0
|
||||
&& insns.get(size - 1).getType() == InsnType.THROW) {
|
||||
reThrowRemoved = true;
|
||||
insns.remove(size - 1);
|
||||
}
|
||||
}
|
||||
if (reThrowRemoved && noExitNode
|
||||
&& extractFinally(mth, excHandler)) {
|
||||
return true;
|
||||
}
|
||||
int totalSize = countInstructions(excHandler);
|
||||
if (totalSize == 0 && reThrowRemoved && noExitNode) {
|
||||
excHandler.getTryBlock().removeHandler(mth, excHandler);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search and remove common code from 'catch' and 'handlers'.
|
||||
*/
|
||||
private static boolean extractFinally(MethodNode mth, ExceptionHandler handler) {
|
||||
int count = handler.getBlocks().size();
|
||||
BitSet bs = new BitSet(count);
|
||||
List<BlockNode> blocks = new ArrayList<>(count);
|
||||
for (BlockNode block : handler.getBlocks()) {
|
||||
List<InsnNode> insns = block.getInstructions();
|
||||
if (!insns.isEmpty()) {
|
||||
if (insns.get(0).getType() != InsnType.MOVE_EXCEPTION) {
|
||||
blocks.add(block);
|
||||
}
|
||||
bs.set(block.getId());
|
||||
}
|
||||
}
|
||||
if (blocks.isEmpty()) {
|
||||
// nothing to do
|
||||
return false;
|
||||
}
|
||||
|
||||
List<BlocksRemoveInfo> removes = new LinkedList<>();
|
||||
Set<BlockNode> splitters = new HashSet<>();
|
||||
|
||||
// remove 'finally' from handlers
|
||||
TryCatchBlock tryBlock = handler.getTryBlock();
|
||||
if (tryBlock.getHandlersCount() > 1) {
|
||||
for (ExceptionHandler otherHandler : tryBlock.getHandlers()) {
|
||||
if (otherHandler == handler) {
|
||||
continue;
|
||||
}
|
||||
for (BlockNode hb : otherHandler.getBlocks()) {
|
||||
BlocksRemoveInfo removeInfo = removeInsns(mth, hb, blocks, bs);
|
||||
if (removeInfo != null) {
|
||||
removes.add(removeInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (removes.size() != tryBlock.getHandlersCount() - 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (ExceptionHandler otherHandler : tryBlock.getHandlers()) {
|
||||
SplitterBlockAttr splitterAttr = otherHandler.getHandlerBlock().get(AType.SPLITTER_BLOCK);
|
||||
if (splitterAttr != null) {
|
||||
BlockNode splBlock = splitterAttr.getBlock();
|
||||
if (!splBlock.getCleanSuccessors().isEmpty()) {
|
||||
splitters.add(splBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove 'finally' from 'try' blocks (dominated by splitter block)
|
||||
boolean removed = false;
|
||||
for (BlockNode splitter : splitters) {
|
||||
BlockNode start = splitter.getCleanSuccessors().get(0);
|
||||
List<BlockNode> list = BlockUtils.collectBlocksDominatedBy(splitter, start);
|
||||
for (BlockNode block : list) {
|
||||
if (bs.get(block.getId())) {
|
||||
continue;
|
||||
}
|
||||
BlocksRemoveInfo removeInfo = removeInsns(mth, block, blocks, bs);
|
||||
if (removeInfo != null) {
|
||||
removes.add(removeInfo);
|
||||
removed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!removed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 'finally' extract confirmed, run remove steps
|
||||
|
||||
LiveVarAnalysis laBefore = null;
|
||||
boolean runReMap = isReMapNeeded(removes);
|
||||
if (runReMap) {
|
||||
laBefore = new LiveVarAnalysis(mth);
|
||||
laBefore.runAnalysis();
|
||||
}
|
||||
|
||||
int removeApplied = 0;
|
||||
for (BlocksRemoveInfo removeInfo : removes) {
|
||||
if (applyRemove(mth, removeInfo)) {
|
||||
removeApplied++;
|
||||
removeInfo.setApplied(true);
|
||||
}
|
||||
}
|
||||
if (removeApplied == 0) {
|
||||
return false;
|
||||
}
|
||||
if (removeApplied != removes.size()) {
|
||||
throw new JadxRuntimeException("Some finally instructions failed to remove: "
|
||||
+ removes.stream().filter(n -> !n.isApplied()).map(BlocksRemoveInfo::toString).collect(Collectors.joining(","))
|
||||
);
|
||||
}
|
||||
|
||||
LiveVarAnalysis laAfter = null;
|
||||
|
||||
// remove 'move-exception' instruction
|
||||
BlockNode handlerBlock = handler.getHandlerBlock();
|
||||
InsnNode me = BlockUtils.getLastInsn(handlerBlock);
|
||||
if (me != null && me.getType() == InsnType.MOVE_EXCEPTION) {
|
||||
boolean replaced = false;
|
||||
List<InsnNode> insnsList = handlerBlock.getInstructions();
|
||||
if (!handlerBlock.getCleanSuccessors().isEmpty()) {
|
||||
laAfter = new LiveVarAnalysis(mth);
|
||||
laAfter.runAnalysis();
|
||||
|
||||
RegisterArg resArg = me.getResult();
|
||||
BlockNode succ = handlerBlock.getCleanSuccessors().get(0);
|
||||
if (laAfter.isLive(succ, resArg.getRegNum())) {
|
||||
// kill variable
|
||||
InsnNode kill = new InsnNode(InsnType.NOP, 0);
|
||||
kill.setResult(resArg);
|
||||
kill.add(AFlag.REMOVE);
|
||||
insnsList.set(insnsList.size() - 1, kill);
|
||||
replaced = true;
|
||||
}
|
||||
}
|
||||
if (!replaced) {
|
||||
insnsList.remove(insnsList.size() - 1);
|
||||
handlerBlock.add(AFlag.SKIP);
|
||||
}
|
||||
}
|
||||
|
||||
// generate 'move' instruction for mapped register pairs
|
||||
if (runReMap) {
|
||||
if (laAfter == null) {
|
||||
laAfter = new LiveVarAnalysis(mth);
|
||||
laAfter.runAnalysis();
|
||||
}
|
||||
performVariablesReMap(mth, removes, laBefore, laAfter);
|
||||
}
|
||||
|
||||
handler.setFinally(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void performVariablesReMap(MethodNode mth, List<BlocksRemoveInfo> removes,
|
||||
LiveVarAnalysis laBefore, LiveVarAnalysis laAfter) {
|
||||
BitSet processed = new BitSet(mth.getRegsCount());
|
||||
for (BlocksRemoveInfo removeInfo : removes) {
|
||||
processed.clear();
|
||||
BlocksPair start = removeInfo.getStart();
|
||||
BlockNode insertBlockBefore = start.getFirst();
|
||||
BlockNode insertBlock = start.getSecond();
|
||||
if (removeInfo.getRegMap().isEmpty() || insertBlock == null) {
|
||||
continue;
|
||||
}
|
||||
for (Map.Entry<RegisterArg, RegisterArg> entry : removeInfo.getRegMap().entrySet()) {
|
||||
RegisterArg fromReg = entry.getKey();
|
||||
RegisterArg toReg = entry.getValue();
|
||||
int fromRegNum = fromReg.getRegNum();
|
||||
int toRegNum = toReg.getRegNum();
|
||||
if (!processed.get(fromRegNum)) {
|
||||
boolean liveFromBefore = laBefore.isLive(insertBlockBefore, fromRegNum);
|
||||
boolean liveFromAfter = laAfter.isLive(insertBlock, fromRegNum);
|
||||
boolean liveToAfter = laAfter.isLive(insertBlock, toRegNum);
|
||||
if (liveToAfter && liveFromBefore) {
|
||||
// merge 'to' and 'from' registers
|
||||
InsnNode merge = new InsnNode(InsnType.MERGE, 2);
|
||||
merge.setResult(toReg.duplicate());
|
||||
merge.addArg(toReg.duplicate());
|
||||
merge.addArg(fromReg.duplicate());
|
||||
injectInsn(mth, insertBlock, merge);
|
||||
} else if (liveFromBefore) {
|
||||
// remap variable
|
||||
InsnNode move = new InsnNode(InsnType.MOVE, 1);
|
||||
move.setResult(toReg.duplicate());
|
||||
move.addArg(fromReg.duplicate());
|
||||
injectInsn(mth, insertBlock, move);
|
||||
} else if (liveFromAfter) {
|
||||
// kill variable
|
||||
InsnNode kill = new InsnNode(InsnType.NOP, 0);
|
||||
kill.setResult(fromReg.duplicate());
|
||||
kill.add(AFlag.REMOVE);
|
||||
injectInsn(mth, insertBlock, kill);
|
||||
}
|
||||
processed.set(fromRegNum);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void injectInsn(MethodNode mth, BlockNode insertBlock, InsnNode insn) {
|
||||
insn.add(AFlag.SYNTHETIC);
|
||||
if (insertBlock.getInstructions().isEmpty()) {
|
||||
insertBlock.getInstructions().add(insn);
|
||||
} else {
|
||||
BlockNode succBlock = splitBlock(mth, insertBlock, 0);
|
||||
BlockNode predBlock = succBlock.getPredecessors().get(0);
|
||||
predBlock.getInstructions().add(insn);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isReMapNeeded(List<BlocksRemoveInfo> removes) {
|
||||
for (BlocksRemoveInfo removeInfo : removes) {
|
||||
if (!removeInfo.getRegMap().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static BlocksRemoveInfo removeInsns(MethodNode mth, BlockNode remBlock, List<BlockNode> blocks, BitSet bs) {
|
||||
if (blocks.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
BlockNode startBlock = blocks.get(0);
|
||||
BlocksRemoveInfo removeInfo = checkFromFirstBlock(remBlock, startBlock, bs);
|
||||
if (removeInfo == null) {
|
||||
return null;
|
||||
}
|
||||
Set<BlocksPair> outs = removeInfo.getOuts();
|
||||
if (outs.size() == 1) {
|
||||
return removeInfo;
|
||||
}
|
||||
// check if several 'return' blocks maps to one out
|
||||
if (mergeReturns(mth, outs)) {
|
||||
return removeInfo;
|
||||
}
|
||||
LOG.debug("Unexpected finally block outs count: {}", outs);
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean mergeReturns(MethodNode mth, Set<BlocksPair> outs) {
|
||||
Set<BlockNode> rightOuts = new HashSet<>();
|
||||
boolean allReturns = true;
|
||||
for (BlocksPair outPair : outs) {
|
||||
BlockNode first = outPair.getFirst();
|
||||
if (!first.isReturnBlock()) {
|
||||
allReturns = false;
|
||||
}
|
||||
rightOuts.add(outPair.getSecond());
|
||||
}
|
||||
if (!allReturns || rightOuts.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
Iterator<BlocksPair> it = outs.iterator();
|
||||
while (it.hasNext()) {
|
||||
BlocksPair out = it.next();
|
||||
BlockNode returnBlock = out.getFirst();
|
||||
if (!returnBlock.contains(AFlag.ORIG_RETURN)) {
|
||||
markForRemove(mth, returnBlock);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static BlocksRemoveInfo checkFromFirstBlock(BlockNode remBlock, BlockNode startBlock, BitSet bs) {
|
||||
BlocksRemoveInfo removeInfo = isStartBlock(remBlock, startBlock);
|
||||
if (removeInfo == null) {
|
||||
return null;
|
||||
}
|
||||
if (!checkBlocksTree(remBlock, startBlock, removeInfo, bs)) {
|
||||
return null;
|
||||
}
|
||||
return removeInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 'Finally' instructions can start in the middle of the first block.
|
||||
*/
|
||||
private static @Nullable
|
||||
BlocksRemoveInfo isStartBlock(BlockNode remBlock, BlockNode startBlock) {
|
||||
List<InsnNode> remInsns = remBlock.getInstructions();
|
||||
List<InsnNode> startInsns = startBlock.getInstructions();
|
||||
if (remInsns.size() < startInsns.size()) {
|
||||
return null;
|
||||
}
|
||||
// first - fast check
|
||||
int startPos = remInsns.size() - startInsns.size();
|
||||
int endPos = 0;
|
||||
if (!checkInsns(remInsns, startInsns, startPos, null)) {
|
||||
if (checkInsns(remInsns, startInsns, 0, null)) {
|
||||
startPos = 0;
|
||||
endPos = startInsns.size();
|
||||
} else {
|
||||
boolean found = false;
|
||||
for (int i = 1; i < startPos; i++) {
|
||||
if (checkInsns(remInsns, startInsns, i, null)) {
|
||||
startPos = i;
|
||||
endPos = startInsns.size() + i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
BlocksPair startPair = new BlocksPair(remBlock, startBlock);
|
||||
BlocksRemoveInfo removeInfo = new BlocksRemoveInfo(startPair);
|
||||
removeInfo.setStartSplitIndex(startPos);
|
||||
removeInfo.setEndSplitIndex(endPos);
|
||||
if (endPos != 0) {
|
||||
removeInfo.setEnd(startPair);
|
||||
}
|
||||
// second - run checks again for collect registers mapping
|
||||
if (!checkInsns(remInsns, startInsns, startPos, removeInfo)) {
|
||||
return null;
|
||||
}
|
||||
return removeInfo;
|
||||
}
|
||||
|
||||
private static boolean checkInsns(List<InsnNode> remInsns, List<InsnNode> startInsns, int delta,
|
||||
@Nullable BlocksRemoveInfo removeInfo) {
|
||||
for (int i = startInsns.size() - 1; i >= 0; i--) {
|
||||
InsnNode startInsn = startInsns.get(i);
|
||||
InsnNode remInsn = remInsns.get(delta + i);
|
||||
if (!sameInsns(remInsn, startInsn, removeInfo)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean checkBlocksTree(BlockNode remBlock, BlockNode startBlock,
|
||||
@NotNull BlocksRemoveInfo removeInfo, BitSet bs) {
|
||||
// skip check on start block
|
||||
if (!removeInfo.getProcessed().isEmpty()
|
||||
&& !sameBlocks(remBlock, startBlock, removeInfo)) {
|
||||
return false;
|
||||
}
|
||||
BlocksPair currentPair = new BlocksPair(remBlock, startBlock);
|
||||
removeInfo.getProcessed().add(currentPair);
|
||||
|
||||
List<BlockNode> baseCS = startBlock.getCleanSuccessors();
|
||||
List<BlockNode> remCS = remBlock.getCleanSuccessors();
|
||||
if (baseCS.size() != remCS.size()) {
|
||||
removeInfo.getOuts().add(currentPair);
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < baseCS.size(); i++) {
|
||||
BlockNode sBlock = baseCS.get(i);
|
||||
BlockNode rBlock = remCS.get(i);
|
||||
if (bs.get(sBlock.getId())) {
|
||||
if (removeInfo.getEndSplitIndex() != 0) {
|
||||
// end block is not correct
|
||||
return false;
|
||||
}
|
||||
if (!checkBlocksTree(rBlock, sBlock, removeInfo, bs)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
removeInfo.getOuts().add(new BlocksPair(rBlock, sBlock));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean sameBlocks(BlockNode remBlock, BlockNode finallyBlock,
|
||||
@NotNull BlocksRemoveInfo removeInfo) {
|
||||
List<InsnNode> first = remBlock.getInstructions();
|
||||
List<InsnNode> second = finallyBlock.getInstructions();
|
||||
if (first.size() < second.size()) {
|
||||
return false;
|
||||
}
|
||||
int size = second.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (!sameInsns(first.get(i), second.get(i), removeInfo)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (first.size() > second.size()) {
|
||||
removeInfo.setEndSplitIndex(second.size());
|
||||
removeInfo.setEnd(new BlocksPair(remBlock, finallyBlock));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean sameInsns(InsnNode remInsn, InsnNode fInsn, @Nullable BlocksRemoveInfo removeInfo) {
|
||||
if (!remInsn.isSame(fInsn)) {
|
||||
return false;
|
||||
}
|
||||
// TODO: check instance arg in ConstructorInsn
|
||||
// TODO: compare literals
|
||||
for (int i = 0; i < remInsn.getArgsCount(); i++) {
|
||||
InsnArg remArg = remInsn.getArg(i);
|
||||
InsnArg fArg = fInsn.getArg(i);
|
||||
if (remArg.isRegister() != fArg.isRegister()) {
|
||||
return false;
|
||||
}
|
||||
if (removeInfo != null && fArg.isRegister()) {
|
||||
RegisterArg remReg = (RegisterArg) remArg;
|
||||
RegisterArg fReg = (RegisterArg) fArg;
|
||||
if (remReg.getRegNum() != fReg.getRegNum()) {
|
||||
RegisterArg mapReg = removeInfo.getRegMap().get(remArg);
|
||||
if (mapReg == null) {
|
||||
removeInfo.getRegMap().put(remReg, fReg);
|
||||
} else if (!mapReg.equalRegisterAndType(fReg)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean applyRemove(MethodNode mth, BlocksRemoveInfo removeInfo) {
|
||||
BlockNode remBlock = removeInfo.getStart().getFirst();
|
||||
BlockNode startBlock = removeInfo.getStart().getSecond();
|
||||
|
||||
if (remBlock.contains(AFlag.REMOVE)) {
|
||||
// already processed
|
||||
return true;
|
||||
}
|
||||
if (remBlock.getPredecessors().size() != 1) {
|
||||
LOG.warn("Finally extract failed: remBlock pred: {}, {}, method: {}", remBlock, remBlock.getPredecessors(), mth);
|
||||
return false;
|
||||
}
|
||||
if (removeInfo.getOuts().isEmpty()) {
|
||||
ErrorsCounter.methodWarn(mth, "Failed to extract finally block: empty outs");
|
||||
return false;
|
||||
}
|
||||
// safe checks finished, altering blocks tree
|
||||
// all error must throw exception to undo changes
|
||||
|
||||
BlockNode remBlockPred = remBlock.getPredecessors().get(0);
|
||||
removeInfo.setStartPredecessor(remBlockPred);
|
||||
|
||||
int startSplitIndex = removeInfo.getStartSplitIndex();
|
||||
int endSplitIndex = removeInfo.getEndSplitIndex();
|
||||
if (removeInfo.getStart().equals(removeInfo.getEnd())) {
|
||||
removeInfo.setEndSplitIndex(endSplitIndex - startSplitIndex);
|
||||
}
|
||||
// split start block (remBlock)
|
||||
if (startSplitIndex > 0) {
|
||||
remBlock = splitBlock(mth, remBlock, startSplitIndex);
|
||||
// change start block in removeInfo
|
||||
removeInfo.getProcessed().remove(removeInfo.getStart());
|
||||
BlocksPair newStart = new BlocksPair(remBlock, startBlock);
|
||||
// removeInfo.setStart(newStart);
|
||||
removeInfo.getProcessed().add(newStart);
|
||||
}
|
||||
// split end block
|
||||
if (endSplitIndex > 0) {
|
||||
BlocksPair end = removeInfo.getEnd();
|
||||
BlockNode newOut = splitBlock(mth, end.getFirst(), endSplitIndex);
|
||||
for (BlockNode s : newOut.getSuccessors()) {
|
||||
BlocksPair replaceOut = null;
|
||||
Iterator<BlocksPair> it = removeInfo.getOuts().iterator();
|
||||
while (it.hasNext()) {
|
||||
BlocksPair outPair = it.next();
|
||||
if (outPair.getFirst().equals(s)) {
|
||||
it.remove();
|
||||
replaceOut = new BlocksPair(newOut, outPair.getSecond());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (replaceOut != null) {
|
||||
removeInfo.getOuts().add(replaceOut);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Set<BlocksPair> outs = removeInfo.getOuts();
|
||||
if (outs.isEmpty()) {
|
||||
throw new JadxRuntimeException("Failed to extract finally block: all outs is deleted");
|
||||
}
|
||||
BlocksPair out = outs.iterator().next();
|
||||
BlockNode rOut = out.getFirst();
|
||||
BlockNode sOut = out.getSecond();
|
||||
|
||||
// redirect out edges
|
||||
List<BlockNode> filtPreds = BlockUtils.filterPredecessors(sOut);
|
||||
if (filtPreds.size() > 1) {
|
||||
BlockNode pred = sOut.getPredecessors().get(0);
|
||||
BlockNode newPred = BlockSplitter.insertBlockBetween(mth, pred, sOut);
|
||||
for (BlockNode predBlock : new ArrayList<>(sOut.getPredecessors())) {
|
||||
if (predBlock != newPred) {
|
||||
BlockSplitter.replaceConnection(predBlock, sOut, newPred);
|
||||
}
|
||||
}
|
||||
rOut.getPredecessors().clear();
|
||||
addIgnoredEdge(newPred, rOut);
|
||||
connect(newPred, rOut);
|
||||
} else if (filtPreds.size() == 1) {
|
||||
BlockNode pred = filtPreds.get(0);
|
||||
BlockNode repl = removeInfo.getBySecond(pred);
|
||||
if (repl == null) {
|
||||
throw new JadxRuntimeException("Block not found by " + pred + ", in " + removeInfo);
|
||||
}
|
||||
removeConnection(pred, rOut);
|
||||
addIgnoredEdge(repl, rOut);
|
||||
connect(repl, rOut);
|
||||
} else {
|
||||
throw new JadxRuntimeException("Finally extract failed, unexpected preds: " + filtPreds
|
||||
+ " for " + sOut + ", method: " + mth);
|
||||
}
|
||||
|
||||
// redirect input edges
|
||||
for (BlockNode pred : new ArrayList<>(remBlock.getPredecessors())) {
|
||||
BlockNode middle = insertBlockBetween(mth, pred, remBlock);
|
||||
removeConnection(middle, remBlock);
|
||||
connect(middle, startBlock);
|
||||
addIgnoredEdge(middle, startBlock);
|
||||
connect(middle, rOut);
|
||||
BlockSplitter.replaceTarget(middle, remBlock, rOut);
|
||||
}
|
||||
|
||||
// mark blocks for remove
|
||||
markForRemove(mth, remBlock);
|
||||
for (BlocksPair pair : removeInfo.getProcessed()) {
|
||||
markForRemove(mth, pair.getFirst());
|
||||
BlockNode second = pair.getSecond();
|
||||
second.updateCleanSuccessors();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split one block into connected 2 blocks with same connections.
|
||||
*
|
||||
* @return new successor block
|
||||
*/
|
||||
private static BlockNode splitBlock(MethodNode mth, BlockNode block, int splitIndex) {
|
||||
BlockNode newBlock = BlockSplitter.startNewBlock(mth, -1);
|
||||
|
||||
newBlock.getSuccessors().addAll(block.getSuccessors());
|
||||
for (BlockNode s : new ArrayList<>(block.getSuccessors())) {
|
||||
removeConnection(block, s);
|
||||
connect(newBlock, s);
|
||||
}
|
||||
block.getSuccessors().clear();
|
||||
connect(block, newBlock);
|
||||
block.updateCleanSuccessors();
|
||||
newBlock.updateCleanSuccessors();
|
||||
|
||||
List<InsnNode> insns = block.getInstructions();
|
||||
int size = insns.size();
|
||||
for (int i = splitIndex; i < size; i++) {
|
||||
InsnNode insnNode = insns.get(i);
|
||||
insnNode.add(AFlag.SKIP);
|
||||
newBlock.getInstructions().add(insnNode);
|
||||
}
|
||||
Iterator<InsnNode> it = insns.iterator();
|
||||
while (it.hasNext()) {
|
||||
InsnNode insnNode = it.next();
|
||||
if (insnNode.contains(AFlag.SKIP)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
for (InsnNode insnNode : newBlock.getInstructions()) {
|
||||
insnNode.remove(AFlag.SKIP);
|
||||
}
|
||||
return newBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbind block for removing.
|
||||
*/
|
||||
private static void markForRemove(MethodNode mth, BlockNode block) {
|
||||
for (BlockNode p : block.getPredecessors()) {
|
||||
p.getSuccessors().remove(block);
|
||||
p.updateCleanSuccessors();
|
||||
}
|
||||
for (BlockNode s : block.getSuccessors()) {
|
||||
s.getPredecessors().remove(block);
|
||||
}
|
||||
block.getPredecessors().clear();
|
||||
block.getSuccessors().clear();
|
||||
block.add(AFlag.REMOVE);
|
||||
block.remove(AFlag.SKIP);
|
||||
|
||||
CatchAttr catchAttr = block.get(AType.CATCH_BLOCK);
|
||||
if (catchAttr != null) {
|
||||
catchAttr.getTryBlock().removeBlock(mth, block);
|
||||
for (BlockNode skipBlock : mth.getBasicBlocks()) {
|
||||
if (skipBlock.contains(AFlag.SKIP)) {
|
||||
markForRemove(mth, skipBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void addIgnoredEdge(BlockNode from, BlockNode toBlock) {
|
||||
IgnoreEdgeAttr edgeAttr = from.get(AType.IGNORE_EDGE);
|
||||
if (edgeAttr == null) {
|
||||
edgeAttr = new IgnoreEdgeAttr();
|
||||
from.addAttr(edgeAttr);
|
||||
}
|
||||
edgeAttr.getBlocks().add(toBlock);
|
||||
}
|
||||
|
||||
private static int countInstructions(ExceptionHandler excHandler) {
|
||||
int totalSize = 0;
|
||||
for (BlockNode excBlock : excHandler.getBlocks()) {
|
||||
List<InsnNode> list = excBlock.getInstructions();
|
||||
if (!list.isEmpty() && list.get(0).getType() == InsnType.MOVE_EXCEPTION) {
|
||||
// don't count 'move-exception' it will be removed later
|
||||
totalSize--;
|
||||
}
|
||||
totalSize += list.size();
|
||||
}
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge return block with same predecessor.
|
||||
*/
|
||||
private static void mergeReturnBlocks(MethodNode mth) {
|
||||
List<BlockNode> exitBlocks = mth.getExitBlocks();
|
||||
BlockNode pred = getFinallyOutBlock(exitBlocks);
|
||||
if (pred == null) {
|
||||
return;
|
||||
}
|
||||
IgnoreEdgeAttr edgeAttr = pred.get(AType.IGNORE_EDGE);
|
||||
if (edgeAttr == null) {
|
||||
return;
|
||||
}
|
||||
List<BlockNode> merge = new LinkedList<>();
|
||||
for (BlockNode blockNode : pred.getSuccessors()) {
|
||||
if (blockNode.contains(AFlag.RETURN)) {
|
||||
merge.add(blockNode);
|
||||
}
|
||||
}
|
||||
if (merge.size() < 2) {
|
||||
return;
|
||||
}
|
||||
// select 'original' return block
|
||||
BlockNode origReturnBlock = null;
|
||||
for (BlockNode ret : merge) {
|
||||
if (ret.contains(AFlag.ORIG_RETURN)) {
|
||||
origReturnBlock = ret;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (origReturnBlock == null) {
|
||||
return;
|
||||
}
|
||||
for (BlockNode mb : merge) {
|
||||
if (mb == origReturnBlock) {
|
||||
continue;
|
||||
}
|
||||
for (BlockNode remPred : mb.getPredecessors()) {
|
||||
connect(remPred, origReturnBlock);
|
||||
}
|
||||
markForRemove(mth, mb);
|
||||
edgeAttr.getBlocks().remove(mb);
|
||||
}
|
||||
mergeSyntheticPredecessors(mth, origReturnBlock);
|
||||
}
|
||||
|
||||
private static void mergeSyntheticPredecessors(MethodNode mth, BlockNode block) {
|
||||
List<BlockNode> preds = new ArrayList<>(block.getPredecessors());
|
||||
Iterator<BlockNode> it = preds.iterator();
|
||||
while (it.hasNext()) {
|
||||
BlockNode predBlock = it.next();
|
||||
if (!predBlock.isSynthetic()) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
if (preds.size() < 2) {
|
||||
return;
|
||||
}
|
||||
BlockNode commonBlock = null;
|
||||
for (BlockNode predBlock : preds) {
|
||||
List<BlockNode> successors = predBlock.getSuccessors();
|
||||
if (successors.size() != 2) {
|
||||
return;
|
||||
}
|
||||
BlockNode cmnBlk = BlockUtils.selectOtherSafe(block, successors);
|
||||
if (commonBlock == null) {
|
||||
commonBlock = cmnBlk;
|
||||
} else if (cmnBlk != commonBlock) {
|
||||
return;
|
||||
}
|
||||
if (!predBlock.contains(AType.IGNORE_EDGE)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (commonBlock == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// merge confirmed
|
||||
BlockNode mergeBlock = null;
|
||||
for (BlockNode predBlock : preds) {
|
||||
if (mergeBlock == null) {
|
||||
mergeBlock = predBlock;
|
||||
continue;
|
||||
}
|
||||
for (BlockNode remPred : predBlock.getPredecessors()) {
|
||||
connect(remPred, mergeBlock);
|
||||
}
|
||||
markForRemove(mth, predBlock);
|
||||
}
|
||||
}
|
||||
|
||||
private static BlockNode getFinallyOutBlock(List<BlockNode> exitBlocks) {
|
||||
for (BlockNode exitBlock : exitBlocks) {
|
||||
for (BlockNode exitPred : exitBlock.getPredecessors()) {
|
||||
IgnoreEdgeAttr edgeAttr = exitPred.get(AType.IGNORE_EDGE);
|
||||
if (edgeAttr != null && edgeAttr.contains(exitBlock)) {
|
||||
return exitPred;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -16,6 +17,7 @@ import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.LoopInfo;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.Edge;
|
||||
@@ -66,17 +68,27 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
markReturnBlocks(mth);
|
||||
|
||||
if (i++ > 100) {
|
||||
throw new AssertionError("Can't fix method cfg: " + mth);
|
||||
mth.addWarn("CFG modification limit reached, blocks count: " + mth.getBasicBlocks().size());
|
||||
break;
|
||||
}
|
||||
}
|
||||
checkForUnreachableBlocks(mth);
|
||||
|
||||
computeDominanceFrontier(mth);
|
||||
registerLoops(mth);
|
||||
processNestedLoops(mth);
|
||||
}
|
||||
|
||||
private static void checkForUnreachableBlocks(MethodNode mth) {
|
||||
mth.getBasicBlocks().forEach(block -> {
|
||||
if (block.getPredecessors().isEmpty() && block != mth.getEnterBlock()) {
|
||||
throw new JadxRuntimeException("Unreachable block: " + block);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean canRemoveBlock(BlockNode block) {
|
||||
return block.getInstructions().isEmpty()
|
||||
&& !block.isSynthetic()
|
||||
&& block.isAttrStorageEmpty()
|
||||
&& block.getSuccessors().size() <= 1
|
||||
&& !block.getPredecessors().isEmpty();
|
||||
@@ -117,6 +129,7 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
if (lastInsn != null && lastInsn.getType() == InsnType.IF) {
|
||||
return false;
|
||||
}
|
||||
// TODO: implement insn extraction into separate block for partial predecessors
|
||||
int sameInsnCount = getSameLastInsnCount(predecessors);
|
||||
if (sameInsnCount > 0) {
|
||||
List<InsnNode> insns = getLastInsns(predecessors.get(0), sameInsnCount);
|
||||
@@ -169,7 +182,43 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static boolean isSame(InsnNode insn, InsnNode curInsn) {
|
||||
return /*insn.getType() == InsnType.MOVE &&*/ insn.isDeepEquals(curInsn) && insn.canReorder();
|
||||
return isInsnsEquals(insn, curInsn) && insn.canReorder();
|
||||
}
|
||||
|
||||
private static boolean isInsnsEquals(InsnNode insn, InsnNode otherInsn) {
|
||||
if (insn == otherInsn) {
|
||||
return true;
|
||||
}
|
||||
if (insn.isSame(otherInsn)
|
||||
&& sameArgs(insn.getResult(), otherInsn.getResult())) {
|
||||
int argsCount = insn.getArgsCount();
|
||||
for (int i = 0; i < argsCount; i++) {
|
||||
if (!sameArgs(insn.getArg(i), otherInsn.getArg(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean sameArgs(@Nullable InsnArg arg, @Nullable InsnArg otherArg) {
|
||||
if (arg == otherArg) {
|
||||
return true;
|
||||
}
|
||||
if (arg == null || otherArg == null) {
|
||||
return false;
|
||||
}
|
||||
if (arg.getClass().equals(otherArg.getClass())) {
|
||||
if (arg.isRegister()) {
|
||||
return ((RegisterArg) arg).getRegNum() == ((RegisterArg) otherArg).getRegNum();
|
||||
}
|
||||
if (arg.isLiteral()) {
|
||||
return ((LiteralArg) arg).getLiteral() == ((LiteralArg) otherArg).getLiteral();
|
||||
}
|
||||
throw new JadxRuntimeException("Unexpected InsnArg types: " + arg + " and " + otherArg);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static InsnNode getInsnsFromEnd(BlockNode block, int number) {
|
||||
@@ -192,32 +241,7 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
BlockNode entryBlock = mth.getEnterBlock();
|
||||
entryBlock.getDoms().clear();
|
||||
entryBlock.getDoms().set(entryBlock.getId());
|
||||
|
||||
BitSet dset = new BitSet(nBlocks);
|
||||
boolean changed;
|
||||
do {
|
||||
changed = false;
|
||||
for (BlockNode block : basicBlocks) {
|
||||
if (block == entryBlock) {
|
||||
continue;
|
||||
}
|
||||
BitSet d = block.getDoms();
|
||||
if (!changed) {
|
||||
dset.clear();
|
||||
dset.or(d);
|
||||
}
|
||||
for (BlockNode pred : block.getPredecessors()) {
|
||||
d.and(pred.getDoms());
|
||||
}
|
||||
d.set(block.getId());
|
||||
if (!changed && !d.equals(dset)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
} while (changed);
|
||||
|
||||
calcDominators(basicBlocks, entryBlock);
|
||||
markLoops(mth);
|
||||
|
||||
// clear self dominance
|
||||
@@ -228,7 +252,38 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
}
|
||||
});
|
||||
|
||||
// calculate immediate dominators
|
||||
calcImmediateDominators(basicBlocks, entryBlock);
|
||||
}
|
||||
|
||||
private static void calcDominators(List<BlockNode> basicBlocks, BlockNode entryBlock) {
|
||||
entryBlock.getDoms().clear();
|
||||
entryBlock.getDoms().set(entryBlock.getId());
|
||||
|
||||
BitSet domSet = new BitSet(basicBlocks.size());
|
||||
boolean changed;
|
||||
do {
|
||||
changed = false;
|
||||
for (BlockNode block : basicBlocks) {
|
||||
if (block == entryBlock) {
|
||||
continue;
|
||||
}
|
||||
BitSet d = block.getDoms();
|
||||
if (!changed) {
|
||||
domSet.clear();
|
||||
domSet.or(d);
|
||||
}
|
||||
for (BlockNode pred : block.getPredecessors()) {
|
||||
d.and(pred.getDoms());
|
||||
}
|
||||
d.set(block.getId());
|
||||
if (!changed && !d.equals(domSet)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
} while (changed);
|
||||
}
|
||||
|
||||
private static void calcImmediateDominators(List<BlockNode> basicBlocks, BlockNode entryBlock) {
|
||||
for (BlockNode block : basicBlocks) {
|
||||
if (block == entryBlock) {
|
||||
continue;
|
||||
@@ -326,7 +381,7 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
private static void markLoops(MethodNode mth) {
|
||||
mth.getBasicBlocks().forEach(block -> {
|
||||
// Every successor that dominates its predecessor is a header of a loop,
|
||||
// block -> succ is a back edge.
|
||||
// block -> successor is a back edge.
|
||||
block.getSuccessors().forEach(successor -> {
|
||||
if (block.getDoms().get(successor.getId())) {
|
||||
successor.add(AFlag.LOOP_START);
|
||||
@@ -413,59 +468,95 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static boolean checkLoops(MethodNode mth, BlockNode block) {
|
||||
// check loops
|
||||
List<LoopInfo> loops = block.getAll(AType.LOOP);
|
||||
if (loops.size() > 1) {
|
||||
boolean oneHeader = true;
|
||||
for (LoopInfo loop : loops) {
|
||||
if (loop.getStart() != block) {
|
||||
oneHeader = false;
|
||||
break;
|
||||
int loopsCount = loops.size();
|
||||
if (loopsCount == 0) {
|
||||
return false;
|
||||
}
|
||||
if (loopsCount > 1 && splitLoops(mth, block, loops)) {
|
||||
return true;
|
||||
}
|
||||
if (loopsCount == 1) {
|
||||
LoopInfo loop = loops.get(0);
|
||||
return insertBlocksForBreak(mth, loop)
|
||||
|| insertBlocksForContinue(mth, loop)
|
||||
|| insertBlockForProdecessors(mth, loop);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert additional blocks for possible 'break' insertion
|
||||
*/
|
||||
private static boolean insertBlocksForBreak(MethodNode mth, LoopInfo loop) {
|
||||
boolean change = false;
|
||||
List<Edge> edges = loop.getExitEdges();
|
||||
if (!edges.isEmpty()) {
|
||||
for (Edge edge : edges) {
|
||||
BlockNode target = edge.getTarget();
|
||||
BlockNode source = edge.getSource();
|
||||
if (!target.contains(AFlag.SYNTHETIC) && !source.contains(AFlag.SYNTHETIC)) {
|
||||
BlockSplitter.insertBlockBetween(mth, source, target);
|
||||
change = true;
|
||||
}
|
||||
}
|
||||
if (oneHeader) {
|
||||
// several back edges connected to one loop header => make additional block
|
||||
BlockNode newLoopEnd = BlockSplitter.startNewBlock(mth, block.getStartOffset());
|
||||
newLoopEnd.add(AFlag.SYNTHETIC);
|
||||
connect(newLoopEnd, block);
|
||||
for (LoopInfo la : loops) {
|
||||
BlockSplitter.replaceConnection(la.getEnd(), block, newLoopEnd);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (loops.size() == 1) {
|
||||
LoopInfo loop = loops.get(0);
|
||||
// insert additional blocks for possible 'break' insertion
|
||||
List<Edge> edges = loop.getExitEdges();
|
||||
if (!edges.isEmpty()) {
|
||||
boolean change = false;
|
||||
for (Edge edge : edges) {
|
||||
BlockNode target = edge.getTarget();
|
||||
BlockNode source = edge.getSource();
|
||||
if (!target.contains(AFlag.SYNTHETIC) && !source.contains(AFlag.SYNTHETIC)) {
|
||||
BlockSplitter.insertBlockBetween(mth, source, target);
|
||||
change = true;
|
||||
}
|
||||
}
|
||||
if (change) {
|
||||
return true;
|
||||
return change;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert additional blocks for possible 'continue' insertion
|
||||
*/
|
||||
private static boolean insertBlocksForContinue(MethodNode mth, LoopInfo loop) {
|
||||
BlockNode loopEnd = loop.getEnd();
|
||||
boolean change = false;
|
||||
List<BlockNode> preds = loopEnd.getPredecessors();
|
||||
if (preds.size() > 1) {
|
||||
for (BlockNode pred : new ArrayList<>(preds)) {
|
||||
if (!pred.contains(AFlag.SYNTHETIC)) {
|
||||
BlockSplitter.insertBlockBetween(mth, pred, loopEnd);
|
||||
change = true;
|
||||
}
|
||||
}
|
||||
// insert additional blocks for possible 'continue' insertion
|
||||
BlockNode loopEnd = loop.getEnd();
|
||||
if (loopEnd.getPredecessors().size() > 1) {
|
||||
boolean change = false;
|
||||
List<BlockNode> nodes = new ArrayList<>(loopEnd.getPredecessors());
|
||||
for (BlockNode pred : nodes) {
|
||||
if (!pred.contains(AFlag.SYNTHETIC)) {
|
||||
BlockSplitter.insertBlockBetween(mth, pred, loopEnd);
|
||||
change = true;
|
||||
}
|
||||
}
|
||||
return change;
|
||||
}
|
||||
return change;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert additional block if loop header has several predecessors (exclude back edges)
|
||||
*/
|
||||
private static boolean insertBlockForProdecessors(MethodNode mth, LoopInfo loop) {
|
||||
BlockNode loopHeader = loop.getStart();
|
||||
List<BlockNode> preds = loopHeader.getPredecessors();
|
||||
if (preds.size() > 2) {
|
||||
List<BlockNode> blocks = new LinkedList<>(preds);
|
||||
blocks.removeIf(block -> block.contains(AFlag.LOOP_END));
|
||||
BlockNode first = blocks.remove(0);
|
||||
BlockNode preHeader = BlockSplitter.insertBlockBetween(mth, first, loopHeader);
|
||||
blocks.forEach(block -> BlockSplitter.replaceConnection(block, loopHeader, preHeader));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean splitLoops(MethodNode mth, BlockNode block, List<LoopInfo> loops) {
|
||||
boolean oneHeader = true;
|
||||
for (LoopInfo loop : loops) {
|
||||
if (loop.getStart() != block) {
|
||||
oneHeader = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (oneHeader) {
|
||||
// several back edges connected to one loop header => make additional block
|
||||
BlockNode newLoopEnd = BlockSplitter.startNewBlock(mth, block.getStartOffset());
|
||||
newLoopEnd.add(AFlag.SYNTHETIC);
|
||||
connect(newLoopEnd, block);
|
||||
for (LoopInfo la : loops) {
|
||||
BlockSplitter.replaceConnection(la.getEnd(), block, newLoopEnd);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -523,11 +614,8 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private static RegisterArg getMoveExceptionRegister(BlockNode block) {
|
||||
if (block.getInstructions().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
InsnNode insn = block.getInstructions().get(0);
|
||||
if (insn.getType() != InsnType.MOVE_EXCEPTION) {
|
||||
InsnNode insn = BlockUtils.getLastInsn(block);
|
||||
if (insn == null || insn.getType() != InsnType.MOVE_EXCEPTION) {
|
||||
return null;
|
||||
}
|
||||
return insn.getResult();
|
||||
@@ -621,7 +709,7 @@ public class BlockProcessor extends AbstractVisitor {
|
||||
InsnNode insn = new InsnNode(returnInsn.getType(), returnInsn.getArgsCount());
|
||||
if (returnInsn.getArgsCount() == 1) {
|
||||
RegisterArg arg = (RegisterArg) returnInsn.getArg(0);
|
||||
insn.addArg(InsnArg.reg(arg.getRegNum(), arg.getType()));
|
||||
insn.addArg(InsnArg.reg(arg.getRegNum(), arg.getInitType()));
|
||||
}
|
||||
insn.copyAttributesFrom(returnInsn);
|
||||
insn.setOffset(returnInsn.getOffset());
|
||||
|
||||
@@ -29,7 +29,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
public class BlockSplitter extends AbstractVisitor {
|
||||
|
||||
// leave these instructions alone in block node
|
||||
private static final Set<InsnType> SEPARATE_INSNS = EnumSet.of(
|
||||
public static final Set<InsnType> SEPARATE_INSNS = EnumSet.of(
|
||||
InsnType.RETURN,
|
||||
InsnType.IF,
|
||||
InsnType.SWITCH,
|
||||
@@ -52,6 +52,9 @@ public class BlockSplitter extends AbstractVisitor {
|
||||
removeEmptyDetachedBlocks(mth);
|
||||
removeUnreachableBlocks(mth);
|
||||
initBlocksInTargetNodes(mth);
|
||||
|
||||
removeJumpAttributes(mth.getInstructions());
|
||||
mth.unloadInsnArr();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -329,6 +332,14 @@ public class BlockSplitter extends AbstractVisitor {
|
||||
);
|
||||
}
|
||||
|
||||
private void removeJumpAttributes(InsnNode[] insnArr) {
|
||||
for (InsnNode insn : insnArr) {
|
||||
if (insn != null && insn.contains(AType.JUMP)) {
|
||||
insn.remove(AType.JUMP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeUnreachableBlocks(MethodNode mth) {
|
||||
Set<BlockNode> toRemove = new LinkedHashSet<>();
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
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 + ')';
|
||||
}
|
||||
}
|
||||
-122
@@ -1,122 +0,0 @@
|
||||
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
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
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" : "")
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
package jadx.core.dex.visitors.debuginfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||
import jadx.core.dex.instructions.PhiInsn;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.Named;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
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.visitors.AbstractVisitor;
|
||||
import jadx.core.dex.visitors.JadxVisitor;
|
||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
|
||||
import jadx.core.dex.visitors.typeinference.TypeUpdateResult;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "Debug Info Apply",
|
||||
desc = "Apply debug info to registers (type and names)",
|
||||
runAfter = {
|
||||
SSATransform.class,
|
||||
TypeInferenceVisitor.class
|
||||
}
|
||||
)
|
||||
public class DebugInfoApplyVisitor extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DebugInfoApplyVisitor.class);
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
try {
|
||||
if (mth.contains(AType.LOCAL_VARS_DEBUG_INFO)) {
|
||||
applyDebugInfo(mth);
|
||||
mth.remove(AType.LOCAL_VARS_DEBUG_INFO);
|
||||
}
|
||||
checkTypes(mth);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error to apply debug info: {}", ErrorsCounter.formatMsg(mth, e.getMessage()), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkTypes(MethodNode mth) {
|
||||
if (mth.isNoCode() || mth.getSVars().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
mth.getSVars().forEach(var -> {
|
||||
ArgType type = var.getTypeInfo().getType();
|
||||
if (!type.isTypeKnown()) {
|
||||
mth.addComment("JADX WARNING: type inference failed for: " + var.getDetailedVarInfo(mth));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void applyDebugInfo(MethodNode mth) {
|
||||
mth.getSVars().forEach(ssaVar -> collectVarDebugInfo(mth, ssaVar));
|
||||
|
||||
fixLinesForReturn(mth);
|
||||
fixNamesForPhiInsns(mth);
|
||||
}
|
||||
|
||||
private static void collectVarDebugInfo(MethodNode mth, SSAVar ssaVar) {
|
||||
Set<RegDebugInfoAttr> debugInfoSet = new HashSet<>(ssaVar.getUseCount() + 1);
|
||||
addRegDbdInfo(debugInfoSet, ssaVar.getAssign());
|
||||
ssaVar.getUseList().forEach(registerArg -> addRegDbdInfo(debugInfoSet, registerArg));
|
||||
|
||||
int dbgCount = debugInfoSet.size();
|
||||
if (dbgCount == 0) {
|
||||
searchDebugInfoByOffset(mth, ssaVar);
|
||||
return;
|
||||
}
|
||||
if (dbgCount == 1) {
|
||||
RegDebugInfoAttr debugInfo = debugInfoSet.iterator().next();
|
||||
applyDebugInfo(mth, ssaVar, debugInfo.getRegType(), debugInfo.getName());
|
||||
} else {
|
||||
LOG.warn("Multiple debug info for {}: {}", ssaVar, debugInfoSet);
|
||||
for (RegDebugInfoAttr debugInfo : debugInfoSet) {
|
||||
applyDebugInfo(mth, ssaVar, debugInfo.getRegType(), debugInfo.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void searchDebugInfoByOffset(MethodNode mth, SSAVar ssaVar) {
|
||||
LocalVarsDebugInfoAttr debugInfoAttr = mth.get(AType.LOCAL_VARS_DEBUG_INFO);
|
||||
if (debugInfoAttr == null) {
|
||||
return;
|
||||
}
|
||||
Optional<Integer> max = ssaVar.getUseList().stream()
|
||||
.map(DebugInfoApplyVisitor::getInsnOffsetByArg)
|
||||
.max(Integer::compareTo);
|
||||
if (!max.isPresent()) {
|
||||
return;
|
||||
}
|
||||
int startOffset = getInsnOffsetByArg(ssaVar.getAssign());
|
||||
int endOffset = max.get();
|
||||
int regNum = ssaVar.getRegNum();
|
||||
for (LocalVar localVar : debugInfoAttr.getLocalVars()) {
|
||||
if (localVar.getRegNum() == regNum) {
|
||||
int startAddr = localVar.getStartAddr();
|
||||
int endAddr = localVar.getEndAddr();
|
||||
if (isInside(startOffset, startAddr, endAddr) || isInside(endOffset, startAddr, endAddr)) {
|
||||
if (Consts.DEBUG) {
|
||||
LOG.debug("Apply debug info by offset for: {} to {}", ssaVar, localVar);
|
||||
}
|
||||
applyDebugInfo(mth, ssaVar, localVar.getType(), localVar.getName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isInside(int var, int start, int end) {
|
||||
return start <= var && var <= end;
|
||||
}
|
||||
|
||||
private static int getInsnOffsetByArg(InsnArg arg) {
|
||||
if (arg != null) {
|
||||
InsnNode insn = arg.getParentInsn();
|
||||
if (insn != null) {
|
||||
return insn.getOffset();
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static void applyDebugInfo(MethodNode mth, SSAVar ssaVar, ArgType type, String varName) {
|
||||
TypeUpdateResult result = mth.root().getTypeUpdate().applyWithWiderAllow(ssaVar, type);
|
||||
if (result == TypeUpdateResult.REJECT) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Reject debug info of type: {} and name: '{}' for {}, mth: {}", type, varName, ssaVar, mth);
|
||||
}
|
||||
} else {
|
||||
if (NameMapper.isValidIdentifier(varName)) {
|
||||
ssaVar.setName(varName);
|
||||
}
|
||||
detachDebugInfo(ssaVar.getAssign());
|
||||
ssaVar.getUseList().forEach(DebugInfoApplyVisitor::detachDebugInfo);
|
||||
}
|
||||
}
|
||||
|
||||
private static void detachDebugInfo(RegisterArg reg) {
|
||||
if (reg != null) {
|
||||
reg.remove(AType.REG_DEBUG_INFO);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addRegDbdInfo(Set<RegDebugInfoAttr> debugInfo, RegisterArg reg) {
|
||||
RegDebugInfoAttr debugInfoAttr = reg.get(AType.REG_DEBUG_INFO);
|
||||
if (debugInfoAttr != null) {
|
||||
debugInfo.add(debugInfoAttr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix debug info for splitter 'return' instructions
|
||||
*/
|
||||
private static void fixLinesForReturn(MethodNode mth) {
|
||||
if (mth.getReturnType().equals(ArgType.VOID)) {
|
||||
return;
|
||||
}
|
||||
InsnNode origReturn = null;
|
||||
List<InsnNode> newReturns = new ArrayList<>(mth.getExitBlocks().size());
|
||||
for (BlockNode exit : mth.getExitBlocks()) {
|
||||
InsnNode ret = BlockUtils.getLastInsn(exit);
|
||||
if (ret != null) {
|
||||
if (ret.contains(AFlag.ORIG_RETURN)) {
|
||||
origReturn = ret;
|
||||
} else {
|
||||
newReturns.add(ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (origReturn != null) {
|
||||
for (InsnNode ret : newReturns) {
|
||||
InsnArg oldArg = origReturn.getArg(0);
|
||||
InsnArg newArg = ret.getArg(0);
|
||||
if (oldArg.isRegister() && newArg.isRegister()) {
|
||||
RegisterArg oldArgReg = (RegisterArg) oldArg;
|
||||
RegisterArg newArgReg = (RegisterArg) newArg;
|
||||
applyDebugInfo(mth, newArgReg.getSVar(), oldArgReg.getType(), oldArgReg.getName());
|
||||
}
|
||||
ret.setSourceLine(origReturn.getSourceLine());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void fixNamesForPhiInsns(MethodNode mth) {
|
||||
mth.getSVars().forEach(ssaVar -> {
|
||||
PhiInsn phiInsn = ssaVar.getUsedInPhi();
|
||||
if (phiInsn != null) {
|
||||
Set<String> names = new HashSet<>(1 + phiInsn.getArgsCount());
|
||||
addArgName(phiInsn.getResult(), names);
|
||||
phiInsn.getArguments().forEach(arg -> addArgName(arg, names));
|
||||
if (names.size() == 1) {
|
||||
setNameForInsn(phiInsn, names.iterator().next());
|
||||
} else if (names.size() > 1) {
|
||||
LOG.warn("Different names in phi insn: {}, use first", names);
|
||||
setNameForInsn(phiInsn, names.iterator().next());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void addArgName(InsnArg arg, Set<String> names) {
|
||||
if (arg instanceof Named) {
|
||||
String name = ((Named) arg).getName();
|
||||
if (name != null) {
|
||||
names.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void setNameForInsn(PhiInsn phiInsn, String name) {
|
||||
phiInsn.getResult().setName(name);
|
||||
phiInsn.getArguments().forEach(arg -> {
|
||||
if (arg instanceof Named) {
|
||||
((Named) arg).setName(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package jadx.core.dex.visitors.debuginfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.Consts;
|
||||
import jadx.core.dex.attributes.nodes.LocalVarsDebugInfoAttr;
|
||||
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
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.BlockSplitter;
|
||||
import jadx.core.dex.visitors.ssa.SSATransform;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "Debug Info Parser",
|
||||
desc = "Parse debug information (variable names and types, instruction lines)",
|
||||
runBefore = {
|
||||
BlockSplitter.class,
|
||||
SSATransform.class
|
||||
}
|
||||
)
|
||||
public class DebugInfoParseVisitor extends AbstractVisitor {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DebugInfoParseVisitor.class);
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
try {
|
||||
int debugOffset = mth.getDebugInfoOffset();
|
||||
if (debugOffset > 0 && mth.dex().checkOffset(debugOffset)) {
|
||||
processDebugInfo(mth, debugOffset);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error to parse debug info: {}", ErrorsCounter.formatMsg(mth, e.getMessage()), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void processDebugInfo(MethodNode mth, int debugOffset) throws DecodeException {
|
||||
InsnNode[] insnArr = mth.getInstructions();
|
||||
DebugInfoParser debugInfoParser = new DebugInfoParser(mth, debugOffset, insnArr);
|
||||
List<LocalVar> localVars = debugInfoParser.process();
|
||||
attachDebugInfo(mth, localVars, insnArr);
|
||||
setMethodSourceLine(mth, insnArr);
|
||||
}
|
||||
|
||||
private void attachDebugInfo(MethodNode mth, List<LocalVar> localVars, InsnNode[] insnArr) {
|
||||
if (localVars.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (Consts.DEBUG) {
|
||||
LOG.debug("Parsed debug info for {}: ", mth);
|
||||
localVars.forEach(v -> LOG.debug(" {}", v));
|
||||
}
|
||||
localVars.forEach(var -> {
|
||||
int start = var.getStartAddr();
|
||||
int end = var.getEndAddr();
|
||||
RegDebugInfoAttr debugInfoAttr = new RegDebugInfoAttr(var);
|
||||
if (start < 0) {
|
||||
// attach to method arguments
|
||||
for (RegisterArg arg : mth.getArguments(true)) {
|
||||
attachDebugInfo(arg, var, debugInfoAttr);
|
||||
}
|
||||
start = 0;
|
||||
}
|
||||
for (int i = start; i <= end; i++) {
|
||||
InsnNode insn = insnArr[i];
|
||||
if (insn != null) {
|
||||
attachDebugInfo(insn.getResult(), var, debugInfoAttr);
|
||||
for (InsnArg arg : insn.getArguments()) {
|
||||
attachDebugInfo(arg, var, debugInfoAttr);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mth.addAttr(new LocalVarsDebugInfoAttr(localVars));
|
||||
}
|
||||
|
||||
private void attachDebugInfo(InsnArg arg, LocalVar var, RegDebugInfoAttr debugInfoAttr) {
|
||||
if (arg instanceof RegisterArg) {
|
||||
RegisterArg reg = (RegisterArg) arg;
|
||||
if (var.getRegNum() == reg.getRegNum()) {
|
||||
reg.addAttr(debugInfoAttr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set method source line from first instruction
|
||||
*/
|
||||
private void setMethodSourceLine(MethodNode mth, InsnNode[] insnArr) {
|
||||
for (InsnNode insn : insnArr) {
|
||||
if (insn != null) {
|
||||
int line = insn.getSourceLine();
|
||||
if (line != 0) {
|
||||
mth.setSourceLine(line - 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
-122
@@ -1,21 +1,21 @@
|
||||
package jadx.core.dex.nodes.parser;
|
||||
package jadx.core.dex.visitors.debuginfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.android.dex.Dex.Section;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.nodes.SourceFileAttr;
|
||||
import jadx.core.dex.instructions.args.InsnArg;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.exceptions.DecodeException;
|
||||
|
||||
public class DebugInfoParser {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DebugInfoParser.class);
|
||||
private static final int DBG_END_SEQUENCE = 0x00;
|
||||
private static final int DBG_ADVANCE_PC = 0x01;
|
||||
private static final int DBG_ADVANCE_LINE = 0x02;
|
||||
@@ -39,9 +39,10 @@ public class DebugInfoParser {
|
||||
private final DexNode dex;
|
||||
|
||||
private final LocalVar[] locals;
|
||||
private final InsnArg[] activeRegisters;
|
||||
private final InsnNode[] insnByOffset;
|
||||
|
||||
private List<LocalVar> resultList;
|
||||
|
||||
public DebugInfoParser(MethodNode mth, int debugOffset, InsnNode[] insnByOffset) {
|
||||
this.mth = mth;
|
||||
this.dex = mth.dex();
|
||||
@@ -49,38 +50,37 @@ public class DebugInfoParser {
|
||||
|
||||
int regsCount = mth.getRegsCount();
|
||||
this.locals = new LocalVar[regsCount];
|
||||
this.activeRegisters = new InsnArg[regsCount];
|
||||
this.insnByOffset = insnByOffset;
|
||||
}
|
||||
|
||||
public void process() throws DecodeException {
|
||||
public List<LocalVar> process() throws DecodeException {
|
||||
boolean varsInfoFound = false;
|
||||
resultList = new ArrayList<>();
|
||||
|
||||
int addr = 0;
|
||||
int line = section.readUleb128();
|
||||
|
||||
int paramsCount = section.readUleb128();
|
||||
List<RegisterArg> mthArgs = mth.getArguments(false);
|
||||
|
||||
for (int i = 0; i < paramsCount; i++) {
|
||||
int id = section.readUleb128() - 1;
|
||||
if (id != DexNode.NO_INDEX) {
|
||||
String name = dex.getString(id);
|
||||
if (i < mthArgs.size()) {
|
||||
mthArgs.get(i).setName(name);
|
||||
int nameId = section.readUleb128() - 1;
|
||||
if (nameId != DexNode.NO_INDEX) {
|
||||
String name = dex.getString(nameId);
|
||||
if (i < mthArgs.size() && name != null) {
|
||||
RegisterArg arg = mthArgs.get(i);
|
||||
int regNum = arg.getRegNum();
|
||||
LocalVar lVar = new LocalVar(regNum, name, arg.getInitType());
|
||||
startVar(lVar, -1);
|
||||
varsInfoFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (RegisterArg arg : mthArgs) {
|
||||
int rn = arg.getRegNum();
|
||||
locals[rn] = new LocalVar(arg);
|
||||
activeRegisters[rn] = arg;
|
||||
}
|
||||
|
||||
// process '0' instruction
|
||||
addrChange(-1, 1, line);
|
||||
setLine(addr, line);
|
||||
|
||||
boolean varsInfoFound = false;
|
||||
|
||||
int c = section.readByte() & 0xFF;
|
||||
while (c != DBG_END_SEQUENCE) {
|
||||
switch (c) {
|
||||
@@ -100,7 +100,7 @@ public class DebugInfoParser {
|
||||
int nameId = section.readUleb128() - 1;
|
||||
int type = section.readUleb128() - 1;
|
||||
LocalVar var = new LocalVar(dex, regNum, nameId, type, DexNode.NO_INDEX);
|
||||
startVar(var, addr, line);
|
||||
startVar(var, addr);
|
||||
varsInfoFound = true;
|
||||
break;
|
||||
}
|
||||
@@ -110,19 +110,13 @@ public class DebugInfoParser {
|
||||
int type = section.readUleb128() - 1;
|
||||
int sign = section.readUleb128() - 1;
|
||||
LocalVar var = new LocalVar(dex, regNum, nameId, type, sign);
|
||||
startVar(var, addr, line);
|
||||
startVar(var, addr);
|
||||
varsInfoFound = true;
|
||||
break;
|
||||
}
|
||||
case DBG_RESTART_LOCAL: {
|
||||
int regNum = section.readUleb128();
|
||||
LocalVar var = locals[regNum];
|
||||
if (var != null) {
|
||||
if (var.end(addr, line)) {
|
||||
setVar(var);
|
||||
}
|
||||
var.start(addr, line);
|
||||
}
|
||||
restartVar(regNum, addr);
|
||||
varsInfoFound = true;
|
||||
break;
|
||||
}
|
||||
@@ -130,8 +124,7 @@ public class DebugInfoParser {
|
||||
int regNum = section.readUleb128();
|
||||
LocalVar var = locals[regNum];
|
||||
if (var != null) {
|
||||
var.end(addr, line);
|
||||
setVar(var);
|
||||
endVar(var, addr);
|
||||
}
|
||||
varsInfoFound = true;
|
||||
break;
|
||||
@@ -153,10 +146,10 @@ public class DebugInfoParser {
|
||||
|
||||
default: {
|
||||
if (c >= DBG_FIRST_SPECIAL) {
|
||||
int adjustedOpcode = c - DBG_FIRST_SPECIAL;
|
||||
int addrInc = adjustedOpcode / DBG_LINE_RANGE;
|
||||
int adjustedOpCode = c - DBG_FIRST_SPECIAL;
|
||||
int addrInc = adjustedOpCode / DBG_LINE_RANGE;
|
||||
addr = addrChange(addr, addrInc, line);
|
||||
line += DBG_LINE_BASE + adjustedOpcode % DBG_LINE_RANGE;
|
||||
line += DBG_LINE_BASE + adjustedOpCode % DBG_LINE_RANGE;
|
||||
setLine(addr, line);
|
||||
} else {
|
||||
throw new DecodeException("Unknown debug insn code: " + c);
|
||||
@@ -170,33 +163,19 @@ public class DebugInfoParser {
|
||||
if (varsInfoFound) {
|
||||
for (LocalVar var : locals) {
|
||||
if (var != null && !var.isEnd()) {
|
||||
var.end(mth.getCodeSize() - 1, line);
|
||||
setVar(var);
|
||||
endVar(var, mth.getCodeSize() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
setSourceLines(addr, insnByOffset.length, line);
|
||||
|
||||
return resultList;
|
||||
}
|
||||
|
||||
private int addrChange(int addr, int addrInc, int line) {
|
||||
int newAddr = addr + addrInc;
|
||||
int maxAddr = insnByOffset.length - 1;
|
||||
newAddr = Math.min(newAddr, maxAddr);
|
||||
for (int i = addr + 1; i <= newAddr; i++) {
|
||||
InsnNode insn = insnByOffset[i];
|
||||
if (insn == null) {
|
||||
continue;
|
||||
}
|
||||
for (InsnArg arg : insn.getArguments()) {
|
||||
if (arg.isRegister()) {
|
||||
activeRegisters[((RegisterArg) arg).getRegNum()] = arg;
|
||||
}
|
||||
}
|
||||
RegisterArg res = insn.getResult();
|
||||
if (res != null) {
|
||||
activeRegisters[res.getRegNum()] = res;
|
||||
}
|
||||
}
|
||||
setSourceLines(addr, newAddr, line);
|
||||
return newAddr;
|
||||
}
|
||||
@@ -214,81 +193,30 @@ public class DebugInfoParser {
|
||||
}
|
||||
}
|
||||
|
||||
private void startVar(LocalVar var, int addr, int line) {
|
||||
int regNum = var.getRegNum();
|
||||
private void restartVar(int regNum, int addr) {
|
||||
LocalVar prev = locals[regNum];
|
||||
if (prev != null && !prev.isEnd()) {
|
||||
prev.end(addr, line);
|
||||
setVar(prev);
|
||||
}
|
||||
InsnArg activeReg = activeRegisters[var.getRegNum()];
|
||||
if (activeReg instanceof RegisterArg) {
|
||||
SSAVar ssaVar = ((RegisterArg) activeReg).getSVar();
|
||||
if (ssaVar != null && ssaVar.getStartAddr() != -1) {
|
||||
InsnNode parentInsn = ssaVar.getAssign().getParentInsn();
|
||||
if (parentInsn != null && parentInsn.getOffset() >= 0) {
|
||||
addr = parentInsn.getOffset();
|
||||
}
|
||||
}
|
||||
}
|
||||
var.start(addr, line);
|
||||
locals[regNum] = var;
|
||||
}
|
||||
|
||||
private void setVar(LocalVar var) {
|
||||
int start = var.getStartAddr();
|
||||
int end = var.getEndAddr();
|
||||
|
||||
for (int i = start; i <= end; i++) {
|
||||
InsnNode insn = insnByOffset[i];
|
||||
if (insn != null) {
|
||||
fillLocals(insn, var);
|
||||
}
|
||||
}
|
||||
merge(activeRegisters[var.getRegNum()], var);
|
||||
}
|
||||
|
||||
private static void fillLocals(InsnNode insn, LocalVar var) {
|
||||
merge(insn.getResult(), var);
|
||||
for (InsnArg arg : insn.getArguments()) {
|
||||
merge(arg, var);
|
||||
}
|
||||
}
|
||||
|
||||
private static void merge(InsnArg arg, LocalVar var) {
|
||||
if (arg == null || !arg.isRegister()) {
|
||||
return;
|
||||
}
|
||||
RegisterArg reg = (RegisterArg) arg;
|
||||
if (var.getRegNum() != reg.getRegNum()) {
|
||||
return;
|
||||
}
|
||||
boolean mergeRequired = false;
|
||||
|
||||
SSAVar ssaVar = reg.getSVar();
|
||||
if (ssaVar != null) {
|
||||
int ssaEnd = ssaVar.getEndAddr();
|
||||
int ssaStart = ssaVar.getStartAddr();
|
||||
int localStart = var.getStartAddr();
|
||||
int localEnd = var.getEndAddr();
|
||||
|
||||
boolean isIntersected = !(localEnd < ssaStart || ssaEnd < localStart);
|
||||
if (isIntersected && ssaEnd <= localEnd) {
|
||||
mergeRequired = true;
|
||||
}
|
||||
if (prev != null) {
|
||||
endVar(prev, addr);
|
||||
LocalVar newVar = new LocalVar(regNum, prev.getName(), prev.getType());
|
||||
startVar(newVar, addr);
|
||||
} else {
|
||||
mergeRequired = true;
|
||||
}
|
||||
|
||||
if (mergeRequired) {
|
||||
applyDebugInfo(reg, var);
|
||||
mth.addComment("Debug info: failed to restart local var, previous not found, register: " + regNum);
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyDebugInfo(RegisterArg reg, LocalVar var) {
|
||||
String varName = var.getName();
|
||||
if (NameMapper.isValidIdentifier(varName)) {
|
||||
reg.mergeDebugInfo(var.getType(), varName);
|
||||
private void startVar(LocalVar newVar, int addr) {
|
||||
int regNum = newVar.getRegNum();
|
||||
LocalVar prev = locals[regNum];
|
||||
if (prev != null) {
|
||||
endVar(prev, addr);
|
||||
}
|
||||
newVar.start(addr);
|
||||
locals[regNum] = newVar;
|
||||
}
|
||||
|
||||
private void endVar(LocalVar var, int addr) {
|
||||
if (var.end(addr)) {
|
||||
resultList.add(var);
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
-29
@@ -1,54 +1,48 @@
|
||||
package jadx.core.dex.nodes.parser;
|
||||
package jadx.core.dex.visitors.debuginfo;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.utils.InsnUtils;
|
||||
|
||||
final class LocalVar {
|
||||
public final class LocalVar {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LocalVar.class);
|
||||
|
||||
private final int regNum;
|
||||
private String name;
|
||||
private ArgType type;
|
||||
private final String name;
|
||||
private final ArgType type;
|
||||
|
||||
private boolean isEnd;
|
||||
private int startAddr;
|
||||
private int endAddr;
|
||||
|
||||
public LocalVar(DexNode dex, int rn, int nameId, int typeId, int signId) {
|
||||
this.regNum = rn;
|
||||
String name = nameId == DexNode.NO_INDEX ? null : dex.getString(nameId);
|
||||
ArgType type = typeId == DexNode.NO_INDEX ? null : dex.getType(typeId);
|
||||
String sign = signId == DexNode.NO_INDEX ? null : dex.getString(signId);
|
||||
|
||||
init(name, type, sign);
|
||||
this(rn, dex.getString(nameId), dex.getType(typeId), dex.getString(signId));
|
||||
}
|
||||
|
||||
public LocalVar(RegisterArg arg) {
|
||||
this.regNum = arg.getRegNum();
|
||||
init(arg.getName(), arg.getType(), null);
|
||||
public LocalVar(int regNum, String name, ArgType type) {
|
||||
this(regNum, name, type, null);
|
||||
}
|
||||
|
||||
private void init(String name, ArgType type, String sign) {
|
||||
public LocalVar(int regNum, String name, ArgType type, String sign) {
|
||||
this.regNum = regNum;
|
||||
this.name = name;
|
||||
if (sign != null) {
|
||||
try {
|
||||
ArgType gType = ArgType.generic(sign);
|
||||
if (checkSignature(type, sign, gType)) {
|
||||
if (checkSignature(type, gType)) {
|
||||
type = gType;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Can't parse signature for local variable: {}", sign, e);
|
||||
}
|
||||
}
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
private boolean checkSignature(ArgType type, String sign, ArgType gType) {
|
||||
private boolean checkSignature(ArgType type, ArgType gType) {
|
||||
boolean apply;
|
||||
ArgType el = gType.getArrayRootElement();
|
||||
if (el.isGeneric()) {
|
||||
@@ -62,7 +56,7 @@ final class LocalVar {
|
||||
return apply;
|
||||
}
|
||||
|
||||
public void start(int addr, int line) {
|
||||
public void start(int addr) {
|
||||
this.isEnd = false;
|
||||
this.startAddr = addr;
|
||||
}
|
||||
@@ -71,16 +65,15 @@ final class LocalVar {
|
||||
* Sets end address of local variable
|
||||
*
|
||||
* @param addr address
|
||||
* @param line source line
|
||||
* @return <b>true</b> if local variable was active, else <b>false</b>
|
||||
*/
|
||||
public boolean end(int addr, int line) {
|
||||
if (!isEnd) {
|
||||
this.isEnd = true;
|
||||
this.endAddr = addr;
|
||||
return true;
|
||||
public boolean end(int addr) {
|
||||
if (isEnd) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
this.isEnd = true;
|
||||
this.endAddr = addr;
|
||||
return true;
|
||||
}
|
||||
|
||||
public int getRegNum() {
|
||||
@@ -119,8 +112,8 @@ final class LocalVar {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ' ' + (isEnd
|
||||
? "end: " + InsnUtils.formatOffset(startAddr) + '-' + InsnUtils.formatOffset(endAddr)
|
||||
: "active: " + InsnUtils.formatOffset(startAddr));
|
||||
return InsnUtils.formatOffset(startAddr)
|
||||
+ '-' + (isEnd ? InsnUtils.formatOffset(endAddr) : " ")
|
||||
+ ": r" + regNum + " '" + name + "' " + type;
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public class CheckRegions extends AbstractVisitor {
|
||||
}
|
||||
if (LOG.isDebugEnabled()
|
||||
&& !block.contains(AFlag.RETURN)
|
||||
&& !block.contains(AFlag.SKIP)
|
||||
&& !block.contains(AFlag.REMOVE)
|
||||
&& !block.contains(AFlag.SYNTHETIC)
|
||||
&& !block.getInstructions().isEmpty()) {
|
||||
LOG.debug("Duplicated block: {} - {}", mth, block);
|
||||
@@ -58,18 +58,20 @@ public class CheckRegions extends AbstractVisitor {
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
if (!blocksInRegions.contains(block)
|
||||
&& !block.getInstructions().isEmpty()
|
||||
&& !block.contains(AFlag.SKIP)) {
|
||||
&& !block.contains(AFlag.ADDED_TO_REGION)
|
||||
&& !block.contains(AFlag.DONT_GENERATE)
|
||||
&& !block.contains(AFlag.REMOVE)) {
|
||||
String blockCode = getBlockInsnStr(mth, block);
|
||||
mth.addWarn("Missing block: " + block + ", code skipped:" + CodeWriter.NL + blockCode);
|
||||
mth.addWarn("Code restructure failed: missing block: " + block + ", code lost:" + blockCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check loop conditions
|
||||
DepthRegionTraversal.traverse(mth, new AbstractRegionVisitor() {
|
||||
@Override
|
||||
public boolean enterRegion(MethodNode mth, IRegion region) {
|
||||
if (region instanceof LoopRegion) {
|
||||
// check loop conditions
|
||||
BlockNode loopHeader = ((LoopRegion) region).getHeader();
|
||||
if (loopHeader != null && loopHeader.getInstructions().size() != 1) {
|
||||
mth.addWarn("Incorrect condition in loop: " + loopHeader);
|
||||
@@ -80,8 +82,9 @@ public class CheckRegions extends AbstractVisitor {
|
||||
});
|
||||
}
|
||||
|
||||
private static String getBlockInsnStr(MethodNode mth, BlockNode block) {
|
||||
private static String getBlockInsnStr(MethodNode mth, IBlock block) {
|
||||
CodeWriter code = new CodeWriter();
|
||||
code.newLine();
|
||||
code.setIndent(3);
|
||||
MethodGen mg = MethodGen.getFallbackMethodGen(mth);
|
||||
InsnGen ig = new InsnGen(mg, true);
|
||||
|
||||
@@ -1,34 +1,57 @@
|
||||
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.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
|
||||
public class CleanRegions {
|
||||
public class CleanRegions extends AbstractVisitor {
|
||||
private static final IRegionVisitor REMOVE_REGION_VISITOR = new RemoveRegionVisitor();
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) {
|
||||
process(mth);
|
||||
}
|
||||
|
||||
public static void process(MethodNode mth) {
|
||||
if (mth.isNoCode() || mth.getBasicBlocks().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
IRegionVisitor removeEmptyBlocks = new AbstractRegionVisitor() {
|
||||
@Override
|
||||
public boolean enterRegion(MethodNode mth, IRegion region) {
|
||||
if (region instanceof Region) {
|
||||
region.getSubBlocks().removeIf(container -> {
|
||||
if (container instanceof BlockNode) {
|
||||
BlockNode block = (BlockNode) container;
|
||||
return block.getInstructions().isEmpty();
|
||||
}
|
||||
DepthRegionTraversal.traverse(mth, REMOVE_REGION_VISITOR);
|
||||
}
|
||||
|
||||
private static class RemoveRegionVisitor extends AbstractRegionVisitor {
|
||||
@Override
|
||||
public boolean enterRegion(MethodNode mth, IRegion region) {
|
||||
if (region instanceof Region) {
|
||||
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 true;
|
||||
}
|
||||
};
|
||||
DepthRegionTraversal.traverse(mth, removeEmptyBlocks);
|
||||
}
|
||||
|
||||
private CleanRegions() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,16 +60,19 @@ public class DepthRegionTraversal {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean traverseIterativeStepInternal(MethodNode mth, IRegionIterativeVisitor visitor,
|
||||
IContainer container) {
|
||||
private static boolean traverseIterativeStepInternal(MethodNode mth, IRegionIterativeVisitor visitor, IContainer container) {
|
||||
if (container instanceof IRegion) {
|
||||
IRegion region = (IRegion) container;
|
||||
if (visitor.visitRegion(mth, region)) {
|
||||
return true;
|
||||
}
|
||||
for (IContainer subCont : region.getSubBlocks()) {
|
||||
if (traverseIterativeStepInternal(mth, visitor, subCont)) {
|
||||
return true;
|
||||
try {
|
||||
if (traverseIterativeStepInternal(mth, visitor, subCont)) {
|
||||
return true;
|
||||
}
|
||||
} catch (StackOverflowError overflow) {
|
||||
throw new JadxOverflowException("Region traversal failed: Recursive call in traverseIterativeStepInternal method");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,10 @@ public class IfMakerHelper {
|
||||
}
|
||||
|
||||
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);
|
||||
IfInfo info = new IfInfo(condition, ifNode.getThenBlock(), ifNode.getElseBlock());
|
||||
info.setIfBlock(ifBlock);
|
||||
@@ -302,13 +305,13 @@ public class IfMakerHelper {
|
||||
if (info.getMergedBlocks().size() > 1) {
|
||||
for (BlockNode block : info.getMergedBlocks()) {
|
||||
if (block != info.getIfBlock()) {
|
||||
block.add(AFlag.SKIP);
|
||||
block.add(AFlag.ADDED_TO_REGION);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!info.getSkipBlocks().isEmpty()) {
|
||||
for (BlockNode block : info.getSkipBlocks()) {
|
||||
block.add(AFlag.SKIP);
|
||||
block.add(AFlag.ADDED_TO_REGION);
|
||||
}
|
||||
info.getSkipBlocks().clear();
|
||||
}
|
||||
@@ -336,11 +339,11 @@ public class IfMakerHelper {
|
||||
}
|
||||
|
||||
private static BlockNode getNextIfNode(BlockNode block) {
|
||||
if (block == null || block.contains(AType.LOOP) || block.contains(AFlag.SKIP)) {
|
||||
if (block == null || block.contains(AType.LOOP) || block.contains(AFlag.ADDED_TO_REGION)) {
|
||||
return null;
|
||||
}
|
||||
List<InsnNode> insns = block.getInstructions();
|
||||
if (insns.size() == 1 && insns.get(0).getType() == InsnType.IF) {
|
||||
InsnNode lastInsn = BlockUtils.getLastInsn(block);
|
||||
if (lastInsn != null && lastInsn.getType() == InsnType.IF) {
|
||||
return block;
|
||||
}
|
||||
// skip this block and search in successors chain
|
||||
@@ -353,6 +356,7 @@ public class IfMakerHelper {
|
||||
if (next.getPredecessors().size() != 1) {
|
||||
return null;
|
||||
}
|
||||
List<InsnNode> insns = block.getInstructions();
|
||||
boolean pass = true;
|
||||
if (!insns.isEmpty()) {
|
||||
// check that all instructions can be inlined
|
||||
|
||||
@@ -33,11 +33,18 @@ import jadx.core.dex.regions.loops.ForLoop;
|
||||
import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.regions.loops.LoopType;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.dex.visitors.CodeShrinker;
|
||||
import jadx.core.dex.visitors.JadxVisitor;
|
||||
import jadx.core.dex.visitors.regions.variables.ProcessVariables;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.InstructionRemover;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||
|
||||
@JadxVisitor(
|
||||
name = "LoopRegionVisitor",
|
||||
desc = "Convert 'while' loops to 'for' loops (indexed or for-each)",
|
||||
runBefore = {ProcessVariables.class}
|
||||
)
|
||||
public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LoopRegionVisitor.class);
|
||||
|
||||
@@ -65,9 +72,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
||||
if (checkForIndexedLoop(mth, loopRegion, condition)) {
|
||||
return;
|
||||
}
|
||||
if (checkIterableForEach(mth, loopRegion, condition)) {
|
||||
return;
|
||||
}
|
||||
checkIterableForEach(mth, loopRegion, condition);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,7 +92,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
||||
PhiInsn phiInsn = incrArg.getSVar().getUsedInPhi();
|
||||
if (phiInsn == null
|
||||
|| phiInsn.getArgsCount() != 2
|
||||
|| !phiInsn.getArg(1).equals(incrArg)
|
||||
|| !phiInsn.containsArg(incrArg)
|
||||
|| incrArg.getSVar().getUseCount() != 1) {
|
||||
return false;
|
||||
}
|
||||
@@ -108,20 +113,24 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
||||
List<RegisterArg> args = new LinkedList<>();
|
||||
incrInsn.getRegisterArgs(args);
|
||||
for (RegisterArg iArg : args) {
|
||||
if (assignOnlyInLoop(mth, loopRegion, iArg)) {
|
||||
return false;
|
||||
try {
|
||||
if (assignOnlyInLoop(mth, loopRegion, iArg)) {
|
||||
return false;
|
||||
}
|
||||
} catch (StackOverflowError error) {
|
||||
throw new JadxOverflowException("LoopRegionVisitor.assignOnlyInLoop endless recursion");
|
||||
}
|
||||
}
|
||||
|
||||
// all checks passed
|
||||
initInsn.add(AFlag.SKIP);
|
||||
incrInsn.add(AFlag.SKIP);
|
||||
initInsn.add(AFlag.DONT_GENERATE);
|
||||
incrInsn.add(AFlag.DONT_GENERATE);
|
||||
LoopType arrForEach = checkArrayForEach(mth, loopRegion, initInsn, incrInsn, condition);
|
||||
if (arrForEach != null) {
|
||||
loopRegion.setType(arrForEach);
|
||||
return true;
|
||||
} else {
|
||||
loopRegion.setType(new ForLoop(initInsn, incrInsn));
|
||||
}
|
||||
loopRegion.setType(new ForLoop(initInsn, incrInsn));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -191,12 +200,18 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
||||
}
|
||||
|
||||
// array for each loop confirmed
|
||||
len.add(AFlag.SKIP);
|
||||
arrGetInsn.add(AFlag.SKIP);
|
||||
InstructionRemover.unbindInsn(mth, len);
|
||||
incrInsn.getResult().add(AFlag.DONT_GENERATE);
|
||||
condArg.add(AFlag.DONT_GENERATE);
|
||||
bCondArg.add(AFlag.DONT_GENERATE);
|
||||
arrGetInsn.add(AFlag.DONT_GENERATE);
|
||||
|
||||
// inline array variable
|
||||
CodeShrinker.shrinkMethod(mth);
|
||||
if (arrayArg.isRegister()) {
|
||||
((RegisterArg) arrayArg).getSVar().removeUse((RegisterArg) arrGetInsn.getArg(0));
|
||||
}
|
||||
CodeShrinkVisitor.shrinkMethod(mth);
|
||||
len.add(AFlag.DONT_GENERATE);
|
||||
|
||||
if (arrGetInsn.contains(AFlag.WRAPPED)) {
|
||||
InsnArg wrapArg = BlockUtils.searchWrappedInsnParent(mth, arrGetInsn);
|
||||
if (wrapArg != null && wrapArg.getParentInsn() != null) {
|
||||
@@ -218,18 +233,15 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
||||
if (sVar == null || sVar.isUsedInPhi()) {
|
||||
return false;
|
||||
}
|
||||
List<RegisterArg> useList = sVar.getUseList();
|
||||
List<RegisterArg> itUseList = sVar.getUseList();
|
||||
InsnNode assignInsn = iteratorArg.getAssignInsn();
|
||||
if (useList.size() != 2
|
||||
|| assignInsn == null
|
||||
|| !checkInvoke(assignInsn, null, "iterator()Ljava/util/Iterator;", 0)) {
|
||||
if (itUseList.size() != 2 || !checkInvoke(assignInsn, null, "iterator()Ljava/util/Iterator;", 0)) {
|
||||
return false;
|
||||
}
|
||||
InsnArg iterableArg = assignInsn.getArg(0);
|
||||
InsnNode hasNextCall = useList.get(0).getParentInsn();
|
||||
InsnNode nextCall = useList.get(1).getParentInsn();
|
||||
if (hasNextCall == null || nextCall == null
|
||||
|| !checkInvoke(hasNextCall, "java.util.Iterator", "hasNext()Z", 0)
|
||||
InsnNode hasNextCall = itUseList.get(0).getParentInsn();
|
||||
InsnNode nextCall = itUseList.get(1).getParentInsn();
|
||||
if (!checkInvoke(hasNextCall, "java.util.Iterator", "hasNext()Z", 0)
|
||||
|| !checkInvoke(nextCall, "java.util.Iterator", "next()Ljava/lang/Object;", 0)) {
|
||||
return false;
|
||||
}
|
||||
@@ -268,9 +280,12 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
||||
toSkip.add(nextCall);
|
||||
}
|
||||
|
||||
assignInsn.add(AFlag.SKIP);
|
||||
assignInsn.add(AFlag.DONT_GENERATE);
|
||||
for (InsnNode insnNode : toSkip) {
|
||||
insnNode.add(AFlag.SKIP);
|
||||
insnNode.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
for (RegisterArg itArg : itUseList) {
|
||||
itArg.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
loopRegion.setType(new ForEachLoop(iterVar, iterableArg));
|
||||
return true;
|
||||
@@ -292,13 +307,13 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
||||
iterVar.setType(gType);
|
||||
return true;
|
||||
}
|
||||
if (ArgType.isInstanceOf(mth.dex(), gType, varType)) {
|
||||
if (ArgType.isInstanceOf(mth.root(), gType, varType)) {
|
||||
return true;
|
||||
}
|
||||
ArgType wildcardType = gType.getWildcardType();
|
||||
if (wildcardType != null
|
||||
&& gType.getWildcardBounds() == 1
|
||||
&& ArgType.isInstanceOf(mth.dex(), wildcardType, varType)) {
|
||||
&& ArgType.isInstanceOf(mth.root(), wildcardType, varType)) {
|
||||
return true;
|
||||
}
|
||||
LOG.warn("Generic type differs: '{}' and '{}' in {}", gType, varType, mth);
|
||||
@@ -317,6 +332,9 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
||||
* Check if instruction is a interface invoke with corresponding parameters.
|
||||
*/
|
||||
private static boolean checkInvoke(InsnNode insn, String declClsFullName, String mthId, int argsCount) {
|
||||
if (insn == null) {
|
||||
return false;
|
||||
}
|
||||
if (insn.getType() == InsnType.INVOKE) {
|
||||
InvokeNode inv = (InvokeNode) insn;
|
||||
MethodInfo callMth = inv.getCallMth();
|
||||
|
||||
@@ -7,9 +7,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.IBranchRegion;
|
||||
@@ -33,8 +30,6 @@ import jadx.core.utils.RegionUtils;
|
||||
*/
|
||||
public class ProcessTryCatchRegions extends AbstractRegionVisitor {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ProcessTryCatchRegions.class);
|
||||
|
||||
public static void process(MethodNode mth) {
|
||||
if (mth.isNoCode() || mth.isNoExceptionHandlers()) {
|
||||
return;
|
||||
|
||||
@@ -1,331 +0,0 @@
|
||||
package jadx.core.dex.visitors.regions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.VarName;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.regions.loops.ForLoop;
|
||||
import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.regions.loops.LoopType;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
public class ProcessVariables extends AbstractVisitor {
|
||||
|
||||
private static class Variable {
|
||||
private final int regNum;
|
||||
private final ArgType type;
|
||||
|
||||
public Variable(RegisterArg arg) {
|
||||
this.regNum = arg.getRegNum();
|
||||
this.type = arg.getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Variable variable = (Variable) o;
|
||||
return regNum == variable.regNum && type.equals(variable.type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * regNum + type.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "r" + regNum + ':' + type;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Usage {
|
||||
private RegisterArg arg;
|
||||
private VarName varName;
|
||||
private IRegion argRegion;
|
||||
private final Set<IRegion> uses = new LinkedHashSet<>(2);
|
||||
private final Set<IRegion> assigns = new LinkedHashSet<>(2);
|
||||
|
||||
public void setArg(RegisterArg arg) {
|
||||
this.arg = arg;
|
||||
}
|
||||
|
||||
public RegisterArg getArg() {
|
||||
return arg;
|
||||
}
|
||||
|
||||
public VarName getVarName() {
|
||||
return varName;
|
||||
}
|
||||
|
||||
public void setVarName(VarName varName) {
|
||||
this.varName = varName;
|
||||
}
|
||||
|
||||
public void setArgRegion(IRegion argRegion) {
|
||||
this.argRegion = argRegion;
|
||||
}
|
||||
|
||||
public IRegion getArgRegion() {
|
||||
return argRegion;
|
||||
}
|
||||
|
||||
public Set<IRegion> getAssigns() {
|
||||
return assigns;
|
||||
}
|
||||
|
||||
public Set<IRegion> getUseRegions() {
|
||||
return uses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return arg + ", a:" + assigns + ", u:" + uses;
|
||||
}
|
||||
}
|
||||
|
||||
private static class CollectUsageRegionVisitor extends TracedRegionVisitor {
|
||||
private final List<RegisterArg> args;
|
||||
private final Map<Variable, Usage> usageMap;
|
||||
|
||||
public CollectUsageRegionVisitor(Map<Variable, Usage> usageMap) {
|
||||
this.usageMap = usageMap;
|
||||
this.args = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processBlockTraced(MethodNode mth, IBlock container, IRegion curRegion) {
|
||||
regionProcess(curRegion);
|
||||
int len = container.getInstructions().size();
|
||||
for (int i = 0; i < len; i++) {
|
||||
InsnNode insn = container.getInstructions().get(i);
|
||||
if (insn.contains(AFlag.SKIP)) {
|
||||
continue;
|
||||
}
|
||||
args.clear();
|
||||
processInsn(insn, curRegion);
|
||||
}
|
||||
}
|
||||
|
||||
private void regionProcess(IRegion region) {
|
||||
if (region instanceof LoopRegion) {
|
||||
LoopRegion loopRegion = (LoopRegion) region;
|
||||
LoopType loopType = loopRegion.getType();
|
||||
if (loopType instanceof ForLoop) {
|
||||
ForLoop forLoop = (ForLoop) loopType;
|
||||
processInsn(forLoop.getInitInsn(), region);
|
||||
processInsn(forLoop.getIncrInsn(), region);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void processInsn(InsnNode insn, IRegion curRegion) {
|
||||
if (insn == null) {
|
||||
return;
|
||||
}
|
||||
// result
|
||||
RegisterArg result = insn.getResult();
|
||||
if (result != null && result.isRegister()) {
|
||||
Usage u = addToUsageMap(result, usageMap);
|
||||
if (u.getArg() == null) {
|
||||
u.setArg(result);
|
||||
u.setArgRegion(curRegion);
|
||||
}
|
||||
u.getAssigns().add(curRegion);
|
||||
}
|
||||
// args
|
||||
args.clear();
|
||||
insn.getRegisterArgs(args);
|
||||
for (RegisterArg arg : args) {
|
||||
Usage u = addToUsageMap(arg, usageMap);
|
||||
u.getUseRegions().add(curRegion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
if (mth.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
List<RegisterArg> mthArguments = mth.getArguments(true);
|
||||
|
||||
Map<Variable, Usage> usageMap = new LinkedHashMap<>();
|
||||
for (RegisterArg arg : mthArguments) {
|
||||
addToUsageMap(arg, usageMap);
|
||||
}
|
||||
|
||||
// collect all variables usage
|
||||
IRegionVisitor collect = new CollectUsageRegionVisitor(usageMap);
|
||||
DepthRegionTraversal.traverse(mth, collect);
|
||||
|
||||
// reduce assigns map
|
||||
for (RegisterArg arg : mthArguments) {
|
||||
usageMap.remove(new Variable(arg));
|
||||
}
|
||||
|
||||
Iterator<Entry<Variable, Usage>> umIt = usageMap.entrySet().iterator();
|
||||
while (umIt.hasNext()) {
|
||||
Entry<Variable, Usage> entry = umIt.next();
|
||||
Usage u = entry.getValue();
|
||||
// if no assigns => remove
|
||||
if (u.getAssigns().isEmpty()) {
|
||||
umIt.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
// variable declared at 'catch' clause
|
||||
InsnNode parentInsn = u.getArg().getParentInsn();
|
||||
if (parentInsn == null || parentInsn.getType() == InsnType.MOVE_EXCEPTION) {
|
||||
umIt.remove();
|
||||
}
|
||||
}
|
||||
if (usageMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Iterator<Entry<Variable, Usage>> it = usageMap.entrySet().iterator(); it.hasNext(); ) {
|
||||
Entry<Variable, Usage> entry = it.next();
|
||||
Usage u = entry.getValue();
|
||||
// check if variable can be declared at current assigns
|
||||
for (IRegion assignRegion : u.getAssigns()) {
|
||||
if (u.getArgRegion() == assignRegion
|
||||
&& canDeclareInRegion(u, assignRegion)
|
||||
&& declareAtAssign(u)) {
|
||||
it.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (usageMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// apply
|
||||
for (Entry<Variable, Usage> entry : usageMap.entrySet()) {
|
||||
Usage u = entry.getValue();
|
||||
|
||||
// find region which contain all usage regions
|
||||
Set<IRegion> set = u.getUseRegions();
|
||||
for (Iterator<IRegion> it = set.iterator(); it.hasNext(); ) {
|
||||
IRegion r = it.next();
|
||||
IRegion parent = r.getParent();
|
||||
if (parent != null && set.contains(parent)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
IRegion region = null;
|
||||
if (!set.isEmpty()) {
|
||||
region = set.iterator().next();
|
||||
} else if (!u.getAssigns().isEmpty()) {
|
||||
region = u.getAssigns().iterator().next();
|
||||
}
|
||||
if (region == null) {
|
||||
continue;
|
||||
}
|
||||
IRegion parent = region;
|
||||
boolean declared = false;
|
||||
while (parent != null) {
|
||||
if (canDeclareInRegion(u, region)) {
|
||||
declareVar(region, u.getArg());
|
||||
declared = true;
|
||||
break;
|
||||
}
|
||||
region = parent;
|
||||
parent = region.getParent();
|
||||
}
|
||||
if (!declared) {
|
||||
declareVar(mth.getRegion(), u.getArg());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Usage addToUsageMap(RegisterArg arg, Map<Variable, Usage> usageMap) {
|
||||
Variable varId = new Variable(arg);
|
||||
Usage usage = usageMap.computeIfAbsent(varId, v -> new Usage());
|
||||
// merge variables names
|
||||
if (usage.getVarName() == null) {
|
||||
VarName argVN = arg.getSVar().getVarName();
|
||||
if (argVN == null) {
|
||||
argVN = new VarName();
|
||||
arg.getSVar().setVarName(argVN);
|
||||
}
|
||||
usage.setVarName(argVN);
|
||||
} else {
|
||||
arg.getSVar().setVarName(usage.getVarName());
|
||||
}
|
||||
return usage;
|
||||
}
|
||||
|
||||
private static boolean declareAtAssign(Usage u) {
|
||||
RegisterArg arg = u.getArg();
|
||||
InsnNode parentInsn = arg.getParentInsn();
|
||||
if (parentInsn == null) {
|
||||
return false;
|
||||
}
|
||||
if (!arg.equals(parentInsn.getResult())) {
|
||||
return false;
|
||||
}
|
||||
parentInsn.add(AFlag.DECLARE_VAR);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void declareVar(IContainer region, RegisterArg arg) {
|
||||
DeclareVariablesAttr dv = region.get(AType.DECLARE_VARIABLES);
|
||||
if (dv == null) {
|
||||
dv = new DeclareVariablesAttr();
|
||||
region.addAttr(dv);
|
||||
}
|
||||
dv.addVar(arg);
|
||||
}
|
||||
|
||||
private static boolean canDeclareInRegion(Usage u, IRegion region) {
|
||||
// workaround for declare variables used in several loops
|
||||
if (region instanceof LoopRegion) {
|
||||
for (IRegion r : u.getAssigns()) {
|
||||
if (!RegionUtils.isRegionContainsRegion(region, r)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// can't declare in else-if chain between 'else' and next 'if'
|
||||
if (region.contains(AFlag.ELSE_IF_CHAIN)) {
|
||||
return false;
|
||||
}
|
||||
// TODO: make index for faster search
|
||||
return isAllRegionsAfter(region, u.getAssigns())
|
||||
&& isAllRegionsAfter(region, u.getUseRegions());
|
||||
}
|
||||
|
||||
private static boolean isAllRegionsAfter(IRegion region, Set<IRegion> others) {
|
||||
for (IRegion r : others) {
|
||||
if (!RegionUtils.isRegionContainsRegion(region, r)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -116,8 +116,8 @@ public class RegionMaker {
|
||||
}
|
||||
}
|
||||
|
||||
if (!processed && block.getInstructions().size() == 1) {
|
||||
InsnNode insn = block.getInstructions().get(0);
|
||||
InsnNode insn = BlockUtils.getLastInsn(block);
|
||||
if (!processed && insn != null) {
|
||||
switch (insn.getType()) {
|
||||
case IF:
|
||||
next = processIf(r, block, (IfNode) insn, stack);
|
||||
@@ -204,13 +204,13 @@ public class RegionMaker {
|
||||
BlockNode thenBlock = condInfo.getThenBlock();
|
||||
out = thenBlock == loopStart ? condInfo.getElseBlock() : thenBlock;
|
||||
loopStart.remove(AType.LOOP);
|
||||
loop.getEnd().add(AFlag.SKIP);
|
||||
loop.getEnd().add(AFlag.ADDED_TO_REGION);
|
||||
stack.addExit(loop.getEnd());
|
||||
processedBlocks.clear(loopStart.getId());
|
||||
Region body = makeRegion(loopStart, stack);
|
||||
loopRegion.setBody(body);
|
||||
loopStart.addAttr(AType.LOOP, loop);
|
||||
loop.getEnd().remove(AFlag.SKIP);
|
||||
loop.getEnd().remove(AFlag.ADDED_TO_REGION);
|
||||
} else {
|
||||
out = condInfo.getElseBlock();
|
||||
if (outerRegion != null
|
||||
@@ -230,7 +230,7 @@ public class RegionMaker {
|
||||
blocks.remove(conditionBlock);
|
||||
for (BlockNode block : blocks) {
|
||||
if (block.getInstructions().isEmpty()
|
||||
&& !block.contains(AFlag.SKIP)
|
||||
&& !block.contains(AFlag.ADDED_TO_REGION)
|
||||
&& !RegionUtils.isRegionContainsBlock(body, block)) {
|
||||
body.add(block);
|
||||
}
|
||||
@@ -248,9 +248,11 @@ public class RegionMaker {
|
||||
*/
|
||||
private LoopRegion makeLoopRegion(IRegion curRegion, LoopInfo loop, List<BlockNode> exitBlocks) {
|
||||
for (BlockNode block : exitBlocks) {
|
||||
if (block.contains(AType.EXC_HANDLER)
|
||||
|| block.getInstructions().size() != 1
|
||||
|| block.getInstructions().get(0).getType() != InsnType.IF) {
|
||||
if (block.contains(AType.EXC_HANDLER)) {
|
||||
continue;
|
||||
}
|
||||
InsnNode lastInsn = BlockUtils.getLastInsn(block);
|
||||
if (lastInsn == null || lastInsn.getType() != InsnType.IF) {
|
||||
continue;
|
||||
}
|
||||
List<LoopInfo> loops = block.getAll(AType.LOOP);
|
||||
@@ -520,7 +522,7 @@ public class RegionMaker {
|
||||
return false;
|
||||
}
|
||||
BlockNode codePred = preds.get(0);
|
||||
if (codePred.contains(AFlag.SKIP)) {
|
||||
if (codePred.contains(AFlag.ADDED_TO_REGION)) {
|
||||
return false;
|
||||
}
|
||||
if (loopEnd.isDominator(codePred)
|
||||
@@ -561,9 +563,10 @@ public class RegionMaker {
|
||||
for (InsnNode exitInsn : synchRegion.getExitInsns()) {
|
||||
BlockNode insnBlock = BlockUtils.getBlockByInsn(mth, exitInsn);
|
||||
if (insnBlock != null) {
|
||||
insnBlock.add(AFlag.SKIP);
|
||||
insnBlock.add(AFlag.DONT_GENERATE);
|
||||
}
|
||||
exitInsn.add(AFlag.SKIP);
|
||||
exitInsn.add(AFlag.DONT_GENERATE);
|
||||
exitInsn.add(AFlag.REMOVE);
|
||||
InstructionRemover.unbindInsn(mth, exitInsn);
|
||||
}
|
||||
|
||||
@@ -646,7 +649,7 @@ public class RegionMaker {
|
||||
}
|
||||
|
||||
private BlockNode processIf(IRegion currentRegion, BlockNode block, IfNode ifnode, RegionStack stack) {
|
||||
if (block.contains(AFlag.SKIP)) {
|
||||
if (block.contains(AFlag.ADDED_TO_REGION)) {
|
||||
// block already included in other 'if' region
|
||||
return ifnode.getThenBlock();
|
||||
}
|
||||
@@ -712,7 +715,7 @@ public class RegionMaker {
|
||||
|
||||
private void addEdgeInsn(IfInfo ifInfo, Region region, EdgeInsnAttr edgeInsnAttr) {
|
||||
BlockNode start = edgeInsnAttr.getStart();
|
||||
if (start.contains(AFlag.SKIP)) {
|
||||
if (start.contains(AFlag.ADDED_TO_REGION)) {
|
||||
return;
|
||||
}
|
||||
boolean fromThisIf = false;
|
||||
@@ -849,7 +852,10 @@ public class RegionMaker {
|
||||
}
|
||||
|
||||
if (!stack.containsExit(defCase)) {
|
||||
sw.setDefaultCase(makeRegion(defCase, stack));
|
||||
Region defRegion = makeRegion(defCase, stack);
|
||||
if (RegionUtils.notEmpty(defRegion)) {
|
||||
sw.setDefaultCase(defRegion);
|
||||
}
|
||||
}
|
||||
for (Entry<BlockNode, List<Object>> entry : blocksMap.entrySet()) {
|
||||
BlockNode caseBlock = entry.getKey();
|
||||
|
||||
@@ -35,7 +35,7 @@ import jadx.core.utils.exceptions.JadxException;
|
||||
public class RegionMakerVisitor extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RegionMakerVisitor.class);
|
||||
|
||||
private static final PostRegionVisitor POST_REGION_VISITOR = new PostRegionVisitor();
|
||||
private static final IRegionVisitor POST_REGION_VISITOR = new PostRegionVisitor();
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
@@ -54,7 +54,6 @@ public class RegionMakerVisitor extends AbstractVisitor {
|
||||
mth.getRegion().add(expOutBlock);
|
||||
}
|
||||
}
|
||||
|
||||
postProcessRegions(mth);
|
||||
}
|
||||
|
||||
@@ -85,82 +84,83 @@ public class RegionMakerVisitor extends AbstractVisitor {
|
||||
insertEdgeInsn((Region) region);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert insn block from edge insn attribute.
|
||||
*/
|
||||
private static void insertEdgeInsn(Region region) {
|
||||
List<IContainer> subBlocks = region.getSubBlocks();
|
||||
if (subBlocks.isEmpty()) {
|
||||
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;
|
||||
}
|
||||
if (last instanceof BlockNode) {
|
||||
BlockNode block = (BlockNode) last;
|
||||
if (block.getInstructions().isEmpty()) {
|
||||
block.getInstructions().add(insnAttr.getInsn());
|
||||
/**
|
||||
* Insert insn block from edge insn attribute.
|
||||
*/
|
||||
private static void insertEdgeInsn(Region region) {
|
||||
List<IContainer> subBlocks = region.getSubBlocks();
|
||||
if (subBlocks.isEmpty()) {
|
||||
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;
|
||||
IContainer last = subBlocks.get(subBlocks.size() - 1);
|
||||
List<EdgeInsnAttr> edgeInsnAttrs = last.getAll(AType.EDGE_INSN);
|
||||
if (edgeInsnAttrs.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Set<IBlock> blocks = new HashSet<>();
|
||||
RegionUtils.getAllRegionBlocks(c, blocks);
|
||||
if (blocks.isEmpty()) {
|
||||
addBreakToContainer((Region) c);
|
||||
continue;
|
||||
EdgeInsnAttr insnAttr = edgeInsnAttrs.get(0);
|
||||
if (!insnAttr.getStart().equals(last)) {
|
||||
return;
|
||||
}
|
||||
for (IBlock block : blocks) {
|
||||
if (!(block instanceof BlockNode)) {
|
||||
continue;
|
||||
if (last instanceof BlockNode) {
|
||||
BlockNode block = (BlockNode) last;
|
||||
if (block.getInstructions().isEmpty()) {
|
||||
block.getInstructions().add(insnAttr.getInsn());
|
||||
return;
|
||||
}
|
||||
BlockNode bn = (BlockNode) block;
|
||||
for (BlockNode s : bn.getCleanSuccessors()) {
|
||||
if (!blocks.contains(s)
|
||||
&& !bn.contains(AFlag.SKIP)
|
||||
&& !s.contains(AFlag.FALL_THROUGH)) {
|
||||
addBreak(mth, c, bn);
|
||||
break;
|
||||
}
|
||||
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) {
|
||||
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) {
|
||||
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);
|
||||
private static void addBreakToContainer(Region c) {
|
||||
if (RegionUtils.hasExitEdge(c)) {
|
||||
return;
|
||||
}
|
||||
List<InsnNode> insns = new ArrayList<>(1);
|
||||
insns.add(new InsnNode(InsnType.BREAK, 0));
|
||||
c.add(new InsnContainer(insns));
|
||||
}
|
||||
}
|
||||
|
||||
private static void addBreakToContainer(Region c) {
|
||||
if (RegionUtils.hasExitEdge(c)) {
|
||||
return;
|
||||
private static void addBreakForBlock(MethodNode mth, IContainer c, Set<IBlock> blocks, BlockNode bn) {
|
||||
for (BlockNode s : bn.getCleanSuccessors()) {
|
||||
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) {
|
||||
|
||||
@@ -17,7 +17,7 @@ import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.regions.Region;
|
||||
import jadx.core.dex.regions.conditions.IfRegion;
|
||||
import jadx.core.dex.visitors.CodeShrinker;
|
||||
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
|
||||
import jadx.core.utils.InsnList;
|
||||
|
||||
public class TernaryMod {
|
||||
@@ -40,18 +40,18 @@ public class TernaryMod {
|
||||
return false;
|
||||
}
|
||||
BlockNode header = ifRegion.getHeader();
|
||||
InsnNode t = tb.getInstructions().get(0);
|
||||
InsnNode e = eb.getInstructions().get(0);
|
||||
InsnNode thenInsn = tb.getInstructions().get(0);
|
||||
InsnNode elseInsn = eb.getInstructions().get(0);
|
||||
|
||||
if (t.getSourceLine() != e.getSourceLine()) {
|
||||
if (t.getSourceLine() != 0 && e.getSourceLine() != 0) {
|
||||
if (thenInsn.getSourceLine() != elseInsn.getSourceLine()) {
|
||||
if (thenInsn.getSourceLine() != 0 && elseInsn.getSourceLine() != 0) {
|
||||
// sometimes source lines incorrect
|
||||
if (!checkLineStats(t, e)) {
|
||||
if (!checkLineStats(thenInsn, elseInsn)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// no debug info
|
||||
if (containsTernary(t) || containsTernary(e)) {
|
||||
if (containsTernary(thenInsn) || containsTernary(elseInsn)) {
|
||||
// don't make nested ternary by default
|
||||
// TODO: add addition checks
|
||||
return false;
|
||||
@@ -59,50 +59,60 @@ public class TernaryMod {
|
||||
}
|
||||
}
|
||||
|
||||
if (t.getResult() != null && e.getResult() != null) {
|
||||
PhiInsn phi = t.getResult().getSVar().getUsedInPhi();
|
||||
if (phi == null || !t.getResult().equalRegisterAndType(e.getResult())) {
|
||||
RegisterArg thenResArg = thenInsn.getResult();
|
||||
RegisterArg elseResArg = elseInsn.getResult();
|
||||
if (thenResArg != null && elseResArg != null) {
|
||||
PhiInsn thenPhi = thenResArg.getSVar().getUsedInPhi();
|
||||
PhiInsn elsePhi = elseResArg.getSVar().getUsedInPhi();
|
||||
if (thenPhi == null || thenPhi != elsePhi) {
|
||||
return false;
|
||||
}
|
||||
if (!ifRegion.getParent().replaceSubBlock(ifRegion, header)) {
|
||||
return false;
|
||||
}
|
||||
InsnList.remove(tb, t);
|
||||
InsnList.remove(eb, e);
|
||||
InsnList.remove(tb, thenInsn);
|
||||
InsnList.remove(eb, elseInsn);
|
||||
|
||||
RegisterArg resArg;
|
||||
if (phi.getArgsCount() == 2) {
|
||||
resArg = phi.getResult();
|
||||
if (thenPhi.getArgsCount() == 2) {
|
||||
resArg = thenPhi.getResult();
|
||||
} else {
|
||||
resArg = t.getResult();
|
||||
phi.removeArg(e.getResult());
|
||||
resArg = thenResArg;
|
||||
thenPhi.removeArg(elseResArg);
|
||||
}
|
||||
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(),
|
||||
resArg, InsnArg.wrapArg(t), InsnArg.wrapArg(e));
|
||||
ternInsn.setSourceLine(t.getSourceLine());
|
||||
resArg, InsnArg.wrapArg(thenInsn), InsnArg.wrapArg(elseInsn));
|
||||
ternInsn.setSourceLine(thenInsn.getSourceLine());
|
||||
|
||||
// remove 'if' instruction
|
||||
header.getInstructions().clear();
|
||||
header.getInstructions().add(ternInsn);
|
||||
|
||||
// shrink method again
|
||||
CodeShrinker.shrinkMethod(mth);
|
||||
CodeShrinkVisitor.shrinkMethod(mth);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!mth.getReturnType().equals(ArgType.VOID)
|
||||
&& t.getType() == InsnType.RETURN && e.getType() == InsnType.RETURN) {
|
||||
&& thenInsn.getType() == InsnType.RETURN
|
||||
&& elseInsn.getType() == InsnType.RETURN) {
|
||||
InsnArg thenArg = thenInsn.getArg(0);
|
||||
InsnArg elseArg = elseInsn.getArg(0);
|
||||
if (thenArg.isLiteral() != elseArg.isLiteral()) {
|
||||
// one arg is literal
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ifRegion.getParent().replaceSubBlock(ifRegion, header)) {
|
||||
return false;
|
||||
}
|
||||
InsnList.remove(tb, t);
|
||||
InsnList.remove(eb, e);
|
||||
InsnList.remove(tb, thenInsn);
|
||||
InsnList.remove(eb, elseInsn);
|
||||
tb.remove(AFlag.RETURN);
|
||||
eb.remove(AFlag.RETURN);
|
||||
|
||||
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(), null, t.getArg(0), e.getArg(0));
|
||||
ternInsn.setSourceLine(t.getSourceLine());
|
||||
TernaryInsn ternInsn = new TernaryInsn(ifRegion.getCondition(), null, thenArg, elseArg);
|
||||
ternInsn.setSourceLine(thenInsn.getSourceLine());
|
||||
InsnNode retInsn = new InsnNode(InsnType.RETURN, 1);
|
||||
retInsn.addArg(InsnArg.wrapArg(ternInsn));
|
||||
|
||||
@@ -110,7 +120,7 @@ public class TernaryMod {
|
||||
header.getInstructions().add(retInsn);
|
||||
header.add(AFlag.RETURN);
|
||||
|
||||
CodeShrinker.shrinkMethod(mth);
|
||||
CodeShrinkVisitor.shrinkMethod(mth);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package jadx.core.dex.visitors.regions.variables;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.regions.loops.ForLoop;
|
||||
import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.regions.loops.LoopType;
|
||||
import jadx.core.dex.visitors.regions.TracedRegionVisitor;
|
||||
|
||||
class CollectUsageRegionVisitor extends TracedRegionVisitor {
|
||||
private final List<RegisterArg> args;
|
||||
private final Map<SSAVar, VarUsage> usageMap;
|
||||
|
||||
public CollectUsageRegionVisitor() {
|
||||
this.usageMap = new LinkedHashMap<>();
|
||||
this.args = new ArrayList<>();
|
||||
}
|
||||
|
||||
public Map<SSAVar, VarUsage> getUsageMap() {
|
||||
return usageMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processBlockTraced(MethodNode mth, IBlock block, IRegion curRegion) {
|
||||
UsePlace usePlace = new UsePlace(curRegion, block);
|
||||
regionProcess(usePlace);
|
||||
int len = block.getInstructions().size();
|
||||
for (int i = 0; i < len; i++) {
|
||||
InsnNode insn = block.getInstructions().get(i);
|
||||
if (insn.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
processInsn(insn, usePlace);
|
||||
}
|
||||
}
|
||||
|
||||
private void regionProcess(UsePlace usePlace) {
|
||||
IRegion region = usePlace.getRegion();
|
||||
if (region instanceof LoopRegion) {
|
||||
LoopRegion loopRegion = (LoopRegion) region;
|
||||
LoopType loopType = loopRegion.getType();
|
||||
if (loopType instanceof ForLoop) {
|
||||
ForLoop forLoop = (ForLoop) loopType;
|
||||
processInsn(forLoop.getInitInsn(), usePlace);
|
||||
processInsn(forLoop.getIncrInsn(), usePlace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void processInsn(InsnNode insn, UsePlace usePlace) {
|
||||
if (insn == null) {
|
||||
return;
|
||||
}
|
||||
// result
|
||||
RegisterArg result = insn.getResult();
|
||||
if (result != null && result.isRegister()) {
|
||||
if (!result.contains(AFlag.DONT_GENERATE)) {
|
||||
VarUsage usage = getUsage(result.getSVar());
|
||||
usage.getAssigns().add(usePlace);
|
||||
}
|
||||
}
|
||||
// args
|
||||
args.clear();
|
||||
insn.getRegisterArgs(args);
|
||||
for (RegisterArg arg : args) {
|
||||
if (arg.contains(AFlag.DONT_GENERATE)) {
|
||||
continue;
|
||||
}
|
||||
VarUsage usage = getUsage(arg.getSVar());
|
||||
usage.getUses().add(usePlace);
|
||||
}
|
||||
}
|
||||
|
||||
private VarUsage getUsage(SSAVar ssaVar) {
|
||||
return usageMap.computeIfAbsent(ssaVar, VarUsage::new);
|
||||
}
|
||||
}
|
||||
+308
@@ -0,0 +1,308 @@
|
||||
package jadx.core.dex.visitors.regions.variables;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.DeclareVariablesAttr;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.CodeVar;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IContainer;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.regions.loops.LoopRegion;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.dex.visitors.regions.DepthRegionTraversal;
|
||||
import jadx.core.utils.RegionUtils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
public class ProcessVariables extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ProcessVariables.class);
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) throws JadxException {
|
||||
if (mth.isNoCode() || mth.getSVars().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<CodeVar> codeVars = collectCodeVars(mth);
|
||||
if (codeVars.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
checkCodeVars(mth, codeVars);
|
||||
// TODO: reduce code vars by name if debug info applied. Need checks for variable scopes before reduce
|
||||
|
||||
// collect all variables usage
|
||||
CollectUsageRegionVisitor usageCollector = new CollectUsageRegionVisitor();
|
||||
DepthRegionTraversal.traverse(mth, usageCollector);
|
||||
Map<SSAVar, VarUsage> ssaUsageMap = usageCollector.getUsageMap();
|
||||
if (ssaUsageMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<CodeVar, List<VarUsage>> codeVarUsage = mergeUsageMaps(codeVars, ssaUsageMap);
|
||||
|
||||
for (Entry<CodeVar, List<VarUsage>> entry : codeVarUsage.entrySet()) {
|
||||
declareVar(mth, entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCodeVars(MethodNode mth, List<CodeVar> codeVars) {
|
||||
int unknownTypesCount = 0;
|
||||
for (CodeVar codeVar : codeVars) {
|
||||
codeVar.getSsaVars().stream()
|
||||
.filter(ssaVar -> ssaVar.contains(AFlag.IMMUTABLE_TYPE))
|
||||
.forEach(ssaVar -> {
|
||||
ArgType ssaType = ssaVar.getAssign().getInitType();
|
||||
if (ssaType.isTypeKnown() && !ssaType.equals(codeVar.getType())) {
|
||||
mth.addWarn("Incorrect type for immutable var: ssa=" + ssaType
|
||||
+ ", code=" + codeVar.getType()
|
||||
+ ", for " + ssaVar.getDetailedVarInfo(mth));
|
||||
}
|
||||
});
|
||||
if (codeVar.getType() == null) {
|
||||
codeVar.setType(ArgType.UNKNOWN);
|
||||
unknownTypesCount++;
|
||||
}
|
||||
}
|
||||
if (unknownTypesCount != 0) {
|
||||
mth.addWarn("Unknown variable types count: " + unknownTypesCount);
|
||||
}
|
||||
}
|
||||
|
||||
private void declareVar(MethodNode mth, CodeVar codeVar, List<VarUsage> usageList) {
|
||||
if (codeVar.isDeclared()) {
|
||||
return;
|
||||
}
|
||||
|
||||
VarUsage mergedUsage = new VarUsage(null);
|
||||
for (VarUsage varUsage : usageList) {
|
||||
mergedUsage.getAssigns().addAll(varUsage.getAssigns());
|
||||
mergedUsage.getUses().addAll(varUsage.getUses());
|
||||
}
|
||||
if (mergedUsage.getAssigns().isEmpty() && mergedUsage.getUses().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if variable can be declared at one of assigns
|
||||
if (checkDeclareAtAssign(usageList, mergedUsage)) {
|
||||
return;
|
||||
}
|
||||
// search closest region for declare
|
||||
if (searchDeclareRegion(mergedUsage, codeVar)) {
|
||||
return;
|
||||
}
|
||||
// region not found, declare at method start
|
||||
declareVarInRegion(mth.getRegion(), codeVar);
|
||||
}
|
||||
|
||||
private List<CodeVar> collectCodeVars(MethodNode mth) {
|
||||
Map<CodeVar, List<SSAVar>> codeVars = new LinkedHashMap<>();
|
||||
for (SSAVar ssaVar : mth.getSVars()) {
|
||||
if (ssaVar.getCodeVar().isThis()) {
|
||||
continue;
|
||||
}
|
||||
CodeVar codeVar = ssaVar.getCodeVar();
|
||||
List<SSAVar> list = codeVars.computeIfAbsent(codeVar, k -> new ArrayList<>());
|
||||
list.add(ssaVar);
|
||||
}
|
||||
|
||||
for (Entry<CodeVar, List<SSAVar>> entry : codeVars.entrySet()) {
|
||||
CodeVar codeVar = entry.getKey();
|
||||
List<SSAVar> list = entry.getValue();
|
||||
for (SSAVar ssaVar : list) {
|
||||
CodeVar localCodeVar = ssaVar.getCodeVar();
|
||||
codeVar.mergeFlagsFrom(localCodeVar);
|
||||
}
|
||||
if (list.size() > 1) {
|
||||
for (SSAVar ssaVar : list) {
|
||||
ssaVar.setCodeVar(codeVar);
|
||||
}
|
||||
}
|
||||
codeVar.setSsaVars(list);
|
||||
}
|
||||
return new ArrayList<>(codeVars.keySet());
|
||||
}
|
||||
|
||||
private Map<CodeVar, List<VarUsage>> mergeUsageMaps(List<CodeVar> codeVars, Map<SSAVar, VarUsage> ssaUsageMap) {
|
||||
Map<CodeVar, List<VarUsage>> codeVarUsage = new LinkedHashMap<>(codeVars.size());
|
||||
for (CodeVar codeVar : codeVars) {
|
||||
List<VarUsage> list = new ArrayList<>();
|
||||
for (SSAVar ssaVar : codeVar.getSsaVars()) {
|
||||
VarUsage usage = ssaUsageMap.get(ssaVar);
|
||||
if (usage != null) {
|
||||
list.add(usage);
|
||||
}
|
||||
}
|
||||
if (!list.isEmpty()) {
|
||||
codeVarUsage.put(codeVar, list);
|
||||
}
|
||||
}
|
||||
return codeVarUsage;
|
||||
}
|
||||
|
||||
private boolean checkDeclareAtAssign(List<VarUsage> list, VarUsage mergedUsage) {
|
||||
if (mergedUsage.getAssigns().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (VarUsage u : list) {
|
||||
for (UsePlace assign : u.getAssigns()) {
|
||||
if (canDeclareAt(mergedUsage, assign)) {
|
||||
return checkDeclareAtAssign(u.getVar());
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean canDeclareAt(VarUsage usage, UsePlace usePlace) {
|
||||
IRegion region = usePlace.getRegion();
|
||||
// workaround for declare variables used in several loops
|
||||
if (region instanceof LoopRegion) {
|
||||
for (UsePlace use : usage.getAssigns()) {
|
||||
if (!RegionUtils.isRegionContainsRegion(region, use.getRegion())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// can't declare in else-if chain between 'else' and next 'if'
|
||||
if (region.contains(AFlag.ELSE_IF_CHAIN)) {
|
||||
return false;
|
||||
}
|
||||
return isAllUseAfter(usePlace, usage.getAssigns())
|
||||
&& isAllUseAfter(usePlace, usage.getUses());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if all {@code usePlaces} are after {@code checkPlace}
|
||||
*/
|
||||
private static boolean isAllUseAfter(UsePlace checkPlace, List<UsePlace> usePlaces) {
|
||||
|
||||
IRegion region = checkPlace.getRegion();
|
||||
IBlock block = checkPlace.getBlock();
|
||||
Set<UsePlace> toCheck = new HashSet<>(usePlaces);
|
||||
boolean blockFound = false;
|
||||
for (IContainer subBlock : region.getSubBlocks()) {
|
||||
if (!blockFound && subBlock == block) {
|
||||
blockFound = true;
|
||||
}
|
||||
if (blockFound) {
|
||||
toCheck.removeIf(usePlace -> isContainerContainsUsePlace(subBlock, usePlace));
|
||||
if (toCheck.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isContainerContainsUsePlace(IContainer subBlock, UsePlace usePlace) {
|
||||
if (subBlock == usePlace.getBlock()) {
|
||||
return true;
|
||||
}
|
||||
if (subBlock instanceof IRegion) {
|
||||
// TODO: make index for faster check
|
||||
return RegionUtils.isRegionContainsRegion(subBlock, usePlace.getRegion());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean checkDeclareAtAssign(SSAVar var) {
|
||||
RegisterArg arg = var.getAssign();
|
||||
InsnNode parentInsn = arg.getParentInsn();
|
||||
if (parentInsn == null) {
|
||||
return false;
|
||||
}
|
||||
if (!arg.equals(parentInsn.getResult())) {
|
||||
return false;
|
||||
}
|
||||
parentInsn.add(AFlag.DECLARE_VAR);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean searchDeclareRegion(VarUsage u, CodeVar codeVar) {
|
||||
/*
|
||||
Set<IRegion> set = u.getUseRegions();
|
||||
for (Iterator<IRegion> it = set.iterator(); it.hasNext(); ) {
|
||||
IRegion r = it.next();
|
||||
IRegion parent = r.getParent();
|
||||
if (parent != null && set.contains(parent)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
IRegion region = null;
|
||||
if (!set.isEmpty()) {
|
||||
region = set.iterator().next();
|
||||
} else if (!u.getAssigns().isEmpty()) {
|
||||
region = u.getAssigns().iterator().next();
|
||||
}
|
||||
if (region == null) {
|
||||
return false;
|
||||
}
|
||||
IRegion parent = region;
|
||||
while (parent != null) {
|
||||
if (canDeclareAt(u, region)) {
|
||||
declareVarInRegion(region, codeVar);
|
||||
return true;
|
||||
}
|
||||
region = parent;
|
||||
parent = region.getParent();
|
||||
}
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void declareVarInRegion(IContainer region, CodeVar var) {
|
||||
if (var.isDeclared()) {
|
||||
LOG.warn("Try to declare already declared variable: {}", var);
|
||||
return;
|
||||
}
|
||||
DeclareVariablesAttr dv = region.get(AType.DECLARE_VARIABLES);
|
||||
if (dv == null) {
|
||||
dv = new DeclareVariablesAttr();
|
||||
region.addAttr(dv);
|
||||
}
|
||||
dv.addVar(var);
|
||||
var.setDeclared(true);
|
||||
}
|
||||
|
||||
private static boolean isAllRegionsAfter(IRegion region, Set<IRegion> others) {
|
||||
IRegion parent = region.getParent();
|
||||
if (parent == null) {
|
||||
return true;
|
||||
}
|
||||
// lazy init for
|
||||
int regionIndex = -2;
|
||||
List<IContainer> subBlocks = Collections.emptyList();
|
||||
for (IRegion r : others) {
|
||||
if (parent == r.getParent()) {
|
||||
// on same level, check order by index
|
||||
if (regionIndex == -2) {
|
||||
subBlocks = parent.getSubBlocks();
|
||||
regionIndex = subBlocks.indexOf(region);
|
||||
}
|
||||
int rIndex = subBlocks.indexOf(r);
|
||||
if (regionIndex > rIndex) {
|
||||
return false;
|
||||
}
|
||||
} else if (!RegionUtils.isRegionContainsRegion(region, r)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package jadx.core.dex.visitors.regions.variables;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import jadx.core.dex.nodes.IBlock;
|
||||
import jadx.core.dex.nodes.IRegion;
|
||||
|
||||
public class UsePlace {
|
||||
public final IRegion region;
|
||||
public final IBlock block;
|
||||
|
||||
public UsePlace(IRegion region, IBlock block) {
|
||||
this.region = region;
|
||||
this.block = block;
|
||||
}
|
||||
|
||||
public IRegion getRegion() {
|
||||
return region;
|
||||
}
|
||||
|
||||
public IBlock getBlock() {
|
||||
return block;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
UsePlace usePlace = (UsePlace) o;
|
||||
return Objects.equals(region, usePlace.region)
|
||||
&& Objects.equals(block, usePlace.block);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(region, block);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UsePlace{" +
|
||||
"region=" + region +
|
||||
", block=" + block +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package jadx.core.dex.visitors.regions.variables;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
|
||||
class VarUsage {
|
||||
private final SSAVar var;
|
||||
private final List<UsePlace> assigns = new ArrayList<>(3);
|
||||
private final List<UsePlace> uses = new ArrayList<>(3);
|
||||
|
||||
VarUsage(SSAVar var) {
|
||||
this.var = var;
|
||||
}
|
||||
|
||||
public SSAVar getVar() {
|
||||
return var;
|
||||
}
|
||||
|
||||
public List<UsePlace> getAssigns() {
|
||||
return assigns;
|
||||
}
|
||||
|
||||
public List<UsePlace> getUses() {
|
||||
return uses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return '{' + (var == null ? "-" : var.toShortString()) + ", a:" + assigns + ", u:" + uses + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package jadx.core.dex.visitors.shrink;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
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.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.instructions.mods.TernaryInsn;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.EmptyBitSet;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
final class ArgsInfo {
|
||||
private final InsnNode insn;
|
||||
private final List<ArgsInfo> argsList;
|
||||
private final List<RegisterArg> args;
|
||||
private final int pos;
|
||||
private int inlineBorder;
|
||||
private ArgsInfo inlinedInsn;
|
||||
|
||||
public ArgsInfo(InsnNode insn, List<ArgsInfo> argsList, int pos) {
|
||||
this.insn = insn;
|
||||
this.argsList = argsList;
|
||||
this.pos = pos;
|
||||
this.inlineBorder = pos;
|
||||
this.args = getArgs(insn);
|
||||
}
|
||||
|
||||
public static List<RegisterArg> getArgs(InsnNode insn) {
|
||||
List<RegisterArg> args = new LinkedList<>();
|
||||
addArgs(insn, args);
|
||||
return args;
|
||||
}
|
||||
|
||||
private static void addArgs(InsnNode insn, List<RegisterArg> args) {
|
||||
if (insn.getType() == InsnType.CONSTRUCTOR) {
|
||||
args.add(((ConstructorInsn) insn).getInstanceArg());
|
||||
} else if (insn.getType() == InsnType.TERNARY) {
|
||||
args.addAll(((TernaryInsn) insn).getCondition().getRegisterArgs());
|
||||
}
|
||||
for (InsnArg arg : insn.getArguments()) {
|
||||
if (arg.isRegister()) {
|
||||
args.add((RegisterArg) arg);
|
||||
}
|
||||
}
|
||||
for (InsnArg arg : insn.getArguments()) {
|
||||
if (arg.isInsnWrap()) {
|
||||
addArgs(((InsnWrapArg) arg).getWrapInsn(), args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public InsnNode getInsn() {
|
||||
return insn;
|
||||
}
|
||||
|
||||
List<RegisterArg> getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
public WrapInfo checkInline(int assignPos, RegisterArg arg) {
|
||||
if (assignPos >= inlineBorder || !canMove(assignPos, inlineBorder)) {
|
||||
return null;
|
||||
}
|
||||
inlineBorder = assignPos;
|
||||
return inline(assignPos, arg);
|
||||
}
|
||||
|
||||
private boolean canMove(int from, int to) {
|
||||
ArgsInfo startInfo = argsList.get(from);
|
||||
List<RegisterArg> movedArgs = startInfo.getArgs();
|
||||
int start = from + 1;
|
||||
if (start == to) {
|
||||
// previous instruction or on edge of inline border
|
||||
return true;
|
||||
}
|
||||
if (start > to) {
|
||||
throw new JadxRuntimeException("Invalid inline insn positions: " + start + " - " + to);
|
||||
}
|
||||
BitSet movedSet;
|
||||
if (movedArgs.isEmpty()) {
|
||||
if (startInfo.insn.isConstInsn()) {
|
||||
return true;
|
||||
}
|
||||
movedSet = EmptyBitSet.EMPTY;
|
||||
} else {
|
||||
movedSet = new BitSet();
|
||||
for (RegisterArg arg : movedArgs) {
|
||||
movedSet.set(arg.getRegNum());
|
||||
}
|
||||
}
|
||||
for (int i = start; i < to; i++) {
|
||||
ArgsInfo argsInfo = argsList.get(i);
|
||||
if (argsInfo.getInlinedInsn() == this) {
|
||||
continue;
|
||||
}
|
||||
InsnNode curInsn = argsInfo.insn;
|
||||
if (!curInsn.canReorder() || usedArgAssign(curInsn, movedSet)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean usedArgAssign(InsnNode insn, BitSet args) {
|
||||
if (args.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
RegisterArg result = insn.getResult();
|
||||
if (result == null || result.isField()) {
|
||||
return false;
|
||||
}
|
||||
return args.get(result.getRegNum());
|
||||
}
|
||||
|
||||
WrapInfo inline(int assignInsnPos, RegisterArg arg) {
|
||||
ArgsInfo argsInfo = argsList.get(assignInsnPos);
|
||||
argsInfo.inlinedInsn = this;
|
||||
return new WrapInfo(argsInfo.insn, arg);
|
||||
}
|
||||
|
||||
ArgsInfo getInlinedInsn() {
|
||||
if (inlinedInsn != null) {
|
||||
ArgsInfo parent = inlinedInsn.getInlinedInsn();
|
||||
if (parent != null) {
|
||||
inlinedInsn = parent;
|
||||
}
|
||||
}
|
||||
return inlinedInsn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ArgsInfo: |" + inlineBorder
|
||||
+ " ->" + (inlinedInsn == null ? "-" : inlinedInsn.pos)
|
||||
+ ' ' + args + " : " + insn;
|
||||
}
|
||||
}
|
||||
+19
-157
@@ -1,8 +1,7 @@
|
||||
package jadx.core.dex.visitors;
|
||||
package jadx.core.dex.visitors.shrink;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Set;
|
||||
@@ -13,17 +12,22 @@ 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.instructions.args.SSAVar;
|
||||
import jadx.core.dex.instructions.mods.ConstructorInsn;
|
||||
import jadx.core.dex.instructions.mods.TernaryInsn;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.dex.visitors.AbstractVisitor;
|
||||
import jadx.core.dex.visitors.JadxVisitor;
|
||||
import jadx.core.dex.visitors.ModVisitor;
|
||||
import jadx.core.utils.BlockUtils;
|
||||
import jadx.core.utils.EmptyBitSet;
|
||||
import jadx.core.utils.InsnList;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class CodeShrinker extends AbstractVisitor {
|
||||
@JadxVisitor(
|
||||
name = "CodeShrinkVisitor",
|
||||
desc = "Inline variables for make code smaller",
|
||||
runAfter = {ModVisitor.class}
|
||||
)
|
||||
public class CodeShrinkVisitor extends AbstractVisitor {
|
||||
|
||||
@Override
|
||||
public void visit(MethodNode mth) {
|
||||
@@ -31,7 +35,7 @@ public class CodeShrinker extends AbstractVisitor {
|
||||
}
|
||||
|
||||
public static void shrinkMethod(MethodNode mth) {
|
||||
if (mth.isNoCode() || mth.contains(AFlag.DONT_SHRINK)) {
|
||||
if (mth.isNoCode()) {
|
||||
return;
|
||||
}
|
||||
for (BlockNode block : mth.getBasicBlocks()) {
|
||||
@@ -40,156 +44,6 @@ public class CodeShrinker extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ArgsInfo {
|
||||
private final InsnNode insn;
|
||||
private final List<ArgsInfo> argsList;
|
||||
private final List<RegisterArg> args;
|
||||
private final int pos;
|
||||
private int inlineBorder;
|
||||
private ArgsInfo inlinedInsn;
|
||||
|
||||
public ArgsInfo(InsnNode insn, List<ArgsInfo> argsList, int pos) {
|
||||
this.insn = insn;
|
||||
this.argsList = argsList;
|
||||
this.pos = pos;
|
||||
this.inlineBorder = pos;
|
||||
this.args = getArgs(insn);
|
||||
}
|
||||
|
||||
public static List<RegisterArg> getArgs(InsnNode insn) {
|
||||
List<RegisterArg> args = new LinkedList<>();
|
||||
addArgs(insn, args);
|
||||
return args;
|
||||
}
|
||||
|
||||
private static void addArgs(InsnNode insn, List<RegisterArg> args) {
|
||||
if (insn.getType() == InsnType.CONSTRUCTOR) {
|
||||
args.add(((ConstructorInsn) insn).getInstanceArg());
|
||||
} else if (insn.getType() == InsnType.TERNARY) {
|
||||
args.addAll(((TernaryInsn) insn).getCondition().getRegisterArgs());
|
||||
}
|
||||
for (InsnArg arg : insn.getArguments()) {
|
||||
if (arg.isRegister()) {
|
||||
args.add((RegisterArg) arg);
|
||||
}
|
||||
}
|
||||
for (InsnArg arg : insn.getArguments()) {
|
||||
if (arg.isInsnWrap()) {
|
||||
addArgs(((InsnWrapArg) arg).getWrapInsn(), args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public InsnNode getInsn() {
|
||||
return insn;
|
||||
}
|
||||
|
||||
private List<RegisterArg> getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
public WrapInfo checkInline(int assignPos, RegisterArg arg) {
|
||||
if (assignPos >= inlineBorder || !canMove(assignPos, inlineBorder)) {
|
||||
return null;
|
||||
}
|
||||
inlineBorder = assignPos;
|
||||
return inline(assignPos, arg);
|
||||
}
|
||||
|
||||
private boolean canMove(int from, int to) {
|
||||
ArgsInfo startInfo = argsList.get(from);
|
||||
List<RegisterArg> movedArgs = startInfo.getArgs();
|
||||
int start = from + 1;
|
||||
if (start == to) {
|
||||
// previous instruction or on edge of inline border
|
||||
return true;
|
||||
}
|
||||
if (start > to) {
|
||||
throw new JadxRuntimeException("Invalid inline insn positions: " + start + " - " + to);
|
||||
}
|
||||
BitSet movedSet;
|
||||
if (movedArgs.isEmpty()) {
|
||||
if (startInfo.insn.isConstInsn()) {
|
||||
return true;
|
||||
}
|
||||
movedSet = EmptyBitSet.EMPTY;
|
||||
} else {
|
||||
movedSet = new BitSet();
|
||||
for (RegisterArg arg : movedArgs) {
|
||||
movedSet.set(arg.getRegNum());
|
||||
}
|
||||
}
|
||||
for (int i = start; i < to; i++) {
|
||||
ArgsInfo argsInfo = argsList.get(i);
|
||||
if (argsInfo.getInlinedInsn() == this) {
|
||||
continue;
|
||||
}
|
||||
InsnNode curInsn = argsInfo.insn;
|
||||
if (!curInsn.canReorder() || usedArgAssign(curInsn, movedSet)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean usedArgAssign(InsnNode insn, BitSet args) {
|
||||
if (args.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
RegisterArg result = insn.getResult();
|
||||
if (result == null || result.isField()) {
|
||||
return false;
|
||||
}
|
||||
return args.get(result.getRegNum());
|
||||
}
|
||||
|
||||
public WrapInfo inline(int assignInsnPos, RegisterArg arg) {
|
||||
ArgsInfo argsInfo = argsList.get(assignInsnPos);
|
||||
argsInfo.inlinedInsn = this;
|
||||
return new WrapInfo(argsInfo.insn, arg);
|
||||
}
|
||||
|
||||
public ArgsInfo getInlinedInsn() {
|
||||
if (inlinedInsn != null) {
|
||||
ArgsInfo parent = inlinedInsn.getInlinedInsn();
|
||||
if (parent != null) {
|
||||
inlinedInsn = parent;
|
||||
}
|
||||
}
|
||||
return inlinedInsn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ArgsInfo: |" + inlineBorder
|
||||
+ " ->" + (inlinedInsn == null ? '-' : inlinedInsn.pos)
|
||||
+ ' ' + args + " : " + insn;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class WrapInfo {
|
||||
private final InsnNode insn;
|
||||
private final RegisterArg arg;
|
||||
|
||||
public WrapInfo(InsnNode assignInsn, RegisterArg arg) {
|
||||
this.insn = assignInsn;
|
||||
this.arg = arg;
|
||||
}
|
||||
|
||||
private InsnNode getInsn() {
|
||||
return insn;
|
||||
}
|
||||
|
||||
private RegisterArg getArg() {
|
||||
return arg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "WrapInfo: " + arg + " -> " + insn;
|
||||
}
|
||||
}
|
||||
|
||||
private static void shrinkBlock(MethodNode mth, BlockNode block) {
|
||||
if (block.getInstructions().isEmpty()) {
|
||||
return;
|
||||
@@ -223,6 +77,14 @@ public class CodeShrinker extends AbstractVisitor {
|
||||
if (assignInsn == null || assignInsn.contains(AFlag.DONT_INLINE)) {
|
||||
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);
|
||||
if (assignPos != -1) {
|
||||
WrapInfo wrapInfo = argsInfo.checkInline(assignPos, arg);
|
||||
@@ -0,0 +1,27 @@
|
||||
package jadx.core.dex.visitors.shrink;
|
||||
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
|
||||
final class WrapInfo {
|
||||
private final InsnNode insn;
|
||||
private final RegisterArg arg;
|
||||
|
||||
WrapInfo(InsnNode assignInsn, RegisterArg arg) {
|
||||
this.insn = assignInsn;
|
||||
this.arg = arg;
|
||||
}
|
||||
|
||||
InsnNode getInsn() {
|
||||
return insn;
|
||||
}
|
||||
|
||||
RegisterArg getArg() {
|
||||
return arg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "WrapInfo: " + arg + " -> " + insn;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user