Add source files and samples

This commit is contained in:
Skylot
2013-03-18 21:05:28 +04:00
parent 0f520dfb42
commit d01a280ddf
145 changed files with 13472 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
package jadx;
public class Consts {
public static final String JADX_VERSION = "dev";
public static final boolean DEBUG = false;
public static final String CLASS_OBJECT = "java.lang.Object";
public static final String CLASS_STRING = "java.lang.String";
public static final String CLASS_CLASS = "java.lang.Class";
public static final String CLASS_THROWABLE = "java.lang.Throwable";
public static final String CLASS_ENUM = "java.lang.Enum";
}
+187
View File
@@ -0,0 +1,187 @@
package jadx;
import jadx.utils.exceptions.JadxException;
import jadx.utils.files.InputFile;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterDescription;
import com.beust.jcommander.ParameterException;
public class JadxArgs {
private final static Logger LOG = LoggerFactory.getLogger(JadxArgs.class);
@Parameter(description = "<input files> (.dex, .apk, .jar or .class)", required = true)
protected List<String> files;
@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
protected String outDirName;
@Parameter(names = { "-j", "--threads-count" }, description = "processing threads count")
protected int threadsCount = Runtime.getRuntime().availableProcessors();
@Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)", help = true)
protected boolean fallbackMode = false;
@Parameter(names = { "--not-obfuscated" }, description = "set this flag if code not obfuscated")
protected boolean notObfuscated = false;
@Parameter(names = { "--cfg" }, description = "save methods control flow graph")
protected boolean cfgOutput = false;
@Parameter(names = { "--raw-cfg" }, description = "save methods control flow graph (use raw instructions)")
protected boolean rawCfgOutput = false;
@Parameter(names = { "-v", "--verbose" }, description = "verbose output")
protected boolean verbose = false;
@Parameter(names = { "-h", "--help" }, description = "print this help", help = true)
protected boolean printHelp = false;
private final List<InputFile> input = new ArrayList<InputFile>();
private File outputDir;
public void parse(String[] args) throws JadxException {
try {
new JCommander(this, args);
processArgs();
} catch (ParameterException e) {
System.out.println("Arguments parse error: " + e.getMessage());
System.out.println();
printHelp = true;
}
}
private void processArgs() throws JadxException {
if (printHelp)
return;
if (files == null || files.isEmpty())
throw new JadxException("Please specify at least one input file");
for (String fileName : files) {
File file = new File(fileName);
if (!file.exists())
throw new JadxException("File not found: " + file);
try {
input.add(new InputFile(file));
} catch (IOException e) {
throw new JadxException("File processing error: " + file, e);
}
}
if (input.isEmpty())
throw new JadxException("No files with correct extension (must be '.dex', '.class' or '.jar')");
if (threadsCount <= 0)
throw new JadxException("Threads count must be positive");
if (outDirName == null) {
File file = new File(files.get(0));
String name = file.getName();
int pos = name.lastIndexOf('.');
if (pos != -1)
outDirName = name.substring(0, pos);
else
outDirName = name + "-jadx-out";
LOG.info("output directory: " + outDirName);
}
outputDir = new File(outDirName);
if (!outputDir.exists() && !outputDir.mkdirs())
throw new JadxException("Can't create directory " + outputDir);
if (!outputDir.isDirectory())
throw new JadxException("Output file exists as file " + outputDir);
}
public void printUsage() {
JCommander jc = new JCommander(this);
// print usage in not sorted fields order (by default its sorted by description)
PrintStream out = System.out;
out.println("jadx - dex to java decompiler, version: '" + Consts.JADX_VERSION + "'");
out.println();
out.println("usage: jadx [options] " + jc.getMainParameterDescription());
out.println("options:");
List<ParameterDescription> params = jc.getParameters();
int maxNamesLen = 0;
for (ParameterDescription p : params) {
int len = p.getNames().length();
if (len > maxNamesLen)
maxNamesLen = len;
}
Field[] fields = this.getClass().getDeclaredFields();
for (Field f : fields) {
for (ParameterDescription p : params) {
if (f.getName().equals(p.getParameterized().getName())) {
StringBuilder opt = new StringBuilder();
opt.append(' ').append(p.getNames());
addSpaces(opt, maxNamesLen - opt.length() + 2);
opt.append("- ").append(p.getDescription());
if (p.getParameter().required())
opt.append(" [required]");
out.println(opt.toString());
break;
}
}
}
out.println("Example:");
out.println(" jadx -d out classes.dex");
}
private static void addSpaces(StringBuilder str, int count) {
for (int i = 0; i < count; i++)
str.append(' ');
}
public File getOutDir() {
return outputDir;
}
public int getThreadsCount() {
return threadsCount;
}
public boolean isCFGOutput() {
return cfgOutput;
}
public boolean isRawCFGOutput() {
return rawCfgOutput;
}
public List<InputFile> getInput() {
return input;
}
public boolean isFallbackMode() {
return fallbackMode;
}
public boolean isNotObfuscated() {
return notObfuscated;
}
public boolean isVerbose() {
return verbose;
}
public boolean isPrintHelp() {
return printHelp;
}
}
+143
View File
@@ -0,0 +1,143 @@
package jadx;
import jadx.codegen.CodeGen;
import jadx.dex.info.ClassInfo;
import jadx.dex.nodes.ClassNode;
import jadx.dex.nodes.RootNode;
import jadx.dex.visitors.BlockMakerVisitor;
import jadx.dex.visitors.ClassCheck;
import jadx.dex.visitors.CodeShrinker;
import jadx.dex.visitors.ConstInlinerVisitor;
import jadx.dex.visitors.DotGraphVisitor;
import jadx.dex.visitors.EnumVisitor;
import jadx.dex.visitors.FallbackModeVisitor;
import jadx.dex.visitors.IDexTreeVisitor;
import jadx.dex.visitors.ModVisitor;
import jadx.dex.visitors.regions.CheckRegions;
import jadx.dex.visitors.regions.PostRegionVisitor;
import jadx.dex.visitors.regions.ProcessVariables;
import jadx.dex.visitors.regions.RegionMakerVisitor;
import jadx.dex.visitors.typeresolver.FinishTypeResolver;
import jadx.dex.visitors.typeresolver.TypeResolver;
import jadx.utils.ErrorsCounter;
import jadx.utils.exceptions.JadxException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
private static final Logger LOG = LoggerFactory.getLogger(Main.class);
static {
if (Consts.DEBUG)
LOG.info("debug enabled");
if (Main.class.desiredAssertionStatus())
LOG.info("assertions enabled");
}
public static int run(JadxArgs args) {
int errorCount;
try {
RootNode root = new RootNode(args);
LOG.info("loading ...");
root.load();
LOG.info("processing ...");
root.init();
int threadsCount = args.getThreadsCount();
LOG.debug("processing threads count: {}", threadsCount);
List<IDexTreeVisitor> passes = getPassesList(args);
if (threadsCount == 1) {
for (ClassNode cls : root.getClasses()) {
ProcessClass job = new ProcessClass(cls, passes);
job.run();
}
} else {
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
for (ClassNode cls : root.getClasses()) {
ProcessClass job = new ProcessClass(cls, passes);
executor.execute(job);
}
executor.shutdown();
executor.awaitTermination(100, TimeUnit.DAYS);
}
} catch (Throwable e) {
LOG.error("jadx error:", e);
} finally {
errorCount = ErrorsCounter.getErrorCount();
if (errorCount != 0)
ErrorsCounter.printReport();
// clear resources if we use jadx as a library
ClassInfo.clearCache();
ErrorsCounter.reset();
}
LOG.info("done");
return errorCount;
}
private static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
List<IDexTreeVisitor> passes = new ArrayList<IDexTreeVisitor>();
if (args.isFallbackMode()) {
passes.add(new FallbackModeVisitor());
} else {
passes.add(new BlockMakerVisitor());
passes.add(new TypeResolver());
passes.add(new ConstInlinerVisitor());
passes.add(new FinishTypeResolver());
passes.add(new ClassCheck());
if (args.isRawCFGOutput())
passes.add(new DotGraphVisitor(args.getOutDir(), false, true));
passes.add(new ModVisitor());
passes.add(new EnumVisitor());
if (args.isCFGOutput())
passes.add(new DotGraphVisitor(args.getOutDir(), false));
passes.add(new RegionMakerVisitor());
passes.add(new PostRegionVisitor());
passes.add(new CodeShrinker());
passes.add(new ProcessVariables());
passes.add(new CheckRegions());
if (args.isCFGOutput())
passes.add(new DotGraphVisitor(args.getOutDir(), true));
}
passes.add(new CodeGen(args));
return passes;
}
public static void main(String[] args) {
JadxArgs jadxArgs = new JadxArgs();
try {
jadxArgs.parse(args);
if (jadxArgs.isPrintHelp()) {
jadxArgs.printUsage();
System.exit(0);
}
} catch (JadxException e) {
LOG.error("Error: " + e.getMessage());
System.exit(1);
}
if (jadxArgs.isVerbose()) {
ch.qos.logback.classic.Logger rootLogger =
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(ch.qos.logback.classic.Level.DEBUG);
}
int result = run(jadxArgs);
System.exit(result);
}
}
+37
View File
@@ -0,0 +1,37 @@
package jadx;
import jadx.dex.nodes.ClassNode;
import jadx.dex.visitors.DepthTraverser;
import jadx.dex.visitors.IDexTreeVisitor;
import jadx.utils.exceptions.DecodeException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class ProcessClass implements Runnable {
private final static Logger LOG = LoggerFactory.getLogger(ProcessClass.class);
private final ClassNode cls;
private final List<IDexTreeVisitor> passes;
ProcessClass(ClassNode cls, List<IDexTreeVisitor> passes) {
this.cls = cls;
this.passes = passes;
}
@Override
public void run() {
try {
cls.load();
for (IDexTreeVisitor visitor : passes) {
DepthTraverser.visit(visitor, cls);
}
} catch (DecodeException e) {
LOG.error("Decode exception: " + cls, e);
} finally {
cls.unload();
}
}
}
@@ -0,0 +1,195 @@
package jadx.codegen;
import jadx.Consts;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.IAttributeNode;
import jadx.dex.attributes.annotations.Annotation;
import jadx.dex.attributes.annotations.AnnotationsList;
import jadx.dex.attributes.annotations.MethodParameters;
import jadx.dex.info.FieldInfo;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.nodes.ClassNode;
import jadx.dex.nodes.FieldNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.StringUtils;
import jadx.utils.Utils;
import jadx.utils.exceptions.JadxRuntimeException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
public class AnnotationGen {
private final ClassNode cls;
private final ClassGen classGen;
public AnnotationGen(ClassNode cls, ClassGen classGen) {
this.cls = cls;
this.classGen = classGen;
}
public void addForClass(CodeWriter code) {
add(cls, code);
}
public void addForMethod(CodeWriter code, MethodNode mth) {
add(mth, code);
}
public void addForField(CodeWriter code, FieldNode field) {
add(field, code);
}
public void addForParameter(CodeWriter code, MethodParameters paramsAnnotations, int n) {
AnnotationsList aList = paramsAnnotations.getParamList().get(n);
if (aList == null || aList.size() == 0)
return;
for (Annotation a : aList.getAll()) {
code.add(formatAnnotation(a));
code.add(' ');
}
}
private void add(IAttributeNode node, CodeWriter code) {
AnnotationsList aList = (AnnotationsList) node.getAttributes().get(AttributeType.ANNOTATION_LIST);
if (aList == null || aList.size() == 0)
return;
for (Annotation a : aList.getAll()) {
String aCls = a.getAnnotationClass();
if (aCls.startsWith("dalvik.annotation.")) {
// skip
if (aCls.equals("dalvik.annotation.Signature"))
code.startLine("// signature: "
+ Utils.mergeSignature((List<String>) a.getValues().get("value")));
else if (Consts.DEBUG)
code.startLine("// " + a);
} else {
code.startLine();
code.add(formatAnnotation(a));
}
}
}
private CodeWriter formatAnnotation(Annotation a) {
CodeWriter code = new CodeWriter();
code.add('@');
code.add(classGen.useClass(a.getType()));
Map<String, Object> vl = a.getValues();
if (vl.size() != 0) {
code.add('(');
if (vl.size() == 1 && vl.containsKey("value")) {
code.add(encValueToString(vl.get("value")));
} else {
for (Iterator<Entry<String, Object>> it = vl.entrySet().iterator(); it.hasNext();) {
Entry<String, Object> e = it.next();
code.add(e.getKey());
code.add(" = ");
code.add(encValueToString(e.getValue()));
if (it.hasNext())
code.add(", ");
}
}
code.add(')');
}
return code;
}
@SuppressWarnings("unchecked")
public void addThrows(MethodNode mth, CodeWriter code) {
AnnotationsList anList = (AnnotationsList) mth.getAttributes().get(AttributeType.ANNOTATION_LIST);
if (anList == null || anList.size() == 0)
return;
Annotation an = anList.get("dalvik.annotation.Throws");
if (an != null) {
Object exs = an.getValues().get("value");
code.add(" throws ");
for (Iterator<ArgType> it = ((List<ArgType>) exs).iterator(); it.hasNext();) {
ArgType ex = it.next();
code.add(TypeGen.translate(classGen, ex));
if (it.hasNext())
code.add(", ");
}
}
}
public Object getAnnotationDefaultValue(String name) {
AnnotationsList anList = (AnnotationsList) cls.getAttributes().get(AttributeType.ANNOTATION_LIST);
if (anList == null || anList.size() == 0)
return null;
Annotation an = anList.get("dalvik.annotation.AnnotationDefault");
if (an != null) {
Annotation defAnnotation = (Annotation) an.getValues().get("value");
return defAnnotation.getValues().get(name);
}
return null;
}
// TODO: refactor this boilerplate code
@SuppressWarnings("unchecked")
public String encValueToString(Object val) {
if (val == null)
return "null";
if (val instanceof String)
return StringUtils.unescapeString((String) val);
if (val instanceof Integer)
return TypeGen.formatInteger((Integer) val);
if (val instanceof Character)
return StringUtils.unescapeChar((Character) val);
if (val instanceof Boolean)
return Boolean.TRUE.equals(val) ? "true" : "false";
if (val instanceof Float)
return TypeGen.formatFloat((Float) val);
if (val instanceof Double)
return TypeGen.formatDouble((Double) val);
if (val instanceof Long)
return TypeGen.formatLong((Long) val);
if (val instanceof Short)
return TypeGen.formatShort((Short) val);
if (val instanceof Byte)
return TypeGen.formatByte((Byte) val);
if (val instanceof ArgType)
return TypeGen.translate(classGen, (ArgType) val) + ".class";
if (val instanceof FieldInfo) {
// must be a static field
FieldInfo field = (FieldInfo) val;
// FIXME: !!code from InsnGen.sfield
String thisClass = cls.getFullName();
if (field.getDeclClass().getFullName().equals(thisClass)) {
return field.getName();
} else {
return classGen.useClass(field.getDeclClass()) + '.' + field.getName();
}
}
if (val instanceof List) {
StringBuilder str = new StringBuilder();
str.append('{');
List<Object> list = (List<Object>) val;
for (Iterator<Object> it = list.iterator(); it.hasNext();) {
Object obj = it.next();
str.append(encValueToString(obj));
if (it.hasNext())
str.append(", ");
}
str.append('}');
return str.toString();
}
if (val instanceof Annotation) {
return formatAnnotation((Annotation) val).toString();
}
// TODO: also can be method values
throw new JadxRuntimeException("Can't decode value: " + val + " (" + val.getClass() + ")");
}
}
+310
View File
@@ -0,0 +1,310 @@
package jadx.codegen;
import jadx.Consts;
import jadx.dex.attributes.AttributeFlag;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.EnumClassAttr;
import jadx.dex.attributes.EnumClassAttr.EnumField;
import jadx.dex.info.AccessInfo;
import jadx.dex.info.ClassInfo;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.nodes.ClassNode;
import jadx.dex.nodes.FieldNode;
import jadx.dex.nodes.MethodNode;
import jadx.dex.nodes.parser.FieldValueAttr;
import jadx.utils.ErrorsCounter;
import jadx.utils.Utils;
import jadx.utils.exceptions.CodegenException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.rop.code.AccessFlags;
public class ClassGen {
private final static Logger LOG = LoggerFactory.getLogger(ClassGen.class);
private final ClassNode cls;
private final ClassGen parentGen;
private final AnnotationGen annotationGen;
private final boolean fallback;
private final Set<ClassInfo> imports = new HashSet<ClassInfo>();
public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean fallback) {
this.cls = cls;
this.parentGen = parentClsGen;
this.fallback = fallback;
this.annotationGen = new AnnotationGen(cls, this);
}
public ClassNode getClassNode() {
return cls;
}
public CodeWriter makeClass() throws CodegenException {
CodeWriter clsBody = new CodeWriter();
addClassCode(clsBody);
CodeWriter clsCode = new CodeWriter();
if (!"".equals(cls.getPackage())) {
clsCode.add("package ").add(cls.getPackage()).add(";");
clsCode.endl();
}
if (imports.size() != 0) {
List<String> sortImports = new ArrayList<String>();
for (ClassInfo ic : imports)
sortImports.add(ic.getFullName());
Collections.sort(sortImports);
for (String imp : sortImports) {
clsCode.startLine("import ").add(imp).add(";");
}
clsCode.endl();
sortImports.clear();
imports.clear();
}
clsCode.add(clsBody);
return clsCode;
}
public void addClassCode(CodeWriter code) throws CodegenException {
if (cls.getAttributes().contains(AttributeFlag.DONT_GENERATE))
return;
makeClassDeclaration(code);
makeClassBody(code);
code.endl();
}
public void makeClassDeclaration(CodeWriter clsCode) {
AccessInfo af = cls.getAccessFlags();
if (af.isInterface()) {
af = af.remove(AccessFlags.ACC_ABSTRACT);
} else if (af.isEnum()) {
af = af.remove(AccessFlags.ACC_FINAL).remove(AccessFlags.ACC_ABSTRACT);
}
annotationGen.addForClass(clsCode);
clsCode.startLine(af.makeString());
if (af.isInterface()) {
if (af.isAnnotation())
clsCode.add('@');
clsCode.add("interface ");
} else if (af.isEnum()) {
clsCode.add("enum ");
} else {
clsCode.add("class ");
}
clsCode.add(cls.getShortName());
ClassInfo sup = cls.getSuperClass();
if (sup != null
&& !sup.getFullName().equals(Consts.CLASS_OBJECT)
&& !sup.getFullName().equals(Consts.CLASS_ENUM)) {
clsCode.add(" extends ").add(useClass(sup));
}
if (cls.getInterfaces().size() > 0 && !af.isAnnotation()) {
if (cls.getAccessFlags().isInterface())
clsCode.add(" extends ");
else
clsCode.add(" implements ");
for (Iterator<ClassInfo> it = cls.getInterfaces().iterator(); it.hasNext();) {
ClassInfo interf = it.next();
clsCode.add(useClass(interf));
if (it.hasNext())
clsCode.add(", ");
}
}
}
public void makeClassBody(CodeWriter clsCode) throws CodegenException {
clsCode.add(" {");
CodeWriter mthsCode = makeMethods(clsCode, cls.getMethods());
clsCode.add(makeFields(clsCode, cls, cls.getFields()));
// insert inner classes code
if (cls.getInnerClasses().size() != 0) {
clsCode.add(makeInnerClasses(cls, clsCode.getIndent()));
}
clsCode.add(mthsCode);
clsCode.startLine("}");
}
private CodeWriter makeInnerClasses(ClassNode cls2, int indent) throws CodegenException {
CodeWriter innerClsCode = new CodeWriter(indent + 1);
for (ClassNode inCls : cls.getInnerClasses()) {
if (inCls.isAnonymous())
continue;
ClassGen inClGen = new ClassGen(inCls, parentGen == null ? this : parentGen, fallback);
inClGen.addClassCode(innerClsCode);
imports.addAll(inClGen.getImports());
}
return innerClsCode;
}
private CodeWriter makeMethods(CodeWriter clsCode, List<MethodNode> mthList) throws CodegenException {
CodeWriter code = new CodeWriter(clsCode.getIndent() + 1);
for (Iterator<MethodNode> it = mthList.iterator(); it.hasNext();) {
MethodNode mth = it.next();
try {
if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) {
MethodGen mthGen = new MethodGen(this, mth);
mthGen.addDefinition(code);
if (cls.getAccessFlags().isAnnotation()) {
Object def = annotationGen.getAnnotationDefaultValue(mth.getName());
if (def != null) {
String v = annotationGen.encValueToString(def);
code.add(" default ").add(v);
}
}
code.add(";");
} else {
if (mth.isNoCode())
continue;
MethodGen mthGen = new MethodGen(this, mth);
mthGen.addDefinition(code);
code.add(" {");
code.add(mthGen.makeInstructions(code.getIndent()));
code.startLine("}");
}
} catch (Throwable e) {
String msg = ErrorsCounter.methodError(mth, "Method generation error", e);
code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + "*/");
}
if (it.hasNext())
code.endl();
}
return code;
}
private CodeWriter makeFields(CodeWriter clsCode, ClassNode cls, List<FieldNode> fields) throws CodegenException {
CodeWriter code = new CodeWriter(clsCode.getIndent() + 1);
EnumClassAttr enumFields = (EnumClassAttr) cls.getAttributes().get(AttributeType.ENUM_CLASS);
if (enumFields != null) {
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
InsnGen igen = new InsnGen(mthGen, enumFields.getStaticMethod(), false);
for (Iterator<EnumField> it = enumFields.getFields().iterator(); it.hasNext();) {
EnumField f = it.next();
code.startLine(f.getName());
if (f.getArgs().size() != 0) {
code.add('(');
for (Iterator<InsnArg> aIt = f.getArgs().iterator(); aIt.hasNext();) {
InsnArg arg = aIt.next();
code.add(igen.arg(arg));
if (aIt.hasNext())
code.add(", ");
}
code.add(')');
}
if (f.getCls() != null) {
new ClassGen(f.getCls(), this, fallback).makeClassBody(code);
}
if (it.hasNext())
code.add(',');
}
if (enumFields.getFields().isEmpty())
code.startLine();
code.add(';');
code.endl();
}
for (FieldNode f : fields) {
annotationGen.addForField(code, f);
code.startLine(f.getAccessFlags().makeString());
code.add(TypeGen.translate(this, f.getType()));
code.add(" ");
code.add(f.getName());
FieldValueAttr fv = (FieldValueAttr) f.getAttributes().get(AttributeType.FIELD_VALUE);
if (fv != null) {
code.add(" = ");
if (fv.getValue() == null) {
code.add(TypeGen.literalToString(0, f.getType()));
} else {
code.add(annotationGen.encValueToString(fv.getValue()));
}
}
code.add(";");
}
if (fields.size() != 0)
code.endl();
return code;
}
public String useClass(ArgType clsType) {
return useClass(ClassInfo.fromType(cls.dex(), clsType));
}
public String useClass(ClassInfo classInfo) {
if (parentGen != null)
return parentGen.useClass(classInfo);
String clsStr = classInfo.getFullName();
if (fallback)
return clsStr;
String shortName = classInfo.getShortName();
if (classInfo.getPackage().equals("java.lang") && classInfo.getParentClass() == null) {
return shortName;
} else {
// don't add import if this class inner for current class
if (isInner(classInfo, cls.getClassInfo()))
return shortName;
for (ClassInfo cls : imports) {
if (!cls.equals(classInfo)) {
if (cls.getShortName().equals(shortName))
return clsStr;
}
}
imports.add(classInfo);
return shortName;
}
}
private boolean isInner(ClassInfo inner, ClassInfo parent) {
if (inner.isInner()) {
ClassInfo p = inner.getParentClass();
return p.equals(parent) || isInner(p, parent);
}
return false;
}
public Set<ClassInfo> getImports() {
return imports;
}
public ClassGen getParentGen() {
return parentGen;
}
public AnnotationGen getAnnotationGen() {
return annotationGen;
}
public boolean isFallbackMode() {
return fallback;
}
}
+35
View File
@@ -0,0 +1,35 @@
package jadx.codegen;
import jadx.JadxArgs;
import jadx.dex.nodes.ClassNode;
import jadx.dex.visitors.AbstractVisitor;
import jadx.utils.exceptions.CodegenException;
import java.io.File;
public class CodeGen extends AbstractVisitor {
private final File dir;
private final JadxArgs args;
public CodeGen(JadxArgs args) {
this.args = args;
this.dir = args.getOutDir();
}
@Override
public boolean visit(ClassNode cls) throws CodegenException {
ClassGen clsGen = new ClassGen(cls, null, isFallbackMode());
CodeWriter clsCode = clsGen.makeClass();
String fileName = cls.getClassInfo().getFullPath() + ".java";
if (isFallbackMode())
fileName += ".jadx";
clsCode.save(dir, fileName);
return false;
}
public boolean isFallbackMode() {
return args.isFallbackMode();
}
}
+181
View File
@@ -0,0 +1,181 @@
package jadx.codegen;
import jadx.utils.exceptions.JadxRuntimeException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CodeWriter {
private final static Logger LOG = LoggerFactory.getLogger(CodeWriter.class);
private static final int MAX_FILENAME_LENGTH = 128;
public final static String NL = System.getProperty("line.separator");
public final static String INDENT = "\t";
private StringBuilder buf = new StringBuilder();
private String indentStr;
private int indent;
public CodeWriter() {
this.indent = 0;
this.indentStr = "";
}
public CodeWriter(int indent) {
this.indent = indent;
updateIndent();
}
public CodeWriter startLine(String str) {
buf.append(NL);
buf.append(indentStr);
buf.append(str);
return this;
}
public CodeWriter startLine(int ind, String str) {
buf.append(NL);
buf.append(indentStr);
for (int i = 0; i < ind; i++)
buf.append(INDENT);
buf.append(str);
return this;
}
public CodeWriter startLine() {
buf.append(NL);
buf.append(indentStr);
return this;
}
public CodeWriter add(String str) {
buf.append(str);
return this;
}
public CodeWriter add(char c) {
buf.append(c);
return this;
}
public CodeWriter add(CodeWriter mthsCode) {
buf.append(mthsCode.toString());
return this;
}
public CodeWriter endl() {
buf.append(NL);
return this;
}
private static final String[] indentCache = new String[] {
"",
INDENT,
INDENT + INDENT,
INDENT + INDENT + INDENT,
INDENT + INDENT + INDENT + INDENT,
INDENT + INDENT + INDENT + INDENT + INDENT,
};
private void updateIndent() {
if (indent < 6) {
this.indentStr = indentCache[indent];
} else {
StringBuilder s = new StringBuilder(indent * INDENT.length());
for (int i = 0; i < indent; i++) {
s.append(INDENT);
}
this.indentStr = s.toString();
}
}
public int getIndent() {
return indent;
}
public void incIndent() {
incIndent(1);
}
public void decIndent() {
decIndent(1);
}
public void incIndent(int c) {
this.indent += c;
updateIndent();
}
public void decIndent(int c) {
this.indent -= c;
if (this.indent < 0) {
LOG.warn("Indent < 0");
this.indent = 0;
}
updateIndent();
}
private static String removeFirstEmptyLine(String str) {
if (str.startsWith(NL)) {
return str.substring(NL.length());
} else {
return str;
}
}
@Override
public String toString() {
return buf.toString();
}
public void save(File dir, String subDir, String fileName) {
save(dir, new File(subDir, fileName).getPath());
}
public void save(File dir, String fileName) {
save(new File(dir, fileName));
}
public void save(File file) {
String name = file.getName();
if (name.length() > MAX_FILENAME_LENGTH) {
int dotIndex = name.indexOf('.');
int cutAt = MAX_FILENAME_LENGTH - name.length() + dotIndex - 1;
if (cutAt <= 0)
name = name.substring(0, MAX_FILENAME_LENGTH - 1);
else
name = name.substring(0, cutAt) + name.substring(dotIndex);
file = new File(file.getParentFile(), name);
}
PrintWriter out = null;
try {
makeDirsForFile(file);
out = new PrintWriter(file);
String code = buf.toString();
code = removeFirstEmptyLine(code);
out.print(code);
} catch (FileNotFoundException e) {
LOG.error("Save file error", e);
} finally {
if (out != null)
out.close();
buf = null;
}
}
private void makeDirsForFile(File file) {
File dir = file.getParentFile();
if (!dir.exists()) {
// if directory already created in other thread mkdirs will return false,
// so check dir existence again
if (!dir.mkdirs() && !dir.exists())
throw new JadxRuntimeException("Can't create directory " + dir);
}
}
}
+533
View File
@@ -0,0 +1,533 @@
package jadx.codegen;
import jadx.dex.attributes.AttributeType;
import jadx.dex.info.ClassInfo;
import jadx.dex.info.FieldInfo;
import jadx.dex.info.MethodInfo;
import jadx.dex.instructions.ArithNode;
import jadx.dex.instructions.ArithOp;
import jadx.dex.instructions.FillArrayOp;
import jadx.dex.instructions.GotoNode;
import jadx.dex.instructions.IfNode;
import jadx.dex.instructions.IndexInsnNode;
import jadx.dex.instructions.InvokeNode;
import jadx.dex.instructions.InvokeType;
import jadx.dex.instructions.SwitchNode;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.InsnWrapArg;
import jadx.dex.instructions.args.LiteralArg;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.instructions.mods.ConstructorInsn;
import jadx.dex.nodes.ClassNode;
import jadx.dex.nodes.FieldNode;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.dex.nodes.RootNode;
import jadx.utils.StringUtils;
import jadx.utils.exceptions.CodegenException;
import java.util.EnumSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class InsnGen {
private final static Logger LOG = LoggerFactory.getLogger(InsnGen.class);
protected final MethodGen mgen;
protected final MethodNode mth;
protected final RootNode root;
private final boolean fallback;
public enum InsnGenState {
SKIP,
NO_SEMICOLON,
NO_RESULT,
BODY_ONLY,
INC_INDENT,
DEC_INDENT,
}
public InsnGen(MethodGen mgen, MethodNode mth, boolean fallback) {
this.mgen = mgen;
this.mth = mth;
this.root = mth.dex().root();
this.fallback = fallback;
}
private boolean isFallback() {
return fallback;
}
public String arg(InsnNode insn, int arg) throws CodegenException {
return arg(insn.getArg(arg));
}
public String arg(InsnArg arg) throws CodegenException {
if (arg.isRegister()) {
return mgen.makeArgName((RegisterArg) arg);
} else if (arg.isLiteral()) {
return lit((LiteralArg) arg);
} else {
CodeWriter code = new CodeWriter();
makeInsn(((InsnWrapArg) arg).getWrapInsn(), code, true);
return code.toString();
}
}
public String assignVar(InsnNode insn) {
// return mgen.assignArg(arg);
try {
RegisterArg arg = insn.getResult();
if (insn.getAttributes().contains(AttributeType.DECLARE_VARIABLE)) {
return declareVar(arg);
} else {
return arg(arg);
}
} catch (CodegenException e) {
LOG.error("Assign var codegen error", e);
}
return "<error>";
}
public String declareVar(RegisterArg arg) throws CodegenException {
return TypeGen.translate(mgen.getClassGen(), arg.getType()) + " " + arg(arg);
}
private String lit(LiteralArg arg) {
return TypeGen.literalToString(arg.getLiteral(), arg.getType());
}
private String ifield(IndexInsnNode insn, int reg) throws CodegenException {
FieldInfo field = (FieldInfo) insn.getIndex();
return arg(insn.getArg(reg)) + '.' + field.getName();
}
private String sfield(IndexInsnNode insn) {
FieldInfo field = (FieldInfo) insn.getIndex();
String thisClass = mth.getParentClass().getFullName();
if (field.getDeclClass().getFullName().equals(thisClass)) {
return field.getName();
} else {
return useClass(field.getDeclClass()) + '.' + field.getName();
}
}
private void fieldPut(IndexInsnNode insn) {
FieldInfo field = (FieldInfo) insn.getIndex();
String thisClass = mth.getParentClass().getFullName();
if (field.getDeclClass().getFullName().equals(thisClass)) {
// if we generate this field - don't init if its final and used
FieldNode fn = mth.getParentClass().searchField(field);
if (fn != null && fn.getAccessFlags().isFinal())
fn.getAttributes().remove(AttributeType.FIELD_VALUE);
}
}
public String useClass(ClassInfo cls) {
return mgen.getClassGen().useClass(cls);
}
private String useType(ArgType type) {
if (type.isObject())
return mgen.getClassGen().useClass(type);
else
return translate(type);
}
private String translate(ArgType type) {
return TypeGen.translate(mgen.getClassGen(), type);
}
public void makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
makeInsn(insn, code, false);
}
private void makeInsn(InsnNode insn, CodeWriter code, boolean bodyOnly) throws CodegenException {
try {
// code.startLine("/* " + insn + "*/");
EnumSet<InsnGenState> state = EnumSet.noneOf(InsnGenState.class);
if (bodyOnly) {
state.add(InsnGenState.BODY_ONLY);
makeInsnBody(code, insn, state);
} else {
CodeWriter body = new CodeWriter(code.getIndent());
makeInsnBody(body, insn, state);
if (state.contains(InsnGenState.SKIP))
return;
if (state.contains(InsnGenState.DEC_INDENT))
code.decIndent();
if (insn.getResult() != null && !state.contains(InsnGenState.NO_RESULT))
code.startLine(assignVar(insn)).add(" = ");
else
code.startLine();
code.add(body);
if (!state.contains(InsnGenState.NO_SEMICOLON))
code.add(';');
if (state.contains(InsnGenState.INC_INDENT))
code.incIndent();
}
} catch (Throwable th) {
throw new CodegenException(mth, "Error generate insn: " + insn, th);
}
}
private void makeInsnBody(CodeWriter code, InsnNode insn, EnumSet<InsnGenState> state) throws CodegenException {
switch (insn.getType()) {
case CONST:
if (insn.getArgsCount() == 0) {
// const in 'index' - string or class
Object ind = ((IndexInsnNode) insn).getIndex();
if (ind instanceof String)
code.add(StringUtils.unescapeString(ind.toString()));
else if (ind instanceof ArgType)
code.add(useType((ArgType) ind)).add(".class");
} else {
LiteralArg arg = (LiteralArg) insn.getArg(0);
code.add(lit(arg));
}
break;
case MOVE:
code.add(arg(insn.getArg(0)));
break;
case CHECK_CAST:
case CAST:
code.add("((");
code.add(translate(((ArgType) ((IndexInsnNode) insn).getIndex())));
code.add(") (");
code.add(arg(insn.getArg(0)));
code.add("))");
break;
case ARITH:
makeArith((ArithNode) insn, code, state);
break;
case NEG:
String base = "-" + arg(insn.getArg(0));
if (state.contains(InsnGenState.BODY_ONLY))
code.add('(').add(base).add(')');
else
code.add(base);
break;
case RETURN:
if (insn.getArgsCount() != 0)
code.add("return " + arg(insn.getArg(0)));
else
code.add("return");
break;
case BREAK:
code.add("break");
break;
case CONTINUE:
code.add("continue");
break;
case THROW:
code.add("throw " + arg(insn.getArg(0)));
break;
case CMP_L:
case CMP_G:
code.add(String.format("(%1$s > %2$s ? 1 : (%1$s == %2$s ? 0 : -1))", arg(insn, 0), arg(insn, 1)));
break;
case INSTANCE_OF:
code.add("(").add(arg(insn, 0)).add(" instanceof ")
.add(useType((ArgType) ((IndexInsnNode) insn).getIndex())).add(")");
break;
case CONSTRUCTOR:
makeConstructor((ConstructorInsn) insn, code, state);
break;
case INVOKE:
makeInvoke((InvokeNode) insn, code);
break;
case NEW_ARRAY: {
ArgType arrayType = insn.getResult().getType();
int dim = arrayType.getArrayDimension();
code.add("new ").add(useType(arrayType.getArrayRootElement())).add('[').add(arg(insn, 0)).add(']');
for (int i = 0; i < dim - 1; i++)
code.add("[]");
break;
}
case ARRAY_LENGTH:
code.add(arg(insn, 0)).add(".length");
break;
case FILL_ARRAY:
fillArray((FillArrayOp) insn, code);
break;
case FILLED_NEW_ARRAY:
filledNewArray(insn, code);
break;
case AGET:
code.add(arg(insn.getArg(0))).add('[').add(arg(insn.getArg(1))).add(']');
break;
case APUT:
code.add(arg(insn, 0)).add('[').add(arg(insn, 1)).add("] = ").add(arg(insn, 2));
break;
case IGET:
code.add(ifield((IndexInsnNode) insn, 0));
break;
case IPUT:
code.add(ifield((IndexInsnNode) insn, 1) + " = " + arg(insn.getArg(0)));
break;
case SGET:
code.add(sfield((IndexInsnNode) insn));
break;
case SPUT:
IndexInsnNode node = (IndexInsnNode) insn;
fieldPut(node);
code.add(sfield(node) + " = " + arg(node.getArg(0)));
break;
case MONITOR_ENTER:
if (isFallback()) {
code.add("monitor-enter(").add(arg(insn.getArg(0))).add(")");
} else {
state.add(InsnGenState.SKIP);
}
break;
case MONITOR_EXIT:
if (isFallback()) {
code.add("monitor-exit(").add(arg(insn.getArg(0))).add(")");
} else {
state.add(InsnGenState.SKIP);
}
break;
case MOVE_EXCEPTION:
if (isFallback()) {
code.add("move-exception");
} else {
// don't have body
if (state.contains(InsnGenState.BODY_ONLY))
code.add(arg(insn.getResult()));
else
state.add(InsnGenState.SKIP);
}
break;
case TERNARY:
break;
/* fallback mode instructions */
case NOP:
state.add(InsnGenState.SKIP);
break;
case IF:
assert isFallback();
IfNode ifInsn = (IfNode) insn;
String cond = arg(insn.getArg(0)) + " " + ifInsn.getOp().getSymbol() + " "
+ (ifInsn.isZeroCmp() ? "0" : arg(insn.getArg(1)));
code.add("if (").add(cond).add(") goto ").add(MethodGen.getLabelName(ifInsn.getTarget()));
break;
case GOTO:
assert isFallback();
code.add("goto ").add(MethodGen.getLabelName(((GotoNode) insn).getTarget()));
break;
case SWITCH:
assert isFallback();
SwitchNode sw = (SwitchNode) insn;
code.add("switch(").add(arg(insn, 0)).add(") {");
code.incIndent();
for (int i = 0; i < sw.getCasesCount(); i++) {
code.startLine("case " + sw.getKeys()[i]
+ ": goto " + MethodGen.getLabelName(sw.getTargets()[i]) + ";");
}
code.startLine("default: goto " + MethodGen.getLabelName(sw.getDefaultCaseOffset()) + ";");
code.decIndent();
code.startLine("}");
state.add(InsnGenState.NO_SEMICOLON);
break;
case NEW_INSTANCE:
// only fallback - make new instance in constructor invoke
assert isFallback();
code.add("new " + insn.getResult().getType());
break;
default:
throw new CodegenException(mth, "Unknown instruction: " + insn.getType());
}
}
private void filledNewArray(InsnNode insn, CodeWriter code) throws CodegenException {
int c = insn.getArgsCount();
code.add("new ").add(useType(insn.getResult().getType()));
code.add("{");
for (int i = 0; i < c; i++) {
code.add(arg(insn, i));
if (i + 1 < c)
code.add(", ");
}
code.add("}");
}
private void fillArray(FillArrayOp insn, CodeWriter code) throws CodegenException {
ArgType elType = insn.getResult().getType().getArrayElement();
if (elType.getPrimitiveType() == null) {
elType = elType.selectFirst();
}
StringBuilder str = new StringBuilder();
switch (elType.getPrimitiveType()) {
case BOOLEAN:
case BYTE:
byte[] array = (byte[]) insn.getData();
for (byte b : array) {
str.append(TypeGen.literalToString(b, elType));
str.append(", ");
}
break;
case SHORT:
case CHAR:
short[] sarray = (short[]) insn.getData();
for (short b : sarray) {
str.append(TypeGen.literalToString(b, elType));
str.append(", ");
}
break;
case INT:
case FLOAT:
int[] iarray = (int[]) insn.getData();
for (int b : iarray) {
str.append(TypeGen.literalToString(b, elType));
str.append(", ");
}
break;
case LONG:
case DOUBLE:
long[] larray = (long[]) insn.getData();
for (long b : larray) {
str.append(TypeGen.literalToString(b, elType));
str.append(", ");
}
break;
default:
throw new CodegenException(mth, "Unknown type: " + elType);
}
int len = str.length();
str.delete(len - 2, len);
code.add("new ").add(translate(elType)).add("[] { ").add(str.toString()).add(" }");
}
private void makeConstructor(ConstructorInsn insn, CodeWriter code, EnumSet<InsnGenState> state)
throws CodegenException {
ClassNode cls = root.resolveClass(insn.getClassType());
if (cls != null && cls.isAnonymous()) {
// anonymous class construction
ClassInfo parent;
if (cls.getSuperClass() != null
&& !cls.getSuperClass().getFullName().equals("java.lang.Object"))
parent = cls.getSuperClass();
else
parent = cls.getInterfaces().get(0);
code.add("new ").add(useClass(parent)).add("()");
code.incIndent(2);
new ClassGen(cls, mgen.getClassGen().getParentGen(), fallback).makeClassBody(code);
code.decIndent(2);
} else if (insn.isSuper()) {
code.add("super");
addArgs(code, insn, 0);
} else if (insn.isThis()) {
code.add("this");
addArgs(code, insn, 0);
} else if (insn.isSelf()) {
// skip
state.add(InsnGenState.SKIP);
} else {
code.add("new ").add(useClass(insn.getClassType()));
addArgs(code, insn, 0);
}
}
private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException {
MethodInfo callMth = insn.getCallMth();
int k = 0;
InvokeType type = insn.getInvokeType();
switch (type) {
case DIRECT:
case VIRTUAL:
case INTERFACE:
code.add(arg(insn.getArg(0))).add('.');
k++;
break;
case SUPER:
// use 'super' instead 'this' in 0 arg
code.add("super").add('.');
k++;
break;
case STATIC:
ClassInfo insnCls = mth.getParentClass().getClassInfo();
if (!insnCls.equals(callMth.getDeclClass()))
code.add(useClass(callMth.getDeclClass())).add('.');
break;
}
code.add(callMth.getName());
addArgs(code, insn, k);
}
private void addArgs(CodeWriter code, InsnNode insn, int k) throws CodegenException {
code.add('(');
for (int i = k; i < insn.getArgsCount(); i++) {
code.add(arg(insn.getArg(i)));
if (i < insn.getArgsCount() - 1)
code.add(", ");
}
code.add(")");
}
private void makeArith(ArithNode insn, CodeWriter code, EnumSet<InsnGenState> state) throws CodegenException {
ArithOp op = insn.getOp();
String v1 = arg(insn.getArg(0));
if (op == ArithOp.INC || op == ArithOp.DEC) {
code.add(v1 + op.getSymbol());
} else {
String res = arg(insn.getResult());
String v2 = arg(insn.getArg(1));
if (res.equals(v1) && !state.contains(InsnGenState.BODY_ONLY)) {
code.add(assignVar(insn) + " " + op.getSymbol() + "= " + v2);
state.add(InsnGenState.NO_RESULT);
} else {
if (state.contains(InsnGenState.BODY_ONLY))
// wrap insn in brackets for save correct operation order
// TODO don't wrap first insn in wrapped stack
code.add("(" + v1 + " " + op.getSymbol() + " " + v2 + ")");
else
code.add(v1 + " " + op.getSymbol() + " " + v2);
}
}
}
}
+305
View File
@@ -0,0 +1,305 @@
package jadx.codegen;
import jadx.Consts;
import jadx.dex.attributes.AttributeFlag;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.AttributesList;
import jadx.dex.attributes.JadxErrorAttr;
import jadx.dex.attributes.annotations.MethodParameters;
import jadx.dex.info.AccessInfo;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.dex.trycatch.CatchAttr;
import jadx.dex.visitors.DepthTraverser;
import jadx.dex.visitors.FallbackModeVisitor;
import jadx.utils.ErrorsCounter;
import jadx.utils.InsnUtils;
import jadx.utils.Utils;
import jadx.utils.exceptions.CodegenException;
import jadx.utils.exceptions.DecodeException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
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 com.android.dx.rop.code.AccessFlags;
public class MethodGen {
private final static Logger LOG = LoggerFactory.getLogger(MethodGen.class);
private final MethodNode mth;
private final Set<String> mthArgsDecls;
private final Map<String, ArgType> varDecls = new HashMap<String, ArgType>();
private final ClassGen classGen;
private final boolean fallback;
private final AnnotationGen annotationGen;
public MethodGen(ClassGen classGen, MethodNode mth) {
this.mth = mth;
this.classGen = classGen;
this.fallback = classGen.isFallbackMode();
this.annotationGen = classGen.getAnnotationGen();
List<RegisterArg> args = mth.getArguments(true);
mthArgsDecls = new HashSet<String>(args.size());
for (RegisterArg arg : args) {
mthArgsDecls.add(makeArgName(arg));
}
}
public ClassGen getClassGen() {
return classGen;
}
public void addDefinition(CodeWriter code) {
if (mth.getMethodInfo().isClassInit()) {
code.startLine("static");
} else {
if (mth.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE)) {
code.startLine("// FIXME: Jadx generate inconsistent code");
// ErrorsCounter.methodError(mth, "Inconsistent code");
}
annotationGen.addForMethod(code, mth);
AccessInfo ai = mth.getAccessFlags();
// don't add 'abstract' to methods in interface
if (mth.getParentClass().getAccessFlags().isInterface()) {
ai = ai.remove(AccessFlags.ACC_ABSTRACT);
}
code.startLine(ai.makeString());
if (mth.getAccessFlags().isConstructor()) {
code.add(classGen.getClassNode().getShortName()); // constructor
} else {
code.add(TypeGen.translate(classGen, mth.getMethodInfo().getReturnType()));
code.add(" ");
code.add(mth.getName());
}
code.add("(");
mth.resetArgsTypes();
List<RegisterArg> args = mth.getArguments(false);
if (mth.getMethodInfo().isConstructor()
&& mth.getParentClass().getAttributes().contains(AttributeType.ENUM_CLASS)) {
if (args.size() == 2)
args.clear();
else if (args.size() > 2)
args = args.subList(2, args.size());
else
LOG.warn(ErrorsCounter.formatErrorMsg(mth,
"Incorrect number of args for enum constructor: " + args.size()
+ " (expected >= 2)"));
}
code.add(makeArguments(args));
code.add(")");
annotationGen.addThrows(mth, code);
}
}
public CodeWriter makeArguments(List<RegisterArg> args) {
CodeWriter argsCode = new CodeWriter();
MethodParameters paramsAnnotation =
(MethodParameters) mth.getAttributes().get(AttributeType.ANNOTATION_MTH_PARAMETERS);
int i = 0;
for (Iterator<RegisterArg> it = args.iterator(); it.hasNext();) {
RegisterArg arg = it.next();
// add argument annotation
if (paramsAnnotation != null)
annotationGen.addForParameter(argsCode, paramsAnnotation, i);
if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) {
// change last array argument to varargs
ArgType type = arg.getType();
if (type.isArray()) {
ArgType elType = type.getArrayElement();
argsCode.add(TypeGen.translate(classGen, elType));
argsCode.add(" ...");
} else {
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Last argument in varargs method not array"));
argsCode.add(TypeGen.translate(classGen, arg.getType()));
}
} else {
argsCode.add(TypeGen.translate(classGen, arg.getType()));
}
argsCode.add(" ");
argsCode.add(makeArgName(arg));
i++;
if (it.hasNext())
argsCode.add(", ");
}
return argsCode;
}
/**
* Make variable name for register,
* Name contains register number and
* variable type or name (if debug info available)
*/
public String makeArgName(RegisterArg arg) {
String name = arg.getTypedVar().getName();
String base = "r" + arg.getRegNum();
if (fallback) {
if (name != null)
return base + "_" + name;
else
return base;
} else {
if (name != null) {
if (name.equals("this"))
return name;
else if (Consts.DEBUG)
return name + "_" + base;
else
return name;
} else {
return base + "_" + Utils.escape(TypeGen.translate(classGen, arg.getType()));
}
}
}
/**
* Put variable declaration and return variable name (used for assignments)
*
* @param arg
* register variable
* @return variable name
*/
public String assignArg(RegisterArg arg) {
String name = makeArgName(arg);
if (!mthArgsDecls.contains(name))
varDecls.put(name, arg.getType());
return name;
}
private void makeInitCode(CodeWriter code) throws CodegenException {
InsnGen igen = new InsnGen(this, mth, fallback);
// generate super call
if (mth.getSuperCall() != null)
igen.makeInsn(mth.getSuperCall(), code);
}
public void makeVariablesDeclaration(CodeWriter code) {
for (Entry<String, ArgType> var : varDecls.entrySet()) {
code.startLine(TypeGen.translate(classGen, var.getValue()));
code.add(" ");
code.add(var.getKey());
code.add(";");
}
if (!varDecls.isEmpty())
code.endl();
}
public CodeWriter makeInstructions(int mthIndent) throws CodegenException {
CodeWriter code = new CodeWriter(mthIndent + 1);
if (mth.getAttributes().contains(AttributeType.JADX_ERROR)) {
code.startLine("throw new UnsupportedOperationException(\"Method not decompiled: ");
code.add(mth.toString());
code.add("\");");
JadxErrorAttr err = (JadxErrorAttr) mth.getAttributes().get(AttributeType.JADX_ERROR);
code.startLine("// FIXME: Jadx error processing method");
Throwable cause = err.getCause();
if (cause != null) {
code.endl().add("/*");
code.startLine("Message: ").add(cause.getMessage());
code.startLine("Error: ").add(Utils.getStackTrace(cause));
code.add("*/");
}
// load original instructions
try {
mth.load();
DepthTraverser.visit(new FallbackModeVisitor(), mth);
} catch (DecodeException e) {
// ignore
return code;
}
code.startLine("/*");
makeFullMethodDump(code, mth);
code.startLine("*/");
} else {
if (mth.getRegion() != null) {
CodeWriter insns = new CodeWriter(mthIndent + 1);
(new RegionGen(this, mth)).makeRegion(insns, mth.getRegion());
makeInitCode(code);
makeVariablesDeclaration(code);
code.add(insns);
} else {
makeFallbackMethod(code, mth);
}
}
return code;
}
private void makeFullMethodDump(CodeWriter code, MethodNode mth) {
getFallbackMethodGen(mth).addDefinition(code);
code.add(" {");
code.incIndent();
makeFallbackMethod(code, mth);
code.decIndent();
code.startLine("}");
}
private void makeFallbackMethod(CodeWriter code, MethodNode mth) {
if (!mth.getAccessFlags().isStatic()) {
code.startLine(getFallbackMethodGen(mth).makeArgName(mth.getThisArg())).add(" = this;");
}
makeFallbackInsns(code, mth, mth.getInstructions(), true);
}
public static void makeFallbackInsns(CodeWriter code, MethodNode mth, List<InsnNode> insns, boolean addLabels) {
InsnGen insnGen = new InsnGen(getFallbackMethodGen(mth), mth, true);
for (InsnNode insn : insns) {
AttributesList attrs = insn.getAttributes();
if (addLabels) {
if (attrs.contains(AttributeType.JUMP)
|| attrs.contains(AttributeType.EXC_HANDLER)) {
code.decIndent();
code.startLine(getLabelName(insn.getOffset()) + ":");
code.incIndent();
}
}
try {
insnGen.makeInsn(insn, code);
} catch (CodegenException e) {
code.startLine("// error: " + insn);
}
CatchAttr _catch = (CatchAttr) attrs.get(AttributeType.CATCH_BLOCK);
if (_catch != null)
code.add("\t // " + _catch);
}
}
/**
* Return fallback variant of method codegen
*/
private static MethodGen getFallbackMethodGen(MethodNode mth) {
ClassGen clsGen = new ClassGen(mth.getParentClass(), null, true);
return new MethodGen(clsGen, mth);
}
public static String getLabelName(int offset) {
return "L_" + InsnUtils.formatOffset(offset);
}
}
+241
View File
@@ -0,0 +1,241 @@
package jadx.codegen;
import jadx.dex.attributes.AttributeFlag;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.DeclareVariableAttr;
import jadx.dex.attributes.ForceReturnAttr;
import jadx.dex.attributes.IAttribute;
import jadx.dex.instructions.IfNode;
import jadx.dex.instructions.IfOp;
import jadx.dex.instructions.SwitchNode;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.PrimitiveType;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.nodes.IBlock;
import jadx.dex.nodes.IContainer;
import jadx.dex.nodes.IRegion;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.dex.regions.IfRegion;
import jadx.dex.regions.LoopRegion;
import jadx.dex.regions.Region;
import jadx.dex.regions.SwitchRegion;
import jadx.dex.regions.SynchronizedRegion;
import jadx.dex.trycatch.CatchAttr;
import jadx.dex.trycatch.ExceptionHandler;
import jadx.dex.trycatch.TryCatchBlock;
import jadx.utils.ErrorsCounter;
import jadx.utils.RegionUtils;
import jadx.utils.exceptions.CodegenException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RegionGen extends InsnGen {
private final static Logger LOG = LoggerFactory.getLogger(RegionGen.class);
public RegionGen(MethodGen mgen, MethodNode mth) {
super(mgen, mth, false);
}
public void makeRegion(CodeWriter code, IContainer cont) throws CodegenException {
assert cont != null;
if (cont instanceof IBlock) {
makeSimpleBlock((IBlock) cont, code);
} else if (cont instanceof IRegion) {
declareVars(code, cont);
if (cont instanceof Region) {
Region r = (Region) cont;
CatchAttr tc = (CatchAttr) r.getAttributes().get(AttributeType.CATCH_BLOCK);
if (tc != null) {
makeTryCatch(cont, tc.getTryBlock(), code);
} else {
for (IContainer c : r.getSubBlocks())
makeRegion(code, c);
}
} else if (cont instanceof IfRegion) {
makeIf((IfRegion) cont, code);
} else if (cont instanceof SwitchRegion) {
makeSwitch((SwitchRegion) cont, code);
} else if (cont instanceof LoopRegion) {
makeLoop((LoopRegion) cont, code);
} else if (cont instanceof SynchronizedRegion) {
makeSynchronizedRegion((SynchronizedRegion) cont, code);
}
} else {
throw new CodegenException("Not processed container: " + cont.toString());
}
}
private void declareVars(CodeWriter code, IContainer cont) throws CodegenException {
DeclareVariableAttr declVars =
(DeclareVariableAttr) cont.getAttributes().get(AttributeType.DECLARE_VARIABLE);
if (declVars != null) {
for (RegisterArg v : declVars.getVars()) {
code.startLine(declareVar(v)).add(';');
}
}
}
public void makeRegionIndent(CodeWriter code, IContainer region) throws CodegenException {
code.incIndent();
makeRegion(code, region);
code.decIndent();
}
private void makeSimpleBlock(IBlock block, CodeWriter code) throws CodegenException {
for (InsnNode insn : block.getInstructions()) {
makeInsn(insn, code);
}
if (block.getAttributes().contains(AttributeFlag.BREAK)) {
code.startLine("break;");
} else {
IAttribute attr;
if ((attr = block.getAttributes().get(AttributeType.FORCE_RETURN)) != null) {
ForceReturnAttr retAttr = (ForceReturnAttr) attr;
makeInsn(retAttr.getReturnInsn(), code);
}
}
}
private void makeIf(IfRegion region, CodeWriter code) throws CodegenException {
IfNode insn = region.getIfInsn();
code.startLine("if ").add(makeCondition(insn)).add(" {");
makeRegionIndent(code, region.getThenRegion());
code.startLine("}");
IContainer els = region.getElseRegion();
if (els != null && RegionUtils.notEmpty(els)) {
code.add(" else {");
code.incIndent();
makeRegion(code, els);
code.decIndent();
code.startLine("}");
}
}
private CodeWriter makeLoop(LoopRegion region, CodeWriter code) throws CodegenException {
if (region.getConditionBlock() == null) {
// infinite loop
code.startLine("while (true) {");
makeRegionIndent(code, region.getBody());
code.startLine("}");
return code;
}
IfNode insn = region.getIfInsn();
if (!region.isConditionAtEnd()) {
code.startLine("while ").add(makeCondition(insn)).add(" {");
makeRegionIndent(code, region.getBody());
code.startLine("}");
} else {
code.startLine("do {");
makeRegionIndent(code, region.getBody());
code.startLine("} while ").add(makeCondition(insn)).add(";");
}
return code;
}
private void makeSynchronizedRegion(SynchronizedRegion cont, CodeWriter code) throws CodegenException {
code.startLine("synchronized(").add(arg(cont.getArg())).add(") {");
makeRegionIndent(code, cont.getRegion());
code.startLine("}");
}
private String makeCondition(IfNode insn) throws CodegenException {
String second;
IfOp op = insn.getOp();
if (insn.isZeroCmp()) {
ArgType type = insn.getArg(0).getType();
if (type.getPrimitiveType() == PrimitiveType.BOOLEAN) {
if (op == IfOp.EQ) {
// == false
return "(!" + arg(insn.getArg(0)) + ")";
} else if (op == IfOp.NE) {
// == true
return "(" + arg(insn.getArg(0)) + ")";
}
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol()));
}
second = arg(InsnArg.lit(0, type));
} else {
second = arg(insn.getArg(1));
}
return "(" + arg(insn.getArg(0)) + " "
+ op.getSymbol() + " "
+ second + ")";
}
private CodeWriter makeSwitch(SwitchRegion sw, CodeWriter code) throws CodegenException {
SwitchNode insn = (SwitchNode) sw.getHeader().getInstructions().get(0);
InsnArg arg = insn.getArg(0);
code.startLine("switch(").add(arg(arg)).add(") {");
code.incIndent();
int size = sw.getKeys().size();
for (int i = 0; i < size; i++) {
List<Integer> keys = sw.getKeys().get(i);
IContainer c = sw.getCases().get(i);
for (Integer k : keys) {
code.startLine("case ")
.add(TypeGen.literalToString(k, arg.getType()))
.add(":");
}
makeRegionIndent(code, c);
if (RegionUtils.hasExitEdge(c))
code.startLine(1, "break;");
}
if (sw.getDefaultCase() != null) {
code.startLine("default:");
makeRegionIndent(code, sw.getDefaultCase());
if (RegionUtils.hasExitEdge(sw.getDefaultCase()))
code.startLine(1, "break;");
}
code.decIndent();
code.startLine("}");
return code;
}
private void makeTryCatch(IContainer region, TryCatchBlock tryCatchBlock, CodeWriter code)
throws CodegenException {
code.startLine("try {");
region.getAttributes().remove(AttributeType.CATCH_BLOCK);
makeRegionIndent(code, region);
ExceptionHandler allHandler = null;
for (ExceptionHandler handler : tryCatchBlock.getHandlers()) {
if (!handler.isCatchAll()) {
makeCatchBlock(code, handler);
} else {
if (allHandler != null)
LOG.warn("Several 'all' handlers in try/catch block in " + mth);
allHandler = handler;
}
}
if (allHandler != null) {
makeCatchBlock(code, allHandler);
}
if (tryCatchBlock.getFinalBlock() != null) {
code.startLine("} finally {");
makeRegionIndent(code, tryCatchBlock.getFinalBlock());
}
code.startLine("}");
}
private void makeCatchBlock(CodeWriter code, ExceptionHandler handler)
throws CodegenException {
IContainer region = handler.getHandlerRegion();
if (region != null /* && RegionUtils.notEmpty(region) */) {
code.startLine("} catch (");
code.add(handler.isCatchAll() ? "Throwable" : useClass(handler.getCatchType()));
code.add(' ');
code.add(arg(handler.getArg()));
code.add(") {");
makeRegionIndent(code, region);
}
}
}
+128
View File
@@ -0,0 +1,128 @@
package jadx.codegen;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.PrimitiveType;
import jadx.utils.StringUtils;
import jadx.utils.Utils;
import jadx.utils.exceptions.JadxRuntimeException;
public class TypeGen {
public static String translate(ClassGen clsGen, ArgType type) {
final PrimitiveType stype = type.getPrimitiveType();
if (stype == null)
return type.toString();
if (stype == PrimitiveType.OBJECT) {
return clsGen.useClass(type);
}
if (stype == PrimitiveType.ARRAY) {
return translate(clsGen, type.getArrayElement()) + "[]";
}
return stype.getLongName();
}
public static String shortString(ArgType type) {
final PrimitiveType stype = type.getPrimitiveType();
if (stype == null)
return type.toString();
if (stype == PrimitiveType.OBJECT) {
return "L";
}
if (stype == PrimitiveType.ARRAY) {
return shortString(type.getArrayElement()) + "A";
}
return stype.getLongName();
}
public static String signature(ArgType type) {
final PrimitiveType stype = type.getPrimitiveType();
if (stype == PrimitiveType.OBJECT) {
return Utils.makeQualifiedObjectName(type.getObject());
}
if (stype == PrimitiveType.ARRAY) {
return '[' + signature(type.getArrayElement());
}
return stype.getShortName();
}
/**
* Convert literal value to string according to value type
*
* @throws JadxRuntimeException
* for incorrect type or literal value
*/
public static String literalToString(long lit, ArgType type) {
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);
return n;
}
switch (type.getPrimitiveType()) {
case BOOLEAN:
return lit == 0 ? "false" : "true";
case CHAR:
return StringUtils.unescapeChar((char) lit);
case BYTE:
return formatByte((byte) lit);
case SHORT:
return formatShort((short) lit);
case INT:
return formatInteger((int) lit);
case LONG:
return formatLong(lit);
case FLOAT:
return formatFloat(Float.intBitsToFloat((int) lit));
case DOUBLE:
return formatDouble(Double.longBitsToDouble(lit));
case OBJECT:
case ARRAY:
if (lit != 0)
throw new JadxRuntimeException("Wrong object literal: " + type + " = " + lit);
return "null";
default:
throw new JadxRuntimeException("Unknown type in literalToString: " + type);
}
}
public static String formatShort(short s) {
return "(short) " + wrapNegNum(s < 0, Short.toString(s));
}
public static String formatByte(byte b) {
return "(byte) " + wrapNegNum(b < 0, Byte.toString(b));
}
public static String formatInteger(int i) {
return wrapNegNum(i < 0, Integer.toString(i));
}
public static String formatDouble(double d) {
return wrapNegNum(d < 0, Double.toString(d) + "d");
}
public static String formatFloat(float f) {
return wrapNegNum(f < 0, Float.toString(f) + "f");
}
public static String formatLong(long lit) {
String l = Long.toString(lit);
if (lit == Long.MIN_VALUE || Math.abs(lit) >= Integer.MAX_VALUE)
l += "L";
return wrapNegNum(lit < 0, l);
}
private static String wrapNegNum(boolean lz, String str) {
if (lz)
return "(" + str + ")";
else
return str;
}
}
+70
View File
@@ -0,0 +1,70 @@
package jadx.deobf;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class NameMapper {
private static final Set<String> reservedNames = new HashSet<String>(
Arrays.asList(new String[] {
"abstract",
"assert",
"boolean",
"break",
"byte",
"case",
"catch",
"char",
"class",
"const",
"continue",
"default",
"do",
"double ",
"else",
"enum",
"extends",
"false",
"final",
"finally",
"float",
"for",
"goto",
"if",
"implements",
"import",
"instanceof",
"int",
"interface",
"long",
"native",
"new",
"null",
"package",
"private",
"protected",
"public",
"return",
"short",
"static",
"strictfp",
"super",
"switch",
"synchronized",
"this",
"throw",
"throws",
"transient",
"true",
"try",
"void",
"volatile",
"while",
}));
public static boolean isReserved(String str) {
return reservedNames.contains(str);
}
}
@@ -0,0 +1,14 @@
package jadx.dex.attributes;
public abstract class AttrNode implements IAttributeNode {
private AttributesList attributesList;
@Override
public AttributesList getAttributes() {
if (attributesList == null)
attributesList = new AttributesList();
return attributesList;
}
}
@@ -0,0 +1,19 @@
package jadx.dex.attributes;
public enum AttributeFlag {
TRY_ENTER,
TRY_LEAVE,
LOOP_START,
LOOP_END,
SYNTHETIC,
BREAK,
RETURN, // block contains only return instruction
DONT_SHRINK,
DONT_GENERATE,
INCONSISTENT_CODE, // warning about incorrect decompilation
}
@@ -0,0 +1,47 @@
package jadx.dex.attributes;
public enum AttributeType {
// TODO? add attribute target (insn, block, method, field, class)
// instructions
JUMP(false),
// blocks
LOOP(false),
CATCH_BLOCK(false),
EXC_HANDLER(true),
SPLITTER_BLOCK(true),
FORCE_RETURN(true),
// fields
FIELD_VALUE(true),
// methods
JADX_ERROR(true),
// classes
ENUM_CLASS(true),
// any
ANNOTATION_LIST(true),
ANNOTATION_MTH_PARAMETERS(true),
DECLARE_VARIABLE(true);
private final boolean uniq;
private AttributeType(boolean isUniq) {
this.uniq = isUniq;
}
public boolean isUniq() {
return uniq;
}
public boolean notUniq() {
return !uniq;
}
}
@@ -0,0 +1,153 @@
package jadx.dex.attributes;
import jadx.utils.Utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class AttributesList {
private final Set<AttributeFlag> flags;
private final Map<AttributeType, IAttribute> uniqAttr;
private final List<IAttribute> attributes;
private final int[] attrCount;
public AttributesList() {
flags = EnumSet.noneOf(AttributeFlag.class);
uniqAttr = new EnumMap<AttributeType, IAttribute>(AttributeType.class);
attributes = new ArrayList<IAttribute>(1);
attrCount = new int[AttributeType.values().length];
}
public void add(IAttribute attr) {
if (attr.getType().isUniq())
uniqAttr.put(attr.getType(), attr);
else
addMultiAttribute(attr);
}
public void add(AttributeFlag flag) {
flags.add(flag);
}
public boolean contains(AttributeFlag flag) {
return flags.contains(flag);
}
public void remove(AttributeFlag flag) {
flags.remove(flag);
}
private void addMultiAttribute(IAttribute attr) {
attributes.add(attr);
attrCount[attr.getType().ordinal()]++;
}
private int getCountInternal(AttributeType type) {
return attrCount[type.ordinal()];
}
public void addAll(AttributesList otherList) {
flags.addAll(otherList.flags);
uniqAttr.putAll(otherList.uniqAttr);
for (IAttribute attr : otherList.attributes)
addMultiAttribute(attr);
}
public boolean contains(AttributeType type) {
if (type.isUniq())
return uniqAttr.containsKey(type);
else
return getCountInternal(type) != 0;
}
public IAttribute get(AttributeType type) {
if (type.isUniq()) {
return uniqAttr.get(type);
} else {
int count = getCountInternal(type);
if (count != 0) {
for (IAttribute attr : attributes)
if (attr.getType() == type)
return attr;
}
return null;
}
}
public int getCount(AttributeType type) {
if (type.isUniq()) {
return 0;
} else {
return getCountInternal(type);
}
}
public List<IAttribute> getAll(AttributeType type) {
assert type.notUniq();
int count = getCountInternal(type);
if (count == 0) {
return Collections.emptyList();
} else {
List<IAttribute> attrs = new ArrayList<IAttribute>(count);
for (IAttribute attr : attributes) {
if (attr.getType() == type)
attrs.add(attr);
}
return attrs;
}
}
public void remove(AttributeType type) {
if (type.isUniq()) {
uniqAttr.remove(type);
} else {
for (Iterator<IAttribute> it = attributes.iterator(); it.hasNext();) {
IAttribute attr = it.next();
if (attr.getType() == type)
it.remove();
}
attrCount[type.ordinal()] = 0;
}
}
public void clear() {
flags.clear();
uniqAttr.clear();
attributes.clear();
Arrays.fill(attrCount, 0);
}
public List<String> getAttributeStrings() {
int size = flags.size() + uniqAttr.size() + attributes.size();
if (size == 0)
return Collections.emptyList();
List<String> list = new ArrayList<String>(size);
for (AttributeFlag a : flags)
list.add(a.toString());
for (IAttribute a : uniqAttr.values())
list.add(a.toString());
for (IAttribute a : attributes)
list.add(a.toString());
return list;
}
@Override
public String toString() {
List<String> list = getAttributeStrings();
if (list.isEmpty())
return "";
return "A:{" + Utils.listToString(list) + "}";
}
}
@@ -0,0 +1,55 @@
package jadx.dex.attributes;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.instructions.args.TypedVar;
import jadx.dex.nodes.MethodNode;
public class BlockRegState {
private final RegisterArg[] regs;
public BlockRegState(MethodNode mth) {
this.regs = new RegisterArg[mth.getRegsCount()];
for (int i = 0; i < regs.length; i++) {
regs[i] = new RegisterArg(i);
}
}
public BlockRegState(BlockRegState state) {
this.regs = new RegisterArg[state.regs.length];
System.arraycopy(state.regs, 0, regs, 0, state.regs.length);
}
public void assignReg(RegisterArg arg) {
int rn = arg.getRegNum();
regs[rn] = new RegisterArg(rn, arg.getType());
use(arg);
}
public void use(RegisterArg arg) {
TypedVar regType = regs[arg.getRegNum()].getTypedVar();
if (regType == null) {
regType = new TypedVar(arg.getType());
regs[arg.getRegNum()].setTypedVar(regType);
}
arg.replace(regType);
regType.getUseList().add(arg);
}
public RegisterArg getRegister(int r) {
return regs[r];
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
for (RegisterArg reg : regs) {
if (reg.getTypedVar() != null) {
if (str.length() != 0)
str.append(", ");
str.append(reg.toString());
}
}
return str.toString();
}
}
@@ -0,0 +1,42 @@
package jadx.dex.attributes;
import jadx.dex.instructions.args.RegisterArg;
import jadx.utils.Utils;
import java.util.List;
public class DeclareVariableAttr implements IAttribute {
private final List<RegisterArg> vars;
public DeclareVariableAttr() {
this.vars = null; // for instruction use result
}
public DeclareVariableAttr(List<RegisterArg> vars) {
this.vars = vars; // for regions
}
public List<RegisterArg> getVars() {
return vars;
}
public void addVar(RegisterArg arg) {
int i;
if ((i = vars.indexOf(arg)) != -1) {
if (vars.get(i).getType().equals(arg.getType()))
return;
}
vars.add(arg);
}
@Override
public AttributeType getType() {
return AttributeType.DECLARE_VARIABLE;
}
@Override
public String toString() {
return "DECL_VAR: " + Utils.listToString(vars);
}
}
@@ -0,0 +1,78 @@
package jadx.dex.attributes;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.nodes.ClassNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.Utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class EnumClassAttr implements IAttribute {
public static class EnumField {
private final String name;
private final List<InsnArg> args;
private ClassNode cls;
public EnumField(String name, int argsCount) {
this.name = name;
if (argsCount != 0)
this.args = new ArrayList<InsnArg>(argsCount);
else
this.args = Collections.emptyList();
}
public String getName() {
return name;
}
public List<InsnArg> getArgs() {
return args;
}
public ClassNode getCls() {
return cls;
}
public void setCls(ClassNode cls) {
this.cls = cls;
}
@Override
public String toString() {
return name + "(" + Utils.listToString(args) + ") " + cls;
}
}
private final List<EnumField> fields;
private MethodNode staticMethod;
public EnumClassAttr(int fieldsCount) {
this.fields = new ArrayList<EnumClassAttr.EnumField>(fieldsCount);
}
public List<EnumField> getFields() {
return fields;
}
public MethodNode getStaticMethod() {
return staticMethod;
}
public void setStaticMethod(MethodNode staticMethod) {
this.staticMethod = staticMethod;
}
@Override
public AttributeType getType() {
return AttributeType.ENUM_CLASS;
}
@Override
public String toString() {
return "Enum fields: " + fields;
}
}
@@ -0,0 +1,28 @@
package jadx.dex.attributes;
import jadx.dex.nodes.InsnNode;
import jadx.utils.Utils;
public class ForceReturnAttr implements IAttribute {
private final InsnNode returnInsn;
public ForceReturnAttr(InsnNode retInsn) {
this.returnInsn = retInsn;
}
public InsnNode getReturnInsn() {
return returnInsn;
}
@Override
public AttributeType getType() {
return AttributeType.FORCE_RETURN;
}
@Override
public String toString() {
return "FORCE_RETURN " + Utils.listToString(returnInsn.getArguments());
}
}
@@ -0,0 +1,7 @@
package jadx.dex.attributes;
public interface IAttribute {
AttributeType getType();
}
@@ -0,0 +1,7 @@
package jadx.dex.attributes;
public interface IAttributeNode {
AttributesList getAttributes();
}
@@ -0,0 +1,38 @@
package jadx.dex.attributes;
import jadx.utils.Utils;
public class JadxErrorAttr implements IAttribute {
private final Throwable cause;
public JadxErrorAttr(Throwable cause) {
this.cause = cause;
}
public Throwable getCause() {
return cause;
}
@Override
public AttributeType getType() {
return AttributeType.JADX_ERROR;
}
@Override
public String toString() {
StringBuilder str = new StringBuilder();
str.append("JadxError: ");
if (cause == null) {
str.append("null");
} else {
str.append(cause.getClass().toString());
str.append(":");
str.append(cause.getMessage());
str.append("\n");
str.append(Utils.getStackTrace(cause));
}
return str.toString();
}
}
@@ -0,0 +1,50 @@
package jadx.dex.attributes;
import jadx.utils.InsnUtils;
public class JumpAttribute implements IAttribute {
private final int src;
private final int dest;
public JumpAttribute(int src, int dest) {
this.src = src;
this.dest = dest;
}
@Override
public AttributeType getType() {
return AttributeType.JUMP;
}
public int getSrc() {
return src;
}
public int getDest() {
return dest;
}
@Override
public String toString() {
return "JUMP: " + InsnUtils.formatOffset(src) + " -> " + InsnUtils.formatOffset(dest);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + dest;
result = prime * result + src;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
JumpAttribute other = (JumpAttribute) obj;
return dest == other.dest && src == other.src;
}
}
@@ -0,0 +1,59 @@
package jadx.dex.attributes;
import jadx.dex.nodes.BlockNode;
import jadx.utils.BlockUtils;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class LoopAttr implements IAttribute {
private final BlockNode start;
private final BlockNode end;
private final Set<BlockNode> loopBlocks;
public LoopAttr(BlockNode start, BlockNode end) {
this.start = start;
this.end = end;
this.loopBlocks = Collections.unmodifiableSet(BlockUtils.getAllPathsBlocks(start, end));
}
public BlockNode getStart() {
return start;
}
public BlockNode getEnd() {
return end;
}
@Override
public AttributeType getType() {
return AttributeType.LOOP;
}
public Set<BlockNode> getLoopBlocks() {
return loopBlocks;
}
/**
* Return block nodes with exit edges from loop <br>
* Exit nodes belongs to loop (contains in {@code loopBlocks})
*/
public Set<BlockNode> getExitNodes() {
Set<BlockNode> nodes = new HashSet<BlockNode>();
Set<BlockNode> inloop = getLoopBlocks();
for (BlockNode block : inloop) {
// exit: successor node not from this loop, (don't change to getCleanSuccessors)
for (BlockNode s : block.getSuccessors())
if (!inloop.contains(s) && !s.getAttributes().contains(AttributeType.EXC_HANDLER))
nodes.add(block);
}
return nodes;
}
@Override
public String toString() {
return "LOOP: " + start + "->" + end;
}
}
@@ -0,0 +1,44 @@
package jadx.dex.attributes.annotations;
import jadx.dex.instructions.args.ArgType;
import java.util.Map;
public class Annotation {
public static enum Visibility {
BUILD, RUNTIME, SYSTEM
}
private final Visibility visibility;
private final ArgType atype;
private final Map<String, Object> values;
public Annotation(Visibility visibility, ArgType type, Map<String, Object> values) {
this.visibility = visibility;
this.atype = type;
this.values = values;
}
public Visibility getVisibility() {
return visibility;
}
public ArgType getType() {
return atype;
}
public String getAnnotationClass() {
return atype.getObject();
}
public Map<String, Object> getValues() {
return values;
}
@Override
public String toString() {
return "Annotation[" + visibility + ", " + atype + ", " + values + "]";
}
}
@@ -0,0 +1,45 @@
package jadx.dex.attributes.annotations;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.IAttribute;
import jadx.utils.Utils;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AnnotationsList implements IAttribute {
private final Map<String, Annotation> map;
public AnnotationsList(List<Annotation> anList) {
map = new HashMap<String, Annotation>(anList.size());
for (Annotation a : anList) {
map.put(a.getAnnotationClass(), a);
}
}
public Annotation get(String className) {
return map.get(className);
}
public Collection<Annotation> getAll() {
return map.values();
}
public int size() {
return map.size();
}
@Override
public AttributeType getType() {
return AttributeType.ANNOTATION_LIST;
}
@Override
public String toString() {
return Utils.listToString(map.values());
}
}
@@ -0,0 +1,32 @@
package jadx.dex.attributes.annotations;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.IAttribute;
import jadx.utils.Utils;
import java.util.ArrayList;
import java.util.List;
public class MethodParameters implements IAttribute {
private final List<AnnotationsList> paramList;
public MethodParameters(int paramCount) {
paramList = new ArrayList<AnnotationsList>(paramCount);
}
public List<AnnotationsList> getParamList() {
return paramList;
}
@Override
public AttributeType getType() {
return AttributeType.ANNOTATION_MTH_PARAMETERS;
}
@Override
public String toString() {
return Utils.listToString(paramList);
}
}
+156
View File
@@ -0,0 +1,156 @@
package jadx.dex.info;
import jadx.Consts;
import com.android.dx.rop.code.AccessFlags;
public class AccessInfo {
private final int accFlags;
public static enum AFType {
CLASS, FIELD, METHOD
}
private final AFType type;
public AccessInfo(int accessFlags, AFType type) {
this.accFlags = accessFlags;
this.type = type;
}
public boolean containsFlag(int flag) {
return (accFlags & flag) != 0;
}
public AccessInfo remove(int flag) {
if (containsFlag(flag))
return new AccessInfo(accFlags - flag, type);
else
return this;
}
public AccessInfo getVisibility() {
int f = (accFlags & AccessFlags.ACC_PUBLIC)
| (accFlags & AccessFlags.ACC_PROTECTED)
| (accFlags & AccessFlags.ACC_PRIVATE);
return new AccessInfo(f, type);
}
public boolean isAbstract() {
return (accFlags & AccessFlags.ACC_ABSTRACT) != 0;
}
public boolean isInterface() {
return (accFlags & AccessFlags.ACC_INTERFACE) != 0;
}
public boolean isAnnotation() {
return (accFlags & AccessFlags.ACC_ANNOTATION) != 0;
}
public boolean isNative() {
return (accFlags & AccessFlags.ACC_NATIVE) != 0;
}
public boolean isStatic() {
return (accFlags & AccessFlags.ACC_STATIC) != 0;
}
public boolean isFinal() {
return (accFlags & AccessFlags.ACC_FINAL) != 0;
}
public boolean isConstructor() {
return (accFlags & AccessFlags.ACC_CONSTRUCTOR) != 0;
}
public boolean isEnum() {
return (accFlags & AccessFlags.ACC_ENUM) != 0;
}
public boolean isSynthetic() {
return (accFlags & AccessFlags.ACC_SYNTHETIC) != 0;
}
public boolean isBridge() {
return (accFlags & AccessFlags.ACC_BRIDGE) != 0;
}
public boolean isVarArgs() {
return (accFlags & AccessFlags.ACC_VARARGS) != 0;
}
public int getFlags() {
return accFlags;
}
public String makeString() {
StringBuilder code = new StringBuilder();
if ((accFlags & AccessFlags.ACC_PUBLIC) != 0)
code.append("public ");
if ((accFlags & AccessFlags.ACC_PRIVATE) != 0)
code.append("private ");
if ((accFlags & AccessFlags.ACC_PROTECTED) != 0)
code.append("protected ");
if (isStatic())
code.append("static ");
if (isFinal())
code.append("final ");
if (isAbstract())
code.append("abstract ");
if (isNative())
code.append("native ");
switch (type) {
case METHOD:
if ((accFlags & AccessFlags.ACC_SYNCHRONIZED) != 0)
code.append("synchronized ");
if ((accFlags & AccessFlags.ACC_DECLARED_SYNCHRONIZED) != 0)
code.append("synchronized ");
if (isBridge())
code.append("/* bridge */ ");
if (Consts.DEBUG) {
if (isVarArgs())
code.append("/* varargs */ ");
}
break;
case FIELD:
if ((accFlags & AccessFlags.ACC_VOLATILE) != 0)
code.append("volatile ");
if ((accFlags & AccessFlags.ACC_TRANSIENT) != 0)
code.append("transient ");
break;
case CLASS:
if ((accFlags & AccessFlags.ACC_STRICT) != 0)
code.append("strict ");
if (Consts.DEBUG) {
if ((accFlags & AccessFlags.ACC_SUPER) != 0)
code.append("/* super */ ");
if ((accFlags & AccessFlags.ACC_ENUM) != 0)
code.append("/* enum */ ");
}
break;
}
if (isSynthetic())
code.append("/* synthetic */ ");
return code.toString();
}
}
+163
View File
@@ -0,0 +1,163 @@
package jadx.dex.info;
import jadx.deobf.NameMapper;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.nodes.DexNode;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
public final class ClassInfo {
private static final Map<ArgType, ClassInfo> CLASSINFO_CACHE = new HashMap<ArgType, ClassInfo>();
private static final String DEFAULT_PACKAGE_NAME = "defpackage";
private final String clsName;
private final String clsPackage;
private final ArgType type;
private final String fullName;
private final ClassInfo parentClass; // not equals null if this is inner class
public static ClassInfo fromDex(DexNode dex, int clsIndex) {
if (clsIndex == DexNode.NO_INDEX)
return null;
ArgType type = dex.getType(clsIndex);
if (type.isArray())
type = ArgType.OBJECT;
return fromType(dex, type);
}
public static ClassInfo fromName(DexNode dex, String clsName) {
return fromType(dex, ArgType.object(clsName));
}
public static ClassInfo fromType(DexNode dex, ArgType type) {
ClassInfo cls = CLASSINFO_CACHE.get(type);
if (cls == null) {
cls = new ClassInfo(dex, type);
CLASSINFO_CACHE.put(type, cls);
}
return cls;
}
public static void clearCache() {
CLASSINFO_CACHE.clear();
}
private ClassInfo(DexNode dex, ArgType type) {
this.type = type;
String fullObjectName = type.getObject();
String name;
String pkg;
assert fullObjectName.indexOf('/') == -1;
int dot = fullObjectName.lastIndexOf('.');
if (dot == -1) {
// rename default package if it used from class with package (often for obfuscated apps),
// TODO? if default package really needed
pkg = DEFAULT_PACKAGE_NAME;
name = fullObjectName;
} else {
pkg = fullObjectName.substring(0, dot);
name = fullObjectName.substring(dot + 1);
}
int sep = name.lastIndexOf('$');
if (sep > 0) {
String parClsName = pkg + '.' + name.substring(0, sep);
if (dex.root().getJadxArgs().isNotObfuscated()
|| dex.root().isClassExists(parClsName)) {
parentClass = fromName(dex, parClsName);
name = name.substring(sep + 1);
} else {
// TODO for more accuracy we need full classpath class listing
// for now instead make more checks
if (sep != name.length() - 1) {
parentClass = fromName(dex, parClsName);
name = name.substring(sep + 1);
} else
parentClass = null;
}
} else {
parentClass = null;
}
if (Character.isDigit(name.charAt(0)))
name = "InnerClass_" + name;
// TODO rename classes with reserved names
if (NameMapper.isReserved(name))
name += "_";
if (parentClass != null)
fullName = parentClass.getFullName() + '.' + name;
else
fullName = pkg + '.' + name;
this.clsName = name;
this.clsPackage = pkg;
}
public String getFullPath() {
return clsPackage.replace('.', File.separatorChar) + File.separatorChar
+ getNameWithoutPackage().replace('.', '_');
}
public String getFullName() {
return fullName;
}
public String getShortName() {
return clsName;
}
public String getPackage() {
return clsPackage;
}
public boolean isPackageDefault() {
return clsPackage.equals(DEFAULT_PACKAGE_NAME);
}
public String getNameWithoutPackage() {
return (parentClass != null ? parentClass.getNameWithoutPackage() + "." : "") + clsName;
}
public ClassInfo getParentClass() {
return parentClass;
}
public boolean isInner() {
return parentClass != null;
}
public ArgType getType() {
return type;
}
@Override
public String toString() {
return getFullName();
}
@Override
public int hashCode() {
return this.getFullName().hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj instanceof ClassInfo) {
ClassInfo cls = (ClassInfo) obj;
return this.getFullName().equals(cls.getFullName());
}
return false;
}
}
@@ -0,0 +1,47 @@
package jadx.dex.info;
import jadx.dex.attributes.AttrNode;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.nodes.DexNode;
import com.android.dx.io.FieldId;
public class FieldInfo extends AttrNode {
private final String name;
private final ArgType type;
private final ClassInfo declClass;
public static FieldInfo fromDex(DexNode dex, int index) {
return new FieldInfo(dex, index);
}
protected FieldInfo(DexNode dex, int ind) {
FieldId field = dex.getFieldId(ind);
this.name = dex.getString(field.getNameIndex());
this.type = dex.getType(field.getTypeIndex());
this.declClass = ClassInfo.fromDex(dex, field.getDeclaringClassIndex());
}
public static String getNameById(DexNode dex, int ind) {
return dex.getString(dex.getFieldId(ind).getNameIndex());
}
public String getName() {
return name;
}
public ArgType getType() {
return type;
}
public ClassInfo getDeclClass() {
return declClass;
}
@Override
public String toString() {
return declClass + "." + name + " " + type;
}
}
@@ -0,0 +1,49 @@
package jadx.dex.info;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.instructions.args.TypedVar;
import jadx.dex.nodes.DexNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LocalVarInfo extends RegisterArg {
private final static Logger LOG = LoggerFactory.getLogger(LocalVarInfo.class);
private boolean isEnd;
public LocalVarInfo(DexNode dex, int rn, int nameId, int typeId, int signId) {
super(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);
}
public LocalVarInfo(DexNode dex, int rn, String name, ArgType type, String sign) {
super(rn);
init(name, type, sign);
}
private void init(String name, ArgType type, String sign) {
TypedVar tv = new TypedVar(type);
tv.setName(name);
setTypedVar(tv);
// LOG.trace("local var: {}, sign: {}", tv, sign);
}
public void start(int addr, int line) {
this.isEnd = false;
}
public void end(int addr, int line) {
this.isEnd = true;
}
public boolean isEnd() {
return isEnd;
}
}
@@ -0,0 +1,85 @@
package jadx.dex.info;
import jadx.codegen.TypeGen;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.nodes.DexNode;
import jadx.utils.Utils;
import java.util.List;
import com.android.dx.io.MethodId;
import com.android.dx.io.ProtoId;
public final class MethodInfo {
private final String name;
private final ArgType retType;
private final List<ArgType> args;
private final ClassInfo declClass;
private final String shortId;
public static MethodInfo fromDex(DexNode dex, int mthIndex) {
return new MethodInfo(dex, mthIndex);
}
private MethodInfo(DexNode dex, int mthIndex) {
MethodId mthId = dex.getMethodId(mthIndex);
name = dex.getString(mthId.getNameIndex());
declClass = ClassInfo.fromDex(dex, mthId.getDeclaringClassIndex());
ProtoId proto = dex.getProtoId(mthId.getProtoIndex());
retType = dex.getType(proto.getReturnTypeIndex());
args = dex.readParamList(proto.getParametersOffset());
StringBuilder strArg = new StringBuilder();
strArg.append('(');
for (ArgType arg : args)
strArg.append(TypeGen.signature(arg));
strArg.append(')');
// strArg.append(TypeGen.signature(retType));
shortId = name + strArg;
}
public String getName() {
return name;
}
public String getFullName() {
return declClass.getFullName() + "." + name;
}
/**
* Method name and signature
*/
public String getShortId() {
return shortId;
}
public ClassInfo getDeclClass() {
return declClass;
}
public ArgType getReturnType() {
return retType;
}
public List<ArgType> getArgumentsTypes() {
return args;
}
public boolean isConstructor() {
return name.equals("<init>");
}
public boolean isClassInit() {
return name.equals("<clinit>");
}
@Override
public String toString() {
return retType + " " + declClass.getFullName() + "." + name
+ "(" + Utils.listToString(args) + ")";
}
}
@@ -0,0 +1,74 @@
package jadx.dex.instructions;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.InsnUtils;
import com.android.dx.io.instructions.DecodedInstruction;
public class ArithNode extends InsnNode {
private final ArithOp op;
public ArithNode(MethodNode mth, DecodedInstruction insn, ArithOp op, ArgType type,
boolean literal) {
super(mth, InsnType.ARITH, 2);
this.op = op;
setResult(InsnArg.reg(insn, 0, type));
int rc = insn.getRegisterCount();
if (literal) {
if (rc == 1) {
// self
addReg(insn, 0, type);
addLit(insn, type);
} else if (rc == 2) {
// normal
addReg(insn, 1, type);
addLit(insn, type);
}
} else {
if (rc == 2) {
// self
addReg(insn, 0, type);
addReg(insn, 1, type);
} else if (rc == 3) {
// normal
addReg(insn, 1, type);
addReg(insn, 2, type);
}
}
assert getArgsCount() == 2;
}
public ArithNode(MethodNode mth, ArithOp op, RegisterArg res, InsnArg a, InsnArg b) {
super(mth, InsnType.ARITH, 2);
setResult(res);
addArg(a);
addArg(b);
this.op = op;
}
public ArithNode(MethodNode mth, ArithOp op, RegisterArg res, InsnArg a) {
super(mth, InsnType.ARITH, 1);
setResult(res);
addArg(a);
this.op = op;
}
public ArithOp getOp() {
return op;
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
+ InsnUtils.insnTypeToString(insnType)
+ getResult() + " = "
+ getArg(0) + " " + op.getSymbol() + " " + getArg(1);
}
}
@@ -0,0 +1,31 @@
package jadx.dex.instructions;
public enum ArithOp {
ADD("+"),
SUB("-"),
MUL("*"),
DIV("/"),
REM("%"),
INC("++"),
DEC("--"),
AND("&"),
OR("|"),
XOR("^"),
SHL("<<"),
SHR(">>"),
USHR(">>>");
private ArithOp(String symbol) {
this.symbol = symbol;
}
private final String symbol;
public String getSymbol() {
return this.symbol;
}
}
@@ -0,0 +1,44 @@
package jadx.dex.instructions;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.PrimitiveType;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
public class FillArrayOp extends InsnNode {
private final Object data;
public FillArrayOp(MethodNode method, int resReg, FillArrayDataPayloadDecodedInstruction payload) {
super(method, InsnType.FILL_ARRAY, 0);
this.data = payload.getData();
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 AssertionError();
}
setResult(InsnArg.reg(resReg, ArgType.array(elType)));
}
public Object getData() {
return data;
}
}
@@ -0,0 +1,28 @@
package jadx.dex.instructions;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.InsnUtils;
public class GotoNode extends InsnNode {
protected int target;
public GotoNode(MethodNode mth, int target) {
this(mth, InsnType.GOTO, target);
}
protected GotoNode(MethodNode mth, InsnType type, int target) {
super(mth, type);
this.target = target;
}
public int getTarget() {
return target;
}
@Override
public String toString() {
return super.toString() + "-> " + InsnUtils.formatOffset(target);
}
}
@@ -0,0 +1,78 @@
package jadx.dex.instructions;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.LiteralArg;
import jadx.dex.instructions.args.PrimitiveType;
import jadx.dex.nodes.MethodNode;
import jadx.utils.InsnUtils;
import com.android.dx.io.instructions.DecodedInstruction;
public class IfNode extends GotoNode {
protected boolean zeroCmp;
protected IfOp op;
public IfNode(MethodNode mth, IfOp op, int targ, InsnArg then, InsnArg els) {
super(mth, InsnType.IF, targ);
addArg(then);
if (els == null) {
zeroCmp = true;
} else {
zeroCmp = false;
addArg(els);
}
}
public IfNode(MethodNode mth, DecodedInstruction insn, IfOp op) {
super(mth, InsnType.IF, insn.getTarget());
this.op = op;
ArgType type = ArgType.unknown(
PrimitiveType.INT, PrimitiveType.OBJECT, PrimitiveType.ARRAY,
PrimitiveType.BOOLEAN, PrimitiveType.SHORT, PrimitiveType.CHAR);
addReg(insn, 0, type);
if (insn.getRegisterCount() == 1) {
zeroCmp = true;
} else {
zeroCmp = false;
addReg(insn, 1, type);
}
}
public IfOp getOp() {
return op;
}
public boolean isZeroCmp() {
return zeroCmp;
}
public void invertOp(int targ) {
op = op.invert();
target = targ;
}
public void changeCondition(InsnArg arg1, InsnArg arg2, IfOp op) {
this.op = op;
this.zeroCmp = arg2.isLiteral() && ((LiteralArg) arg2).getLiteral() == 0;
setArg(0, arg1);
if (!zeroCmp) {
if (getArgsCount() == 2)
setArg(1, arg2);
else
addArg(arg2);
}
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
+ InsnUtils.insnTypeToString(insnType)
+ getArg(0) + " " + op.getSymbol()
+ " " + (zeroCmp ? "0" : getArg(1))
+ " -> " + InsnUtils.formatOffset(target);
}
}
@@ -0,0 +1,42 @@
package jadx.dex.instructions;
public enum IfOp {
EQ("=="),
NE("!="),
LT("<"),
LE("<="),
GT(">"),
GE(">=");
private final String symbol;
private IfOp(String symbol) {
this.symbol = symbol;
}
public String getSymbol() {
return symbol;
}
public IfOp invert() {
switch (this) {
case EQ:
return IfOp.NE;
case NE:
return IfOp.EQ;
case LT:
return IfOp.GE;
case LE:
return IfOp.GT;
case GT:
return IfOp.LE;
case GE:
return IfOp.LT;
default:
return null;
}
}
}
@@ -0,0 +1,24 @@
package jadx.dex.instructions;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.InsnUtils;
public class IndexInsnNode extends InsnNode {
protected final Object index;
public IndexInsnNode(MethodNode mth, InsnType type, Object index, int argCount) {
super(mth, type, argCount);
this.index = index;
}
public Object getIndex() {
return index;
}
@Override
public String toString() {
return super.toString() + " " + InsnUtils.indexToString(index);
}
}
@@ -0,0 +1,725 @@
package jadx.dex.instructions;
import jadx.dex.info.FieldInfo;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.PrimitiveType;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.nodes.DexNode;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.exceptions.DecodeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.io.Code;
import com.android.dx.io.OpcodeInfo;
import com.android.dx.io.Opcodes;
import com.android.dx.io.instructions.DecodedInstruction;
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
import com.android.dx.io.instructions.PackedSwitchPayloadDecodedInstruction;
import com.android.dx.io.instructions.SparseSwitchPayloadDecodedInstruction;
public class InsnDecoder {
private final static Logger LOG = LoggerFactory.getLogger(InsnDecoder.class);
private final MethodNode method;
private final DecodedInstruction[] insnArr;
private final DexNode dex;
public InsnDecoder(MethodNode mthNode, Code mthCode) {
this.method = mthNode;
this.dex = method.dex();
this.insnArr = DecodedInstruction.decodeAll(mthCode.getInstructions());
}
public InsnNode[] run() {
InsnNode[] instructions = new InsnNode[insnArr.length];
for (int i = 0; i < insnArr.length; i++) {
try {
DecodedInstruction rawInsn = insnArr[i];
if (rawInsn != null) {
InsnNode insn = decode(rawInsn, i);
if (insn != null) {
insn.setOffset(i);
insn.setInsnHashCode(calcHashCode(rawInsn));
}
instructions[i] = insn;
} else {
instructions[i] = null;
}
} catch (DecodeException e) {
LOG.error("Instruction decode error", e);
System.exit(1);
}
}
return instructions;
}
private int calcHashCode(DecodedInstruction insn) {
int hash = insn.getOpcode();
hash = hash * 31 + insn.getClass().getName().hashCode();
hash = hash * 31 + insn.getFormat().ordinal();
hash = hash * 31 + insn.getRegisterCount();
hash = hash * 31 + insn.getIndex();
hash = hash * 31 + insn.getTarget();
hash = hash * 31 + insn.getA();
hash = hash * 31 + insn.getB();
hash = hash * 31 + insn.getC();
hash = hash * 31 + insn.getD();
hash = hash * 31 + insn.getE();
return hash;
}
private InsnNode decode(DecodedInstruction insn, int offset) throws DecodeException {
switch (insn.getOpcode()) {
case Opcodes.NOP:
case Opcodes.PACKED_SWITCH_PAYLOAD:
case Opcodes.SPARSE_SWITCH_PAYLOAD:
case Opcodes.FILL_ARRAY_DATA_PAYLOAD:
return null;
// move-result will be process in invoke and filled-new-array instructions
case Opcodes.MOVE_RESULT:
case Opcodes.MOVE_RESULT_WIDE:
case Opcodes.MOVE_RESULT_OBJECT:
return new InsnNode(method, InsnType.NOP, 0);
case Opcodes.CONST:
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));
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));
case Opcodes.CONST_STRING:
case Opcodes.CONST_STRING_JUMBO: {
InsnNode node = new IndexInsnNode(method, InsnType.CONST, dex.getString(insn.getIndex()), 0);
node.setResult(InsnArg.reg(insn, 0, ArgType.STRING));
return node;
}
case Opcodes.CONST_CLASS: {
InsnNode node = new IndexInsnNode(method, InsnType.CONST, dex.getType(insn.getIndex()), 0);
node.setResult(InsnArg.reg(insn, 0, ArgType.CLASS));
return node;
}
case Opcodes.MOVE:
case Opcodes.MOVE_16:
case Opcodes.MOVE_FROM16:
return insn(InsnType.MOVE,
InsnArg.reg(insn, 0, ArgType.NARROW),
InsnArg.reg(insn, 1, ArgType.NARROW));
case Opcodes.MOVE_WIDE:
case Opcodes.MOVE_WIDE_16:
case Opcodes.MOVE_WIDE_FROM16:
return insn(InsnType.MOVE,
InsnArg.reg(insn, 0, ArgType.WIDE),
InsnArg.reg(insn, 1, ArgType.WIDE));
case Opcodes.MOVE_OBJECT:
case Opcodes.MOVE_OBJECT_16:
case Opcodes.MOVE_OBJECT_FROM16:
return insn(InsnType.MOVE,
InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT),
InsnArg.reg(insn, 1, ArgType.UNKNOWN_OBJECT));
case Opcodes.ADD_INT:
case Opcodes.ADD_INT_2ADDR:
return arith(insn, ArithOp.ADD, ArgType.INT);
case Opcodes.ADD_DOUBLE:
case Opcodes.ADD_DOUBLE_2ADDR:
return arith(insn, ArithOp.ADD, ArgType.DOUBLE);
case Opcodes.ADD_FLOAT:
case Opcodes.ADD_FLOAT_2ADDR:
return arith(insn, ArithOp.ADD, ArgType.FLOAT);
case Opcodes.ADD_LONG:
case Opcodes.ADD_LONG_2ADDR:
return arith(insn, ArithOp.ADD, ArgType.LONG);
case Opcodes.ADD_INT_LIT8:
case Opcodes.ADD_INT_LIT16:
return arith_lit(insn, ArithOp.ADD, ArgType.INT);
case Opcodes.SUB_INT:
case Opcodes.SUB_INT_2ADDR:
return arith(insn, ArithOp.SUB, ArgType.INT);
case Opcodes.RSUB_INT:
return new ArithNode(method, ArithOp.SUB,
InsnArg.reg(insn, 0, ArgType.INT),
InsnArg.reg(insn, 2, ArgType.INT),
InsnArg.reg(insn, 1, ArgType.INT));
case Opcodes.RSUB_INT_LIT8:
return new ArithNode(method, ArithOp.SUB,
InsnArg.reg(insn, 0, ArgType.INT),
InsnArg.lit(insn, ArgType.INT),
InsnArg.reg(insn, 1, ArgType.INT));
case Opcodes.SUB_LONG:
case Opcodes.SUB_LONG_2ADDR:
return arith(insn, ArithOp.SUB, ArgType.LONG);
case Opcodes.SUB_FLOAT:
case Opcodes.SUB_FLOAT_2ADDR:
return arith(insn, ArithOp.SUB, ArgType.FLOAT);
case Opcodes.SUB_DOUBLE:
case Opcodes.SUB_DOUBLE_2ADDR:
return arith(insn, ArithOp.SUB, ArgType.DOUBLE);
case Opcodes.MUL_INT:
case Opcodes.MUL_INT_2ADDR:
return arith(insn, ArithOp.MUL, ArgType.INT);
case Opcodes.MUL_DOUBLE:
case Opcodes.MUL_DOUBLE_2ADDR:
return arith(insn, ArithOp.MUL, ArgType.DOUBLE);
case Opcodes.MUL_FLOAT:
case Opcodes.MUL_FLOAT_2ADDR:
return arith(insn, ArithOp.MUL, ArgType.FLOAT);
case Opcodes.MUL_LONG:
case Opcodes.MUL_LONG_2ADDR:
return arith(insn, ArithOp.MUL, ArgType.LONG);
case Opcodes.MUL_INT_LIT8:
case Opcodes.MUL_INT_LIT16:
return arith_lit(insn, ArithOp.MUL, ArgType.INT);
case Opcodes.DIV_INT:
case Opcodes.DIV_INT_2ADDR:
return arith(insn, ArithOp.DIV, ArgType.INT);
case Opcodes.REM_INT:
case Opcodes.REM_INT_2ADDR:
return arith(insn, ArithOp.REM, ArgType.INT);
case Opcodes.REM_LONG:
case Opcodes.REM_LONG_2ADDR:
return arith(insn, ArithOp.REM, ArgType.LONG);
case Opcodes.REM_FLOAT:
case Opcodes.REM_FLOAT_2ADDR:
return arith(insn, ArithOp.REM, ArgType.FLOAT);
case Opcodes.REM_DOUBLE:
case Opcodes.REM_DOUBLE_2ADDR:
return arith(insn, ArithOp.REM, ArgType.DOUBLE);
case Opcodes.DIV_DOUBLE:
case Opcodes.DIV_DOUBLE_2ADDR:
return arith(insn, ArithOp.DIV, ArgType.DOUBLE);
case Opcodes.DIV_FLOAT:
case Opcodes.DIV_FLOAT_2ADDR:
return arith(insn, ArithOp.DIV, ArgType.FLOAT);
case Opcodes.DIV_LONG:
case Opcodes.DIV_LONG_2ADDR:
return arith(insn, ArithOp.DIV, ArgType.LONG);
case Opcodes.DIV_INT_LIT8:
case Opcodes.DIV_INT_LIT16:
return arith_lit(insn, ArithOp.DIV, ArgType.INT);
case Opcodes.REM_INT_LIT8:
case Opcodes.REM_INT_LIT16:
return arith_lit(insn, ArithOp.REM, ArgType.INT);
case Opcodes.AND_INT:
case Opcodes.AND_INT_2ADDR:
return arith(insn, ArithOp.AND, ArgType.INT);
case Opcodes.AND_INT_LIT8:
case Opcodes.AND_INT_LIT16:
return arith_lit(insn, ArithOp.AND, ArgType.INT);
case Opcodes.XOR_INT_LIT8:
case Opcodes.XOR_INT_LIT16:
return arith_lit(insn, ArithOp.XOR, ArgType.INT);
case Opcodes.AND_LONG:
case Opcodes.AND_LONG_2ADDR:
return arith(insn, ArithOp.AND, ArgType.LONG);
case Opcodes.OR_INT:
case Opcodes.OR_INT_2ADDR:
return arith(insn, ArithOp.OR, ArgType.INT);
case Opcodes.OR_INT_LIT8:
case Opcodes.OR_INT_LIT16:
return arith_lit(insn, ArithOp.OR, ArgType.INT);
case Opcodes.XOR_INT:
case Opcodes.XOR_INT_2ADDR:
return arith(insn, ArithOp.XOR, ArgType.INT);
case Opcodes.OR_LONG:
case Opcodes.OR_LONG_2ADDR:
return arith(insn, ArithOp.OR, ArgType.LONG);
case Opcodes.XOR_LONG:
case Opcodes.XOR_LONG_2ADDR:
return arith(insn, ArithOp.XOR, ArgType.LONG);
case Opcodes.USHR_INT:
case Opcodes.USHR_INT_2ADDR:
return arith(insn, ArithOp.USHR, ArgType.INT);
case Opcodes.USHR_LONG:
case Opcodes.USHR_LONG_2ADDR:
return arith(insn, ArithOp.USHR, ArgType.LONG);
case Opcodes.SHL_INT:
case Opcodes.SHL_INT_2ADDR:
return arith(insn, ArithOp.SHL, ArgType.INT);
case Opcodes.SHL_LONG:
case Opcodes.SHL_LONG_2ADDR:
return arith(insn, ArithOp.SHL, ArgType.LONG);
case Opcodes.SHR_INT:
case Opcodes.SHR_INT_2ADDR:
return arith(insn, ArithOp.SHR, ArgType.INT);
case Opcodes.SHR_LONG:
case Opcodes.SHR_LONG_2ADDR:
return arith(insn, ArithOp.SHR, ArgType.LONG);
case Opcodes.SHL_INT_LIT8:
return arith_lit(insn, ArithOp.SHL, ArgType.INT);
case Opcodes.SHR_INT_LIT8:
return arith_lit(insn, ArithOp.SHR, ArgType.INT);
case Opcodes.USHR_INT_LIT8:
return arith_lit(insn, ArithOp.USHR, ArgType.INT);
case Opcodes.NEG_INT:
return neg(insn, ArgType.INT);
case Opcodes.NEG_LONG:
return neg(insn, ArgType.LONG);
case Opcodes.NEG_FLOAT:
return neg(insn, ArgType.FLOAT);
case Opcodes.NEG_DOUBLE:
return neg(insn, ArgType.DOUBLE);
case Opcodes.INT_TO_BYTE:
return cast(insn, ArgType.INT, ArgType.BYTE);
case Opcodes.INT_TO_CHAR:
return cast(insn, ArgType.INT, ArgType.CHAR);
case Opcodes.INT_TO_SHORT:
return cast(insn, ArgType.INT, ArgType.SHORT);
case Opcodes.INT_TO_FLOAT:
return cast(insn, ArgType.INT, ArgType.FLOAT);
case Opcodes.INT_TO_DOUBLE:
return cast(insn, ArgType.INT, ArgType.DOUBLE);
case Opcodes.INT_TO_LONG:
return cast(insn, ArgType.INT, ArgType.LONG);
case Opcodes.FLOAT_TO_INT:
return cast(insn, ArgType.FLOAT, ArgType.INT);
case Opcodes.FLOAT_TO_DOUBLE:
return cast(insn, ArgType.FLOAT, ArgType.DOUBLE);
case Opcodes.FLOAT_TO_LONG:
return cast(insn, ArgType.FLOAT, ArgType.LONG);
case Opcodes.DOUBLE_TO_INT:
return cast(insn, ArgType.DOUBLE, ArgType.INT);
case Opcodes.DOUBLE_TO_FLOAT:
return cast(insn, ArgType.DOUBLE, ArgType.FLOAT);
case Opcodes.DOUBLE_TO_LONG:
return cast(insn, ArgType.DOUBLE, ArgType.LONG);
case Opcodes.LONG_TO_INT:
return cast(insn, ArgType.LONG, ArgType.INT);
case Opcodes.LONG_TO_FLOAT:
return cast(insn, ArgType.LONG, ArgType.FLOAT);
case Opcodes.LONG_TO_DOUBLE:
return cast(insn, ArgType.LONG, ArgType.DOUBLE);
case Opcodes.IF_EQ:
case Opcodes.IF_EQZ:
return new IfNode(method, insn, IfOp.EQ);
case Opcodes.IF_NE:
case Opcodes.IF_NEZ:
return new IfNode(method, insn, IfOp.NE);
case Opcodes.IF_GT:
case Opcodes.IF_GTZ:
return new IfNode(method, insn, IfOp.GT);
case Opcodes.IF_GE:
case Opcodes.IF_GEZ:
return new IfNode(method, insn, IfOp.GE);
case Opcodes.IF_LT:
case Opcodes.IF_LTZ:
return new IfNode(method, insn, IfOp.LT);
case Opcodes.IF_LE:
case Opcodes.IF_LEZ:
return new IfNode(method, insn, IfOp.LE);
case Opcodes.CMP_LONG:
return cmp(insn, InsnType.CMP_L, ArgType.LONG);
case Opcodes.CMPL_FLOAT:
return cmp(insn, InsnType.CMP_L, ArgType.FLOAT);
case Opcodes.CMPL_DOUBLE:
return cmp(insn, InsnType.CMP_L, ArgType.DOUBLE);
case Opcodes.CMPG_FLOAT:
return cmp(insn, InsnType.CMP_G, ArgType.FLOAT);
case Opcodes.CMPG_DOUBLE:
return cmp(insn, InsnType.CMP_G, ArgType.DOUBLE);
case Opcodes.GOTO:
case Opcodes.GOTO_16:
case Opcodes.GOTO_32:
return new GotoNode(method, insn.getTarget());
case Opcodes.THROW:
return insn(InsnType.THROW, null,
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)));
case Opcodes.MOVE_EXCEPTION:
return insn(InsnType.MOVE_EXCEPTION,
InsnArg.reg(insn, 0, ArgType.unknown(PrimitiveType.OBJECT)));
case Opcodes.RETURN_VOID:
return new InsnNode(method, InsnType.RETURN, 0);
case Opcodes.RETURN:
case Opcodes.RETURN_WIDE:
case Opcodes.RETURN_OBJECT:
return insn(InsnType.RETURN,
null,
InsnArg.reg(insn, 0, method.getMethodInfo().getReturnType()));
case Opcodes.INSTANCE_OF: {
InsnNode node = new IndexInsnNode(method, InsnType.INSTANCE_OF, dex.getType(insn.getIndex()), 1);
node.setResult(InsnArg.reg(insn, 0, ArgType.BOOLEAN));
node.addArg(InsnArg.reg(insn, 1, ArgType.UNKNOWN_OBJECT));
return node;
}
case Opcodes.CHECK_CAST: {
ArgType castType = dex.getType(insn.getIndex());
InsnNode node = new IndexInsnNode(method, InsnType.CHECK_CAST, castType, 1);
node.setResult(InsnArg.reg(insn, 0, castType));
node.addArg(InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));
return node;
}
case Opcodes.IGET:
case Opcodes.IGET_BOOLEAN:
case Opcodes.IGET_BYTE:
case Opcodes.IGET_CHAR:
case Opcodes.IGET_SHORT:
case Opcodes.IGET_WIDE:
case Opcodes.IGET_OBJECT: {
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode node = new IndexInsnNode(method, InsnType.IGET, field, 1);
node.setResult(InsnArg.reg(insn, 0, field.getType()));
node.addArg(InsnArg.reg(insn, 1, field.getDeclClass().getType()));
return node;
}
case Opcodes.IPUT:
case Opcodes.IPUT_BOOLEAN:
case Opcodes.IPUT_BYTE:
case Opcodes.IPUT_CHAR:
case Opcodes.IPUT_SHORT:
case Opcodes.IPUT_WIDE:
case Opcodes.IPUT_OBJECT: {
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode node = new IndexInsnNode(method, InsnType.IPUT, field, 2);
node.addArg(InsnArg.reg(insn, 0, field.getType()));
node.addArg(InsnArg.reg(insn, 1, field.getDeclClass().getType()));
return node;
}
case Opcodes.SGET:
case Opcodes.SGET_BOOLEAN:
case Opcodes.SGET_BYTE:
case Opcodes.SGET_CHAR:
case Opcodes.SGET_SHORT:
case Opcodes.SGET_WIDE:
case Opcodes.SGET_OBJECT: {
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode node = new IndexInsnNode(method, InsnType.SGET, field, 0);
node.setResult(InsnArg.reg(insn, 0, field.getType()));
return node;
}
case Opcodes.SPUT:
case Opcodes.SPUT_BOOLEAN:
case Opcodes.SPUT_BYTE:
case Opcodes.SPUT_CHAR:
case Opcodes.SPUT_SHORT:
case Opcodes.SPUT_WIDE:
case Opcodes.SPUT_OBJECT: {
FieldInfo field = FieldInfo.fromDex(dex, insn.getIndex());
InsnNode node = new IndexInsnNode(method, InsnType.SPUT, field, 1);
node.addArg(InsnArg.reg(insn, 0, field.getType()));
return node;
}
case Opcodes.ARRAY_LENGTH: {
InsnNode node = new InsnNode(method, InsnType.ARRAY_LENGTH, 1);
node.setResult(InsnArg.reg(insn, 0, ArgType.INT));
node.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
return node;
}
case Opcodes.AGET:
return arrayGet(insn, ArgType.NARROW);
case Opcodes.AGET_BOOLEAN:
return arrayGet(insn, ArgType.BOOLEAN);
case Opcodes.AGET_BYTE:
return arrayGet(insn, ArgType.BYTE);
case Opcodes.AGET_CHAR:
return arrayGet(insn, ArgType.CHAR);
case Opcodes.AGET_SHORT:
return arrayGet(insn, ArgType.SHORT);
case Opcodes.AGET_WIDE:
return arrayGet(insn, ArgType.WIDE);
case Opcodes.AGET_OBJECT:
return arrayGet(insn, ArgType.UNKNOWN_OBJECT);
case Opcodes.APUT:
return arrayPut(insn, ArgType.NARROW);
case Opcodes.APUT_BOOLEAN:
return arrayPut(insn, ArgType.BOOLEAN);
case Opcodes.APUT_BYTE:
return arrayPut(insn, ArgType.BYTE);
case Opcodes.APUT_CHAR:
return arrayPut(insn, ArgType.CHAR);
case Opcodes.APUT_SHORT:
return arrayPut(insn, ArgType.SHORT);
case Opcodes.APUT_WIDE:
return arrayPut(insn, ArgType.WIDE);
case Opcodes.APUT_OBJECT:
return arrayPut(insn, ArgType.UNKNOWN_OBJECT);
case Opcodes.INVOKE_STATIC:
return invoke(insn, offset, InvokeType.STATIC, false);
case Opcodes.INVOKE_STATIC_RANGE:
return invoke(insn, offset, InvokeType.STATIC, true);
case Opcodes.INVOKE_DIRECT:
return invoke(insn, offset, InvokeType.DIRECT, false);
case Opcodes.INVOKE_INTERFACE:
return invoke(insn, offset, InvokeType.INTERFACE, false);
case Opcodes.INVOKE_SUPER:
return invoke(insn, offset, InvokeType.SUPER, false);
case Opcodes.INVOKE_VIRTUAL:
return invoke(insn, offset, InvokeType.VIRTUAL, false);
case Opcodes.INVOKE_DIRECT_RANGE:
return invoke(insn, offset, InvokeType.DIRECT, true);
case Opcodes.INVOKE_INTERFACE_RANGE:
return invoke(insn, offset, InvokeType.INTERFACE, true);
case Opcodes.INVOKE_SUPER_RANGE:
return invoke(insn, offset, InvokeType.SUPER, true);
case Opcodes.INVOKE_VIRTUAL_RANGE:
return invoke(insn, offset, InvokeType.VIRTUAL, true);
case Opcodes.NEW_INSTANCE:
return insn(InsnType.NEW_INSTANCE,
InsnArg.reg(insn, 0, dex.getType(insn.getIndex())));
case Opcodes.NEW_ARRAY:
return insn(InsnType.NEW_ARRAY,
InsnArg.reg(insn, 0, dex.getType(insn.getIndex())),
InsnArg.reg(insn, 1, ArgType.INT));
case Opcodes.FILL_ARRAY_DATA:
return fillArray(insn);
case Opcodes.FILLED_NEW_ARRAY:
return filledNewArray(insn, offset, false);
case Opcodes.FILLED_NEW_ARRAY_RANGE:
return filledNewArray(insn, offset, true);
case Opcodes.PACKED_SWITCH:
return decodeSwitch(insn, offset, true);
case Opcodes.SPARSE_SWITCH:
return decodeSwitch(insn, offset, false);
case Opcodes.MONITOR_ENTER:
return insn(InsnType.MONITOR_ENTER,
null,
InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));
case Opcodes.MONITOR_EXIT:
return insn(InsnType.MONITOR_EXIT,
null,
InsnArg.reg(insn, 0, ArgType.UNKNOWN_OBJECT));
}
throw new DecodeException("Unknown instruction: " + OpcodeInfo.getName(insn.getOpcode()));
}
private InsnNode decodeSwitch(DecodedInstruction insn, int offset, boolean packed) {
int payloadOffset = insn.getTarget();
DecodedInstruction payload = insnArr[payloadOffset];
int[] keys;
int[] targets;
if (packed) {
PackedSwitchPayloadDecodedInstruction ps = (PackedSwitchPayloadDecodedInstruction) payload;
targets = ps.getTargets();
keys = new int[targets.length];
int k = ps.getFirstKey();
for (int i = 0; i < keys.length; i++)
keys[i] = k++;
} else {
SparseSwitchPayloadDecodedInstruction ss = (SparseSwitchPayloadDecodedInstruction) payload;
targets = ss.getTargets();
keys = ss.getKeys();
}
// convert from relative to absolute offsets
for (int i = 0; i < targets.length; i++) {
targets[i] = targets[i] - payloadOffset + offset;
}
int nextOffset = getNextInsnOffset(insnArr, offset);
return new SwitchNode(method, InsnArg.reg(insn, 0, ArgType.NARROW),
keys, targets, nextOffset);
}
private InsnNode fillArray(DecodedInstruction insn) {
DecodedInstruction payload = insnArr[insn.getTarget()];
return new FillArrayOp(method, insn.getA(), (FillArrayDataPayloadDecodedInstruction) payload);
}
private InsnNode filledNewArray(DecodedInstruction insn, int offset, boolean isRange) {
int resReg = getMoveResultRegister(insnArr, offset);
ArgType arrType = dex.getType(insn.getIndex());
ArgType elType = arrType.getArrayElement();
InsnArg[] regs = new InsnArg[insn.getRegisterCount()];
if (isRange) {
int r = insn.getA();
for (int i = 0; i < insn.getRegisterCount(); i++) {
regs[i] = InsnArg.reg(r, elType);
r++;
}
} else {
for (int i = 0; i < insn.getRegisterCount(); i++)
regs[i] = InsnArg.reg(insn, i, elType);
}
return insn(InsnType.FILLED_NEW_ARRAY,
resReg == -1 ? null : InsnArg.reg(resReg, arrType),
regs);
}
private InsnNode cmp(DecodedInstruction insn, InsnType itype, ArgType argType) {
InsnNode inode = new InsnNode(method, itype, 2);
inode.setResult(InsnArg.reg(insn, 0, ArgType.INT));
inode.addArg(InsnArg.reg(insn, 1, argType));
inode.addArg(InsnArg.reg(insn, 2, argType));
return inode;
}
private InsnNode cast(DecodedInstruction insn, ArgType from, ArgType to) {
InsnNode inode = new IndexInsnNode(method, InsnType.CAST, to, 1);
inode.setResult(InsnArg.reg(insn, 0, to));
inode.addArg(InsnArg.reg(insn, 1, from));
return inode;
}
private InsnNode invoke(DecodedInstruction insn, int offset, InvokeType type, boolean isRange) {
int resReg = getMoveResultRegister(insnArr, offset);
return new InvokeNode(method, insn, type, isRange, resReg);
}
private InsnNode arrayGet(DecodedInstruction insn, ArgType argType) {
InsnNode inode = new InsnNode(method, 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));
return inode;
}
private InsnNode arrayPut(DecodedInstruction insn, ArgType argType) {
InsnNode inode = new InsnNode(method, 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));
return inode;
}
private InsnNode arith(DecodedInstruction insn, ArithOp op, ArgType type) {
return new ArithNode(method, insn, op, type, false);
}
private InsnNode arith_lit(DecodedInstruction insn, ArithOp op, ArgType type) {
return new ArithNode(method, insn, op, type, true);
}
private InsnNode neg(DecodedInstruction insn, ArgType type) {
InsnNode inode = new InsnNode(method, InsnType.NEG, 1);
inode.setResult(InsnArg.reg(insn, 0, type));
inode.addArg(InsnArg.reg(insn, 1, type));
return inode;
}
private InsnNode insn(InsnType type, RegisterArg res, InsnArg... args) {
InsnNode inode = new InsnNode(method, type, args == null ? 0 : args.length);
inode.setResult(res);
if (args != null)
for (InsnArg arg : args)
inode.addArg(arg);
return inode;
}
private int getMoveResultRegister(DecodedInstruction[] insnArr, int offset) {
int nextOffset = getNextInsnOffset(insnArr, offset);
if (nextOffset >= 0) {
DecodedInstruction next = insnArr[nextOffset];
int opc = next.getOpcode();
if (opc == Opcodes.MOVE_RESULT
|| opc == Opcodes.MOVE_RESULT_WIDE
|| opc == Opcodes.MOVE_RESULT_OBJECT) {
return next.getA();
}
}
return -1;
}
public static int getPrevInsnOffset(Object[] insnArr, int offset) {
int i = offset - 1;
while (i >= 0 && insnArr[i] == null)
i--;
if (i < 0)
return -1;
return i;
}
public static int getNextInsnOffset(Object[] insnArr, int offset) {
int i = offset + 1;
while (i < insnArr.length && insnArr[i] == null)
i++;
if (i >= insnArr.length)
return -1;
return i;
}
}
@@ -0,0 +1,55 @@
package jadx.dex.instructions;
public enum InsnType {
NOP, // replacement for removed instructions
CONST,
ARITH,
NEG,
MOVE,
CAST,
RETURN,
GOTO,
THROW,
MOVE_EXCEPTION,
CMP_L,
CMP_G,
IF,
SWITCH,
MONITOR_ENTER,
MONITOR_EXIT,
CHECK_CAST,
INSTANCE_OF,
ARRAY_LENGTH,
FILL_ARRAY,
FILLED_NEW_ARRAY,
AGET,
APUT,
NEW_ARRAY,
NEW_INSTANCE,
IGET,
IPUT,
SGET,
SPUT,
INVOKE,
// additional instructions
CONSTRUCTOR,
BREAK,
CONTINUE,
TERNARY,
NEW_MULTIDIM_ARRAY // TODO: now multidimensional arrays created using Array.newInstance function
}
@@ -0,0 +1,57 @@
package jadx.dex.instructions;
import jadx.dex.info.MethodInfo;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.InsnUtils;
import jadx.utils.Utils;
import com.android.dx.io.instructions.DecodedInstruction;
public class InvokeNode extends InsnNode {
private final InvokeType type;
private final MethodInfo mth;
public InvokeNode(MethodNode method, DecodedInstruction insn, InvokeType type, boolean isRange,
int resReg) {
super(method, InsnType.INVOKE);
this.mth = MethodInfo.fromDex(method.dex(), insn.getIndex());
this.type = type;
if (resReg >= 0)
setResult(InsnArg.reg(resReg, mth.getReturnType()));
int k = isRange ? insn.getA() : 0;
if (type != InvokeType.STATIC) {
int r = isRange ? k : InsnUtils.getArg(insn, k);
addReg(r, mth.getDeclClass().getType());
k++;
}
for (ArgType arg : mth.getArgumentsTypes()) {
addReg(isRange ? k : InsnUtils.getArg(insn, k), arg);
k += arg.getRegCount();
}
}
public InvokeType getInvokeType() {
return type;
}
public MethodInfo getCallMth() {
return mth;
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
+ InsnUtils.insnTypeToString(insnType)
+ (getResult() == null ? "" : getResult() + " = ")
+ Utils.listToString(getArguments())
+ " " + mth
+ " type: " + type;
}
}
@@ -0,0 +1,9 @@
package jadx.dex.instructions;
public enum InvokeType {
STATIC,
DIRECT,
VIRTUAL,
INTERFACE,
SUPER,
}
@@ -0,0 +1,52 @@
package jadx.dex.instructions;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.InsnUtils;
import java.util.Arrays;
public class SwitchNode extends InsnNode {
private final int[] keys;
private final int[] targets;
private final int def; // next instruction
public SwitchNode(MethodNode mth, InsnArg arg, int[] keys, int[] targets, int def) {
super(mth, InsnType.SWITCH, 1);
this.keys = keys;
this.targets = targets;
this.def = def;
addArg(arg);
}
public int getCasesCount() {
return keys.length;
}
public int[] getKeys() {
return keys;
}
public int[] getTargets() {
return targets;
}
public int getDefaultCaseOffset() {
return def;
}
@Override
public String toString() {
StringBuilder targ = new StringBuilder();
targ.append('[');
for (int i = 0; i < targets.length; i++) {
targ.append(InsnUtils.formatOffset(targets[i]));
if (i < targets.length - 1)
targ.append(", ");
}
targ.append(']');
return super.toString() + " k:" + Arrays.toString(keys) + " t:" + targ;
}
}
@@ -0,0 +1,299 @@
package jadx.dex.instructions.args;
import jadx.Consts;
import jadx.utils.Utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public final 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);
public static final ArgType SHORT = primitive(PrimitiveType.SHORT);
public static final ArgType CHAR = primitive(PrimitiveType.CHAR);
public static final ArgType FLOAT = primitive(PrimitiveType.FLOAT);
public static final ArgType DOUBLE = primitive(PrimitiveType.DOUBLE);
public static final ArgType LONG = primitive(PrimitiveType.LONG);
public static final ArgType VOID = primitive(PrimitiveType.VOID);
public static final ArgType OBJECT = object(Consts.CLASS_OBJECT);
public static final ArgType CLASS = object(Consts.CLASS_CLASS);
public static final ArgType STRING = object(Consts.CLASS_STRING);
public static final ArgType THROWABLE = object(Consts.CLASS_THROWABLE);
public static final ArgType UNKNOWN = unknown(PrimitiveType.values());
public static final ArgType NARROW = unknown(
PrimitiveType.INT, PrimitiveType.FLOAT,
PrimitiveType.BOOLEAN, PrimitiveType.SHORT, PrimitiveType.BYTE, PrimitiveType.CHAR,
PrimitiveType.OBJECT, PrimitiveType.ARRAY);
public static final ArgType WIDE = unknown(PrimitiveType.LONG, PrimitiveType.DOUBLE);
public static final ArgType UNKNOWN_OBJECT = unknown(PrimitiveType.OBJECT, PrimitiveType.ARRAY);
private final PrimitiveType type;
private final String object;
private final ArgType arrayElement;
private final PrimitiveType possibleTypes[];
private final int hash;
private ArgType(PrimitiveType type, String object, ArgType arrayElement) {
this.type = type;
this.object = (object == null ? null : Utils.cleanObjectName(object));
this.arrayElement = arrayElement;
this.possibleTypes = null;
this.hash = calcHashCode();
}
private ArgType(PrimitiveType[] posTypes) {
this.type = null;
this.object = null;
this.arrayElement = null;
this.possibleTypes = posTypes;
this.hash = calcHashCode();
}
public static ArgType primitive(PrimitiveType stype) {
assert stype != PrimitiveType.OBJECT && stype != PrimitiveType.ARRAY;
return new ArgType(stype, null, null);
}
public static ArgType object(String obj) {
assert obj != null;
return new ArgType(PrimitiveType.OBJECT, obj, null);
}
public static ArgType array(ArgType vtype) {
return new ArgType(PrimitiveType.ARRAY, null, vtype);
}
public static ArgType unknown(PrimitiveType... types) {
return new ArgType(types);
}
public boolean isTypeKnown() {
return type != null;
}
public PrimitiveType getPrimitiveType() {
return type;
}
public boolean isPrimitive() {
return type != null && type != PrimitiveType.OBJECT && type != PrimitiveType.ARRAY;
}
public String getObject() {
return object;
}
public boolean isObject() {
return type == PrimitiveType.OBJECT;
}
public ArgType getArrayElement() {
return arrayElement;
}
public boolean isArray() {
return type == PrimitiveType.ARRAY;
}
public int getArrayDimension() {
if (isArray())
return 1 + arrayElement.getArrayDimension();
else
return 0;
}
public ArgType getArrayRootElement() {
if (isArray())
return arrayElement.getArrayRootElement();
else
return this;
}
public boolean contains(PrimitiveType type) {
for (PrimitiveType t : possibleTypes)
if (t == type)
return true;
return false;
}
public ArgType selectFirst() {
assert possibleTypes != null;
PrimitiveType f = possibleTypes[0];
if (f == PrimitiveType.OBJECT || f == PrimitiveType.ARRAY)
return object(Consts.CLASS_OBJECT);
else
return primitive(f);
}
public static ArgType merge(ArgType a, ArgType b) {
if (a == b)
return a;
if (b == null || a == null)
return null;
ArgType res = mergeInternal(a, b);
if (res == null)
res = mergeInternal(b, a); // swap
return res;
}
private static ArgType mergeInternal(ArgType a, ArgType b) {
if (a == UNKNOWN)
return b;
if (a.possibleTypes != null) {
if (b.isTypeKnown()) {
if (a.contains(b.getPrimitiveType()))
return b;
else
return null;
} else {
// both types unknown
List<PrimitiveType> types = new ArrayList<PrimitiveType>();
for (PrimitiveType type : a.possibleTypes) {
if (b.contains(type))
types.add(type);
}
if (types.size() == 0) {
return null;
} else 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.isObject() && b.isObject()) {
if (a.getObject().equals(b.getObject()))
return a;
else if (a.getObject().equals(OBJECT.getObject()))
return b;
else if (b.getObject().equals(OBJECT.getObject()))
return a;
else
// different objects
return OBJECT;
// return null;
}
if (a.isArray() && b.isArray()) {
ArgType res = merge(a.getArrayElement(), b.getArrayElement());
return (res == null ? null : ArgType.array(res));
}
if (a.isPrimitive() && b.isPrimitive()) {
if (a.getRegCount() == b.getRegCount())
// return primitive(PrimitiveType.getWidest(a.getPrimitiveType(), b.getPrimitiveType()));
return primitive(PrimitiveType.getSmaller(a.getPrimitiveType(), b.getPrimitiveType()));
}
}
return null;
}
public static ArgType parse(String type) {
assert type.length() > 0 : "Empty type";
char f = type.charAt(0);
if (f == 'L')
return object(type);
else if (f == '[')
return array(parse(type.substring(1)));
else
return parse(f);
}
private static ArgType parse(char f) {
switch (f) {
case 'Z':
return BOOLEAN;
case 'B':
return BYTE;
case 'C':
return CHAR;
case 'S':
return SHORT;
case 'I':
return INT;
case 'J':
return LONG;
case 'F':
return FLOAT;
case 'D':
return DOUBLE;
case 'V':
return VOID;
}
throw new RuntimeException("Unknown type: " + f);
}
public int getRegCount() {
if (type == PrimitiveType.LONG || type == PrimitiveType.DOUBLE)
return 2;
else
return 1;
}
@Override
public String toString() {
if (this == UNKNOWN)
return "ANY";
if (type != null) {
if (type == PrimitiveType.OBJECT)
return object;
else if (type == PrimitiveType.ARRAY)
return arrayElement + "[]";
else
return type.toString();
} else {
return "?" + Arrays.asList(possibleTypes).toString();
}
}
private int calcHashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((type == null) ? 0 : type.hashCode());
result = prime * result + ((object == null) ? 0 : object.hashCode());
result = prime * result + ((arrayElement == null) ? 0 : arrayElement.hashCode());
result = prime * result + Arrays.hashCode(possibleTypes);
return result;
}
@Override
public int hashCode() {
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (hash != obj.hashCode()) return false;
if (getClass() != obj.getClass()) return false;
ArgType other = (ArgType) obj;
if (type != other.type) return false;
if (!Arrays.equals(possibleTypes, other.possibleTypes)) return false;
if (arrayElement == null) {
if (other.arrayElement != null) return false;
} else if (!arrayElement.equals(other.arrayElement)) return false;
if (object == null) {
if (other.object != null) return false;
} else if (!object.equals(other.object)) return false;
return true;
}
}
@@ -0,0 +1,75 @@
package jadx.dex.instructions.args;
import jadx.dex.nodes.InsnNode;
import jadx.utils.InsnUtils;
import com.android.dx.io.instructions.DecodedInstruction;
/**
* Instruction argument,
* argument can be register, literal or instruction
*/
public abstract class InsnArg extends Typed {
protected InsnNode parentInsn;
public static RegisterArg reg(int regNum, ArgType type) {
assert regNum >= 0 : "Register number must be positive";
return new RegisterArg(regNum, type);
}
public static RegisterArg reg(DecodedInstruction insn, int argNum, ArgType type) {
return reg(InsnUtils.getArg(insn, argNum), type);
}
public static LiteralArg lit(long literal, ArgType type) {
return new LiteralArg(literal, type);
}
public static LiteralArg lit(DecodedInstruction insn, ArgType type) {
return lit(insn.getLiteral(), type);
}
public static InsnWrapArg wrap(InsnNode insn) {
return new InsnWrapArg(insn);
}
public boolean isRegister() {
return false;
}
public boolean isLiteral() {
return false;
}
public boolean isInsnWrap() {
return false;
}
public InsnNode getParentInsn() {
return parentInsn;
}
public void setParentInsn(InsnNode parentInsn) {
this.parentInsn = parentInsn;
}
public InsnWrapArg wrapInstruction(InsnNode insn) {
assert parentInsn != insn : "Can't wrap instruction info itself";
int count = parentInsn.getArgsCount();
for (int i = 0; i < count; i++) {
if (parentInsn.getArg(i) == this) {
InsnWrapArg arg = wrap(insn);
parentInsn.setArg(i, arg);
return arg;
}
}
return null;
}
public boolean isThis() {
// must be implemented in RegisterArg
return false;
}
}
@@ -0,0 +1,34 @@
package jadx.dex.instructions.args;
import jadx.dex.nodes.InsnNode;
public class InsnWrapArg extends InsnArg {
private final InsnNode wrappedInsn;
public InsnWrapArg(InsnNode insn) {
ArgType type = (insn.getResult() == null ? ArgType.VOID : insn.getResult().getType());
this.typedVar = new TypedVar(type);
this.wrappedInsn = insn;
}
public InsnNode getWrapInsn() {
return wrappedInsn;
}
@Override
public void setParentInsn(InsnNode parentInsn) {
assert parentInsn != wrappedInsn : "Can't wrap instruction info itself: " + parentInsn;
this.parentInsn = parentInsn;
}
@Override
public boolean isInsnWrap() {
return true;
}
@Override
public String toString() {
return "(wrap: " + typedVar + "\n " + wrappedInsn + ")";
}
}
@@ -0,0 +1,35 @@
package jadx.dex.instructions.args;
import jadx.codegen.TypeGen;
import jadx.utils.exceptions.JadxRuntimeException;
public class LiteralArg extends InsnArg {
private final long literal;
public LiteralArg(long value, ArgType type) {
this.literal = value;
this.typedVar = new TypedVar(type);
if (literal != 0 && type.isObject())
throw new RuntimeException("wrong literal type");
}
public long getLiteral() {
return literal;
}
@Override
public boolean isLiteral() {
return true;
}
@Override
public String toString() {
try {
return "(" + TypeGen.literalToString(literal, getType()) + " " + typedVar + ")";
} catch (JadxRuntimeException ex) {
// can't convert literal to string
return "(" + literal + " " + typedVar + ")";
}
}
}
@@ -0,0 +1,50 @@
package jadx.dex.instructions.args;
public enum PrimitiveType {
BOOLEAN("Z", "boolean"),
CHAR("C", "char"),
BYTE("B", "byte"),
SHORT("S", "short"),
INT("I", "int"),
FLOAT("F", "float"),
LONG("J", "long"),
DOUBLE("D", "double"),
OBJECT("L", "OBJECT"),
ARRAY("[", "ARRAY"),
VOID("V", "void");
private final String shortName;
private final String longName;
private PrimitiveType(String shortName, String longName) {
this.shortName = shortName;
this.longName = longName;
}
public String getShortName() {
return shortName;
}
public String getLongName() {
return longName;
}
public static PrimitiveType getWidest(PrimitiveType a, PrimitiveType b) {
if (a.ordinal() > b.ordinal())
return a;
else
return b;
}
public static PrimitiveType getSmaller(PrimitiveType a, PrimitiveType b) {
if (a.ordinal() < b.ordinal())
return a;
else
return b;
}
@Override
public String toString() {
return this.name().toLowerCase();
}
}
@@ -0,0 +1,102 @@
package jadx.dex.instructions.args;
import jadx.dex.instructions.IndexInsnNode;
import jadx.dex.instructions.InsnType;
import jadx.dex.nodes.InsnNode;
import jadx.dex.visitors.InstructionRemover;
public class RegisterArg extends InsnArg {
protected final int regNum;
public RegisterArg(int rn) {
this.regNum = rn;
}
public RegisterArg(int rn, ArgType type) {
this.typedVar = new TypedVar(type);
this.regNum = rn;
}
public int getRegNum() {
return regNum;
}
@Override
public boolean isRegister() {
return true;
}
public InsnNode getAssignInsn() {
for (InsnArg arg : getTypedVar().getUseList()) {
InsnNode assignInsn = arg.getParentInsn();
if (assignInsn == null)
// assign as function argument
return null;
else if (assignInsn.getResult() != null
&& assignInsn.getResult().getRegNum() == regNum)
return assignInsn;
}
return null;
}
/**
* Return constant value from register assign or null if not constant
*
* @return LiteralArg, String or ArgType
*/
public Object getConstValue() {
InsnNode parInsn = getAssignInsn();
if (parInsn != null && parInsn.getType() == InsnType.CONST) {
if (parInsn.getArgsCount() == 0) {
// const in 'index' - string or class
return ((IndexInsnNode) parInsn).getIndex();
} else {
return parInsn.getArg(0);
}
}
return null;
}
@Override
public boolean isThis() {
if (isRegister()) {
String name = getTypedVar().getName();
if (name != null && name.equals("this"))
return true;
// maybe it was moved from 'this' register
InsnNode ai = getAssignInsn();
if (ai != null && ai.getType() == InsnType.MOVE) {
if (ai.getArg(0).isThis()) {
// actually we need to remove this instruction but we can't
// because of iterating on instructions list
// so unbind insn and rely on code shrinker
InstructionRemover.unbindInsn(ai);
return true;
}
}
}
return false;
}
@Override
public int hashCode() {
return regNum * 31 + typedVar.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
RegisterArg other = (RegisterArg) obj;
if (regNum != other.regNum) return false;
if (!typedVar.equals(other.typedVar)) return false;
return true;
}
@Override
public String toString() {
return "(r" + regNum + " " + typedVar + ")";
}
}
@@ -0,0 +1,50 @@
package jadx.dex.instructions.args;
public abstract class Typed {
protected TypedVar typedVar;
public TypedVar getTypedVar() {
return typedVar;
}
public void setTypedVar(TypedVar arg) {
this.typedVar = arg;
}
public ArgType getType() {
return typedVar.getType();
}
public boolean merge(Typed var) {
return typedVar.merge(var.getTypedVar());
}
public boolean merge(ArgType var) {
return typedVar.merge(var);
}
public void replace(Typed var) {
replace(var.getTypedVar());
}
public void replace(TypedVar newVar) {
assert newVar != null;
if (typedVar == newVar)
return;
if (typedVar != null) {
newVar.merge(typedVar);
for (InsnArg arg : typedVar.getUseList()) {
if (arg != this)
arg.setTypedVar(newVar);
}
newVar.getUseList().addAll(typedVar.getUseList());
if (typedVar.getName() != null)
newVar.setName(typedVar.getName());
typedVar.getUseList().clear();
}
typedVar = newVar;
}
}
@@ -0,0 +1,82 @@
package jadx.dex.instructions.args;
import java.util.ArrayList;
import java.util.List;
public class TypedVar {
private ArgType type;
private final List<InsnArg> useList = new ArrayList<InsnArg>(2);
private String name;
public TypedVar(ArgType initType) {
this.type = initType;
}
public ArgType getType() {
return type;
}
/**
* This method must be used very carefully
*/
public boolean forceSetType(ArgType type) {
if (type != null && !type.equals(type)) {
this.type = type;
return true;
} else {
return false;
}
}
public boolean merge(TypedVar typedVar) {
return merge(typedVar.getType());
}
public boolean merge(ArgType mtype) {
ArgType res = ArgType.merge(type, mtype);
if (res != null && !type.equals(res)) {
this.type = res;
return true;
} else {
return false;
}
}
public List<InsnArg> getUseList() {
return useList;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
return type.hashCode() * 31 + ((name == null) ? 0 : name.hashCode());
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
TypedVar other = (TypedVar) obj;
if (name == null) {
if (other.name != null) return false;
} else if (!name.equals(other.name)) return false;
if (type == null) {
if (other.type != null) return false;
} else if (!type.equals(other.type)) return false;
return true;
}
@Override
public String toString() {
return (name != null ? "'" + name + "' " : "") + type.toString();
}
}
@@ -0,0 +1,75 @@
package jadx.dex.instructions.mods;
import jadx.dex.info.ClassInfo;
import jadx.dex.info.MethodInfo;
import jadx.dex.instructions.InsnType;
import jadx.dex.instructions.InvokeNode;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
public class ConstructorInsn extends InsnNode {
private final MethodInfo callMth;
private static enum CallType {
CONSTRUCTOR, // just new instance
SUPER, // super call
THIS, // call constructor from other constructor
SELF // call itself
}
private final CallType callType;
public ConstructorInsn(MethodNode mth, InvokeNode invoke) {
super(mth, InsnType.CONSTRUCTOR, invoke.getArgsCount() - 1);
this.callMth = invoke.getCallMth();
ClassInfo classType = callMth.getDeclClass();
if (invoke.getArg(0).isThis()) {
if (classType.equals(mth.getParentClass().getClassInfo())) {
// self constructor
if (callMth.getShortId().equals(mth.getMethodInfo().getShortId())) {
callType = CallType.SELF;
} else {
callType = CallType.THIS;
}
} else {
callType = CallType.SUPER;
}
} else {
callType = CallType.CONSTRUCTOR;
setResult((RegisterArg) invoke.getArg(0));
}
for (int i = 1; i < invoke.getArgsCount(); i++) {
addArg(invoke.getArg(i));
}
offset = invoke.getOffset();
}
public MethodInfo getCallMth() {
return callMth;
}
public ClassInfo getClassType() {
return callMth.getDeclClass();
}
public boolean isSuper() {
return callType == CallType.SUPER;
}
public boolean isThis() {
return callType == CallType.THIS;
}
public boolean isSelf() {
return callType == CallType.SELF;
}
@Override
public String toString() {
return super.toString() + " " + callMth + " " + callType;
}
}
@@ -0,0 +1,31 @@
package jadx.dex.instructions.mods;
import jadx.dex.instructions.IfNode;
import jadx.dex.instructions.IfOp;
import jadx.dex.instructions.InsnType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.InsnUtils;
import jadx.utils.Utils;
public class TernaryInsn extends IfNode {
public TernaryInsn(MethodNode mth, IfOp op, InsnNode then, InsnNode els) {
super(mth, op, then.getOffset(),
InsnArg.wrap(then),
els == null ? null : InsnArg.wrap(els));
}
@Override
public InsnType getType() {
return InsnType.TERNARY;
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": TERNARY"
+ getResult() + " = "
+ Utils.listToString(getArguments());
}
}
+173
View File
@@ -0,0 +1,173 @@
package jadx.dex.nodes;
import jadx.dex.attributes.AttrNode;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.BlockRegState;
import jadx.dex.attributes.LoopAttr;
import jadx.utils.InsnUtils;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
public class BlockNode extends AttrNode implements IBlock {
private final int startOffset;
private final List<InsnNode> instructions = new ArrayList<InsnNode>(2);
private List<BlockNode> predecessors = new ArrayList<BlockNode>(1);
private List<BlockNode> successors = new ArrayList<BlockNode>(1);
private BitSet doms; // all dominators
private BlockNode idom; // immediate dominator
private final List<BlockNode> dominatesOn = new ArrayList<BlockNode>(1);
private BlockRegState startState;
private BlockRegState endState;
private int id;
public static int initialID;
public BlockNode(MethodNode mth, int offset) {
this.startOffset = offset;
this.id = ++initialID;
}
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public List<BlockNode> getPredecessors() {
return predecessors;
}
public List<BlockNode> getSuccessors() {
return successors;
}
private List<BlockNode> cleanSuccessors;
public List<BlockNode> getCleanSuccessors() {
return cleanSuccessors;
}
public void updateCleanSuccessors() {
cleanSuccessors = cleanSuccessors(this);
}
public void lock() {
cleanSuccessors = Collections.unmodifiableList(cleanSuccessors);
successors = Collections.unmodifiableList(successors);
predecessors = Collections.unmodifiableList(predecessors);
}
/**
* Return all successor which are not exception handler or followed by loop back edge
*/
private static List<BlockNode> cleanSuccessors(BlockNode block) {
List<BlockNode> sucList = block.getSuccessors();
List<BlockNode> nodes = new ArrayList<BlockNode>(sucList.size());
LoopAttr loop = (LoopAttr) block.getAttributes().get(AttributeType.LOOP);
if (loop == null) {
for (BlockNode b : sucList) {
if (!b.getAttributes().contains(AttributeType.EXC_HANDLER))
nodes.add(b);
}
} else {
for (BlockNode b : sucList) {
if (!b.getAttributes().contains(AttributeType.EXC_HANDLER)) {
// don't follow back edge
if (loop.getStart() == b && loop.getEnd() == block)
continue;
nodes.add(b);
}
}
}
return (nodes.size() == sucList.size() ? sucList : nodes);
}
@Override
public List<InsnNode> getInstructions() {
return instructions;
}
public int getStartOffset() {
return startOffset;
}
/**
* Check if 'block' dominated on this node
*/
public boolean isDominator(BlockNode block) {
return doms.get(block.getId());
}
/**
* Dominators of this node (exclude itself)
*/
public BitSet getDoms() {
return doms;
}
public void setDoms(BitSet doms) {
this.doms = doms;
}
/**
* Immediate dominator
*/
public BlockNode getIDom() {
return idom;
}
public void setIDom(BlockNode idom) {
this.idom = idom;
}
public List<BlockNode> getDominatesOn() {
return dominatesOn;
}
public BlockRegState getStartState() {
return startState;
}
public void setStartState(BlockRegState startState) {
this.startState = startState;
}
public BlockRegState getEndState() {
return endState;
}
public void setEndState(BlockRegState endState) {
this.endState = endState;
}
@Override
public int hashCode() {
return id; // TODO id can change during reindex
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (hashCode() != obj.hashCode()) return false;
if (!(obj instanceof BlockNode)) return false;
BlockNode other = (BlockNode) obj;
if (id != other.id) return false;
if (startOffset != other.startOffset) return false;
return true;
}
@Override
public String toString() {
return "B:" + id + ":" + InsnUtils.formatOffset(startOffset);
}
}
+258
View File
@@ -0,0 +1,258 @@
package jadx.dex.nodes;
import jadx.dex.attributes.AttrNode;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.IAttribute;
import jadx.dex.attributes.annotations.Annotation;
import jadx.dex.attributes.annotations.AnnotationsList;
import jadx.dex.info.AccessInfo;
import jadx.dex.info.AccessInfo.AFType;
import jadx.dex.info.ClassInfo;
import jadx.dex.info.FieldInfo;
import jadx.dex.info.MethodInfo;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.nodes.parser.AnnotationsParser;
import jadx.dex.nodes.parser.FieldValueAttr;
import jadx.dex.nodes.parser.StaticValuesParser;
import jadx.utils.exceptions.DecodeException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.io.ClassData;
import com.android.dx.io.ClassData.Field;
import com.android.dx.io.ClassData.Method;
import com.android.dx.io.ClassDef;
public class ClassNode extends AttrNode implements ILoadable {
private final static Logger LOG = LoggerFactory.getLogger(ClassNode.class);
private final DexNode dex;
private final ClassInfo clsInfo;
private final ClassInfo superClass;
private final List<ClassInfo> interfaces;
private final List<MethodNode> methods = new ArrayList<MethodNode>();
private final List<FieldNode> fields = new ArrayList<FieldNode>();
private final AccessInfo accessFlags;
private List<ClassNode> innerClasses = Collections.emptyList();
private final Map<Object, FieldNode> constFields = new HashMap<Object, FieldNode>();
public ClassNode(DexNode dex, ClassDef cls) throws DecodeException {
this.dex = dex;
this.clsInfo = ClassInfo.fromDex(dex, cls.getTypeIndex());
try {
this.superClass = cls.getSupertypeIndex() == DexNode.NO_INDEX
? null
: ClassInfo.fromDex(dex, cls.getSupertypeIndex());
this.interfaces = new ArrayList<ClassInfo>(cls.getInterfaces().length);
for (short interfaceIdx : cls.getInterfaces()) {
this.interfaces.add(ClassInfo.fromDex(dex, interfaceIdx));
}
if (cls.getClassDataOffset() == 0) {
// nothing to load
} else {
ClassData clsData = dex.readClassData(cls);
for (Method mth : clsData.getDirectMethods())
methods.add(new MethodNode(this, mth));
for (Method mth : clsData.getVirtualMethods())
methods.add(new MethodNode(this, mth));
for (Field f : clsData.getStaticFields())
fields.add(new FieldNode(this, f));
loadStaticValues(cls, fields);
for (Field f : clsData.getInstanceFields())
fields.add(new FieldNode(this, f));
}
loadAnnotations(cls);
int accFlagsValue = cls.getAccessFlags();
IAttribute annotations = getAttributes().get(AttributeType.ANNOTATION_LIST);
if (annotations != null) {
AnnotationsList list = (AnnotationsList) annotations;
Annotation iCls = list.get("dalvik.annotation.InnerClass");
if (iCls != null)
accFlagsValue = (Integer) iCls.getValues().get("accessFlags");
}
this.accessFlags = new AccessInfo(accFlagsValue, AFType.CLASS);
} catch (Exception e) {
throw new DecodeException("Error decode class: " + getFullName(), e);
}
}
private void loadAnnotations(ClassDef cls) {
int offset = cls.getAnnotationsOffset();
if (offset != 0) {
try {
new AnnotationsParser(this, offset);
} catch (DecodeException e) {
LOG.error("Error parsing annotations in " + this, e);
}
}
}
private void loadStaticValues(ClassDef cls, List<FieldNode> staticFields) throws DecodeException {
for (FieldNode f : staticFields) {
if (f.getAccessFlags().isFinal()) {
FieldValueAttr nullValue = new FieldValueAttr(null);
f.getAttributes().add(nullValue);
}
}
int offset = cls.getStaticValuesOffset();
if (offset != 0) {
StaticValuesParser parser = new StaticValuesParser(dex, dex.openSection(offset));
parser.processFields(staticFields);
for (FieldNode f : staticFields) {
if (f.getType().equals(ArgType.STRING)) {
FieldValueAttr fv = (FieldValueAttr) f.getAttributes().get(AttributeType.FIELD_VALUE);
if (fv != null && fv.getValue() != null) {
constFields.put(fv.getValue(), f);
}
}
}
}
}
@Override
public void load() throws DecodeException {
for (MethodNode mth : getMethods()) {
mth.load();
}
for (ClassNode innerCls : getInnerClasses()) {
innerCls.load();
}
}
@Override
public void unload() {
for (MethodNode mth : getMethods()) {
mth.unload();
}
for (ClassNode innerCls : getInnerClasses()) {
innerCls.unload();
}
}
public ClassInfo getSuperClass() {
return superClass;
}
public List<ClassInfo> getInterfaces() {
return interfaces;
}
public List<MethodNode> getMethods() {
return methods;
}
public List<FieldNode> getFields() {
return fields;
}
public FieldNode searchFieldById(int id) {
String name = FieldInfo.getNameById(dex, id);
for (FieldNode f : fields) {
if (f.getName().equals(name))
return f;
}
return null;
}
public FieldNode searchField(FieldInfo field) {
String name = field.getName();
for (FieldNode f : fields) {
if (f.getName().equals(name))
return f;
}
return null;
}
public MethodNode searchMethodById(String shortId) {
for (MethodNode m : methods) {
if (m.getMethodInfo().getShortId().equals(shortId))
return m;
}
return null;
}
public MethodNode searchMethodById(int id) {
return searchMethodById(MethodInfo.fromDex(dex, id).getShortId());
}
public List<ClassNode> getInnerClasses() {
return innerClasses;
}
public void addInnerClass(ClassNode cls) {
if (innerClasses.isEmpty())
innerClasses = new ArrayList<ClassNode>(3);
innerClasses.add(cls);
}
public boolean isAnonymous() {
boolean simple = false;
for (MethodNode m : methods) {
MethodInfo mi = m.getMethodInfo();
if (mi.isConstructor() && mi.getArgumentsTypes().size() == 0) {
simple = true;
break;
}
}
return simple && Character.isDigit(getShortName().charAt(0));
}
public AccessInfo getAccessFlags() {
return accessFlags;
}
public Map<Object, FieldNode> getConstFields() {
return constFields;
}
public DexNode dex() {
return dex;
}
public ClassInfo getClassInfo() {
return clsInfo;
}
public String getShortName() {
return clsInfo.getShortName();
}
public String getFullName() {
return clsInfo.getFullName();
}
public String getPackage() {
return clsInfo.getPackage();
}
@Override
public String toString() {
return getFullName();
}
}
+119
View File
@@ -0,0 +1,119 @@
package jadx.dex.nodes;
import jadx.dex.info.ClassInfo;
import jadx.dex.instructions.args.ArgType;
import jadx.utils.Utils;
import jadx.utils.exceptions.DecodeException;
import jadx.utils.files.InputFile;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.android.dx.io.ClassData;
import com.android.dx.io.ClassData.Method;
import com.android.dx.io.ClassDef;
import com.android.dx.io.Code;
import com.android.dx.io.DexBuffer;
import com.android.dx.io.DexBuffer.Section;
import com.android.dx.io.FieldId;
import com.android.dx.io.MethodId;
import com.android.dx.io.ProtoId;
import com.android.dx.merge.TypeList;
public class DexNode {
public final static int NO_INDEX = -1;
private final RootNode root;
private final DexBuffer dexBuf;
private final List<ClassNode> classes = new ArrayList<ClassNode>();
private final String[] strings;
public DexNode(RootNode root, InputFile input) throws IOException, DecodeException {
this.root = root;
this.dexBuf = input.getDexBuffer();
List<String> stringList = dexBuf.strings();
this.strings = stringList.toArray(new String[stringList.size()]);
}
public void loadClasses(RootNode root) throws DecodeException {
for (ClassDef cls : dexBuf.classDefs()) {
classes.add(new ClassNode(this, cls));
}
}
public List<ClassNode> getClasses() {
return classes;
}
public ClassNode resolveClass(ClassInfo clsInfo) {
return root.resolveClass(clsInfo);
}
// DexBuffer wrappers
public String getString(int index) {
return strings[index];
}
public ArgType getType(int index) {
return ArgType.parse(getString(dexBuf.typeIds().get(index)));
}
public List<String> getAllClassesNames() {
List<Integer> types = dexBuf.typeIds();
int size = types.size();
List<String> list = new ArrayList<String>(size);
for (Integer typeId : types) {
String type = getString(typeId);
if (type.length() > 0 && type.charAt(0) == 'L')
list.add(Utils.cleanObjectName(type));
}
return list;
}
public MethodId getMethodId(int mthIndex) {
return dexBuf.methodIds().get(mthIndex);
}
public FieldId getFieldId(int fieldIndex) {
return dexBuf.fieldIds().get(fieldIndex);
}
public ProtoId getProtoId(int protoIndex) {
return dexBuf.protoIds().get(protoIndex);
}
public ClassData readClassData(ClassDef cls) {
return dexBuf.readClassData(cls);
}
public List<ArgType> readParamList(int parametersOffset) {
TypeList paramList = dexBuf.readTypeList(parametersOffset);
List<ArgType> args = new ArrayList<ArgType>(paramList.getTypes().length);
for (short t : paramList.getTypes()) {
args.add(getType(t));
}
return args;
}
public Code readCode(Method mth) {
return dexBuf.readCode(mth);
}
public Section openSection(int offset) {
return dexBuf.open(offset);
}
public RootNode root() {
return root;
}
@Override
public String toString() {
return "DEX";
}
}
@@ -0,0 +1,22 @@
package jadx.dex.nodes;
import jadx.dex.info.AccessInfo;
import jadx.dex.info.AccessInfo.AFType;
import jadx.dex.info.FieldInfo;
import com.android.dx.io.ClassData.Field;
public class FieldNode extends FieldInfo {
private final AccessInfo accFlags;
public FieldNode(ClassNode cls, Field field) {
super(cls.dex(), field.getFieldIndex());
this.accFlags = new AccessInfo(field.getAccessFlags(), AFType.FIELD);
}
public AccessInfo getAccessFlags() {
return accFlags;
}
}
+8
View File
@@ -0,0 +1,8 @@
package jadx.dex.nodes;
import java.util.List;
public interface IBlock extends IContainer {
public List<InsnNode> getInstructions();
}
@@ -0,0 +1,11 @@
package jadx.dex.nodes;
import jadx.dex.attributes.AttributesList;
import jadx.dex.attributes.IAttributeNode;
public interface IContainer extends IAttributeNode {
@Override
public AttributesList getAttributes();
}
@@ -0,0 +1,19 @@
package jadx.dex.nodes;
import jadx.utils.exceptions.DecodeException;
public interface ILoadable {
/**
* On demand loading
*
* @throws DecodeException
*/
public void load() throws DecodeException;
/**
* Free resources
*/
public void unload();
}
+11
View File
@@ -0,0 +1,11 @@
package jadx.dex.nodes;
import java.util.List;
public interface IRegion extends IContainer {
public IRegion getParent();
public List<IContainer> getSubBlocks();
}
@@ -0,0 +1,20 @@
package jadx.dex.nodes;
import jadx.dex.attributes.AttrNode;
import java.util.List;
public class InsnContainer extends AttrNode implements IBlock {
private List<InsnNode> insns;
public void setInstructions(List<InsnNode> insns) {
this.insns = insns;
}
@Override
public List<InsnNode> getInstructions() {
return insns;
}
}
+162
View File
@@ -0,0 +1,162 @@
package jadx.dex.nodes;
import jadx.dex.attributes.AttrNode;
import jadx.dex.instructions.InsnType;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.InsnWrapArg;
import jadx.dex.instructions.args.RegisterArg;
import jadx.utils.InsnUtils;
import jadx.utils.Utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.android.dx.io.instructions.DecodedInstruction;
public class InsnNode extends AttrNode {
protected final InsnType insnType;
private RegisterArg result;
private final List<InsnArg> arguments;
protected int offset;
protected int insnHashCode = super.hashCode();
protected InsnNode(MethodNode mth, InsnType type) {
this(mth, type, 3);
}
public InsnNode(MethodNode mth, InsnType type, int argsCount) {
this.insnType = type;
this.offset = -1;
if (argsCount == 0)
this.arguments = Collections.emptyList();
else
this.arguments = new ArrayList<InsnArg>(argsCount);
}
public void setResult(RegisterArg res) {
if (res != null)
res.setParentInsn(this);
this.result = res;
}
public void addArg(InsnArg arg) {
arg.setParentInsn(this);
arguments.add(arg);
}
public InsnType getType() {
return insnType;
}
public RegisterArg getResult() {
return result;
}
public Iterable<InsnArg> getArguments() {
return arguments;
}
public int getArgsCount() {
return arguments.size();
}
public InsnArg getArg(int n) {
return arguments.get(n);
}
public boolean containsArg(RegisterArg arg) {
for (InsnArg a : arguments) {
if (a == arg || (a.isRegister() && ((RegisterArg) a).getRegNum() == arg.getRegNum()))
return true;
}
return false;
}
public void setArg(int n, InsnArg arg) {
arg.setParentInsn(this);
arguments.set(n, arg);
}
public boolean replaceArg(InsnArg from, InsnArg to) {
int count = getArgsCount();
for (int i = 0; i < count; i++) {
if (arguments.get(i) == from) {
// TODO correct remove from use list
// from.getTypedVar().getUseList().remove(from);
setArg(i, to);
return true;
}
}
return false;
}
protected void addReg(DecodedInstruction insn, int i, ArgType type) {
addArg(InsnArg.reg(insn, i, type));
}
protected void addReg(int regNum, ArgType type) {
addArg(InsnArg.reg(regNum, type));
}
protected void addLit(long literal, ArgType type) {
addArg(InsnArg.lit(literal, type));
}
protected void addLit(DecodedInstruction insn, ArgType type) {
addArg(InsnArg.lit(insn, type));
}
public int getOffset() {
return offset;
}
public void setOffset(int offset) {
this.offset = offset;
}
public void getRegisterArgs(List<RegisterArg> list) {
for (InsnArg arg : this.getArguments()) {
if (arg.isRegister())
list.add((RegisterArg) arg);
else if (arg.isInsnWrap())
((InsnWrapArg) arg).getWrapInsn().getRegisterArgs(list);
}
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
+ InsnUtils.insnTypeToString(insnType)
+ (result == null ? "" : result + " = ")
+ Utils.listToString(arguments);
}
public void setInsnHashCode(int insnHashCode) {
this.insnHashCode = insnHashCode;
}
@Override
public int hashCode() {
return insnHashCode;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (hashCode() != obj.hashCode()) return false;
if (!(obj instanceof InsnNode)) return false;
InsnNode other = (InsnNode) obj;
if (insnType != other.insnType) return false;
if (arguments.size() != other.arguments.size()) return false;
// TODO !!! finish equals
return true;
}
}
@@ -0,0 +1,403 @@
package jadx.dex.nodes;
import jadx.dex.attributes.AttrNode;
import jadx.dex.attributes.AttributeFlag;
import jadx.dex.attributes.JumpAttribute;
import jadx.dex.info.AccessInfo;
import jadx.dex.info.AccessInfo.AFType;
import jadx.dex.info.ClassInfo;
import jadx.dex.info.MethodInfo;
import jadx.dex.instructions.GotoNode;
import jadx.dex.instructions.InsnDecoder;
import jadx.dex.instructions.SwitchNode;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.instructions.mods.ConstructorInsn;
import jadx.dex.nodes.parser.DebugInfoParser;
import jadx.dex.trycatch.ExcHandlerAttr;
import jadx.dex.trycatch.ExceptionHandler;
import jadx.dex.trycatch.TryCatchBlock;
import jadx.utils.Utils;
import jadx.utils.exceptions.DecodeException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.io.ClassData.Method;
import com.android.dx.io.Code;
import com.android.dx.io.Code.CatchHandler;
import com.android.dx.io.Code.Try;
public class MethodNode extends AttrNode implements ILoadable {
private final static Logger LOG = LoggerFactory.getLogger(MethodNode.class);
private final MethodInfo mthInfo;
private final ClassNode parentClass;
private final AccessInfo accFlags;
private final Method methodData;
private int regsCount;
private List<InsnNode> instructions;
private boolean noCode;
private RegisterArg thisArg;
private List<RegisterArg> argsList;
private List<BlockNode> blocks;
private BlockNode enterBlock;
private List<BlockNode> exitBlocks;
private ConstructorInsn superCall;
private IContainer region;
private List<ExceptionHandler> exceptionHandlers;
public MethodNode(ClassNode classNode, Method mth) {
this.mthInfo = MethodInfo.fromDex(classNode.dex(), mth.getMethodIndex());
this.parentClass = classNode;
this.accFlags = new AccessInfo(mth.getAccessFlags(), AFType.METHOD);
this.methodData = mth;
if (methodData.getCodeOffset() == 0) {
noCode = true;
regsCount = 0;
initArguments();
} else {
noCode = false;
}
}
@Override
public void load() throws DecodeException {
if (noCode)
return;
try {
DexNode dex = parentClass.dex();
Code mthCode = dex.readCode(methodData);
regsCount = mthCode.getRegistersSize();
InsnDecoder decoder = new InsnDecoder(this, mthCode);
InsnNode[] insnByOffset = decoder.run();
instructions = new ArrayList<InsnNode>();
for (InsnNode insn : insnByOffset) {
if (insn != null)
instructions.add(insn);
}
((ArrayList<InsnNode>) instructions).trimToSize();
initArguments();
initTryCatches(mthCode, insnByOffset);
initJumps(insnByOffset);
if (mthCode.getDebugInfoOffset() > 0) {
DebugInfoParser debugInfo = new DebugInfoParser(this, dex.openSection(mthCode.getDebugInfoOffset()));
debugInfo.process(insnByOffset);
}
} catch (Exception e) {
throw new DecodeException(this, "Load method exception", e);
}
}
@Override
public void unload() {
if (noCode)
return;
if (instructions != null) instructions.clear();
// if (blocks != null) blocks.clear();
// if (exitBlocks != null) exitBlocks.clear();
blocks = null;
exitBlocks = null;
if (exceptionHandlers != null) exceptionHandlers.clear();
getAttributes().clear();
noCode = true;
}
private void initArguments() {
List<ArgType> args = mthInfo.getArgumentsTypes();
int pos;
if (!noCode) {
pos = regsCount;
for (ArgType arg : args)
pos -= arg.getRegCount();
} else {
pos = 2 * args.size() + 1;
}
if (accFlags.isStatic()) {
thisArg = null;
} else {
thisArg = InsnArg.reg(pos - 1, parentClass.getClassInfo().getType());
thisArg.getTypedVar().setName("this");
}
argsList = new ArrayList<RegisterArg>(args.size());
for (ArgType arg : args) {
argsList.add(InsnArg.reg(pos, arg));
pos += arg.getRegCount();
}
}
public List<RegisterArg> getArguments(boolean includeThis) {
if (includeThis && thisArg != null) {
List<RegisterArg> list = new ArrayList<RegisterArg>(argsList.size() + 1);
list.add(thisArg);
list.addAll(argsList);
return list;
} else {
return argsList;
}
}
public RegisterArg getThisArg() {
return thisArg;
}
// TODO: args types can change during type resolving => reset and copy back names
@Deprecated
public void resetArgsTypes() {
List<InsnArg> modArgs = new ArrayList<InsnArg>(argsList);
initArguments();
for (int i = 0; i < argsList.size(); i++) {
argsList.get(i).getTypedVar().setName(modArgs.get(i).getTypedVar().getName());
}
}
// move to external class
private void initTryCatches(Code mthCode, InsnNode[] insnByOffset) {
CatchHandler[] catchBlocks = mthCode.getCatchHandlers();
Try[] tries = mthCode.getTries();
// Bug in dx library already fixed (Try.getHandlerOffset() replaced by Try.getCatchHandlerIndex())
// and we don't need this mapping anymore,
// but in maven repository still old version
Set<Integer> handlerSet = new HashSet<Integer>(tries.length);
for (Try try_ : tries) {
handlerSet.add(try_.getHandlerOffset());
}
List<Integer> handlerList = new ArrayList<Integer>(catchBlocks.length);
handlerList.addAll(handlerSet);
Collections.sort(handlerList);
handlerSet = null;
// -------------------
int hc = 0;
Set<Integer> addrs = new HashSet<Integer>();
List<TryCatchBlock> catches = new ArrayList<TryCatchBlock>(catchBlocks.length);
for (CatchHandler catch_ : catchBlocks) {
TryCatchBlock tcBlock = new TryCatchBlock();
catches.add(tcBlock);
for (int i = 0; i < catch_.getAddresses().length; i++) {
int addr = catch_.getAddresses()[i];
ClassInfo type = ClassInfo.fromDex(parentClass.dex(), catch_.getTypeIndexes()[i]);
tcBlock.addHandler(this, addr, type);
addrs.add(addr);
hc++;
}
int addr = catch_.getCatchAllAddress();
if (addr >= 0) {
tcBlock.addHandler(this, addr, null);
addrs.add(addr);
hc++;
}
}
if (hc > 0 && hc != addrs.size()) {
// resolve nested try blocks:
// inner block contains all handlers from outer block => remove these handlers from inner block
// each handler must be only in one try/catch block
for (TryCatchBlock ct1 : catches)
for (TryCatchBlock ct2 : catches)
if (ct1 != ct2 && ct2.getHandlers().containsAll(ct1.getHandlers()))
for (ExceptionHandler h : ct1.getHandlers())
ct2.removeHandler(this, h);
}
// attach EXC_HANDLER attributes to instructions
addrs.clear();
for (TryCatchBlock ct : catches) {
for (ExceptionHandler eh : ct.getHandlers()) {
int addr = eh.getHandleOffset();
// assert addrs.add(addr) : "Instruction already contains EXC_HANDLER attribute";
ExcHandlerAttr ehAttr = new ExcHandlerAttr(ct, eh);
insnByOffset[addr].getAttributes().add(ehAttr);
}
}
// attach TRY_ENTER, TRY_LEAVE attributes to instructions
for (Try try_ : tries) {
int catchNum = handlerList.indexOf(try_.getHandlerOffset());
TryCatchBlock block = catches.get(catchNum);
int offset = try_.getStartAddress();
int end = offset + try_.getInstructionCount() - 1;
insnByOffset[offset].getAttributes().add(AttributeFlag.TRY_ENTER);
while (offset <= end && offset >= 0) {
block.addInsn(insnByOffset[offset]);
offset = InsnDecoder.getNextInsnOffset(insnByOffset, offset);
}
if (insnByOffset[end] != null)
insnByOffset[end].getAttributes().add(AttributeFlag.TRY_LEAVE);
}
}
private void initJumps(InsnNode[] insnByOffset) {
for (InsnNode insn : getInstructions()) {
int offset = insn.getOffset();
switch (insn.getType()) {
case SWITCH: {
SwitchNode sw = (SwitchNode) insn;
for (int target : sw.getTargets()) {
insnByOffset[target].getAttributes().add(new JumpAttribute(offset, target));
}
// default case
int next = InsnDecoder.getNextInsnOffset(insnByOffset, offset);
if (next != -1)
insnByOffset[next].getAttributes().add(new JumpAttribute(offset, next));
break;
}
case IF:
int next = InsnDecoder.getNextInsnOffset(insnByOffset, offset);
if (next != -1)
insnByOffset[next].getAttributes().add(new JumpAttribute(offset, next));
// no break
case GOTO:
int target = ((GotoNode) insn).getTarget();
insnByOffset[target].getAttributes().add(new JumpAttribute(offset, target));
break;
default:
break;
}
}
}
public String getName() {
String name = mthInfo.getName();
if (name.equals(parentClass.getShortName()))
return name + "_";
else
return name;
}
public ClassNode getParentClass() {
return parentClass;
}
public boolean isNoCode() {
return noCode;
}
public List<InsnNode> getInstructions() {
return instructions;
}
public void initBasicBlocks() {
blocks = new ArrayList<BlockNode>();
exitBlocks = new ArrayList<BlockNode>(1);
}
public void finishBasicBlocks() {
// after filling basic blocks we don't need instructions list anymore
instructions.clear();
instructions = null;
((ArrayList<BlockNode>) blocks).trimToSize();
((ArrayList<BlockNode>) exitBlocks).trimToSize();
blocks = Collections.unmodifiableList(blocks);
exitBlocks = Collections.unmodifiableList(exitBlocks);
for (BlockNode block : blocks)
block.lock();
}
public List<BlockNode> getBasicBlocks() {
return blocks;
}
public BlockNode getEnterBlock() {
return enterBlock;
}
public void setEnterBlock(BlockNode enterBlock) {
this.enterBlock = enterBlock;
}
public List<BlockNode> getExitBlocks() {
return exitBlocks;
}
public void addExitBlock(BlockNode exitBlock) {
this.exitBlocks.add(exitBlock);
}
public ExceptionHandler addExceptionHandler(ExceptionHandler handler) {
if (exceptionHandlers == null) {
exceptionHandlers = new ArrayList<ExceptionHandler>(2);
} else {
for (ExceptionHandler h : exceptionHandlers) {
if (h == handler || h.getHandleOffset() == handler.getHandleOffset())
return h;
}
}
exceptionHandlers.add(handler);
return handler;
}
public List<ExceptionHandler> getExceptionHandlers() {
return exceptionHandlers;
}
public int getRegsCount() {
return regsCount;
}
public AccessInfo getAccessFlags() {
return accFlags;
}
public void setSuperCall(ConstructorInsn insn) {
this.superCall = insn;
}
public ConstructorInsn getSuperCall() {
return this.superCall;
}
public IContainer getRegion() {
return region;
}
public void setRegion(IContainer region) {
this.region = region;
}
public DexNode dex() {
return parentClass.dex();
}
public MethodInfo getMethodInfo() {
return mthInfo;
}
@Override
public String toString() {
return mthInfo.getReturnType()
+ " " + parentClass.getFullName() + "." + mthInfo.getName()
+ "(" + Utils.listToString(mthInfo.getArgumentsTypes()) + ")";
}
}
+103
View File
@@ -0,0 +1,103 @@
package jadx.dex.nodes;
import jadx.JadxArgs;
import jadx.dex.info.ClassInfo;
import jadx.utils.exceptions.DecodeException;
import jadx.utils.files.InputFile;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RootNode {
private final static Logger LOG = LoggerFactory.getLogger(RootNode.class);
private final JadxArgs jadxArgs;
private List<DexNode> dexNodes;
private final List<ClassNode> classes = new ArrayList<ClassNode>();
private final Set<String> earlyClassList = new HashSet<String>();
private final Map<String, ClassNode> names = new HashMap<String, ClassNode>();
public RootNode(JadxArgs args) {
this.jadxArgs = args;
}
public void load() throws DecodeException {
List<InputFile> dexFiles = jadxArgs.getInput();
dexNodes = new ArrayList<DexNode>(dexFiles.size());
for (InputFile dex : dexFiles) {
DexNode dexNode;
try {
dexNode = new DexNode(this, dex);
} catch (Exception e) {
throw new DecodeException("Error decode file: " + dex, e);
}
dexNodes.add(dexNode);
}
for (DexNode dexNode : dexNodes)
earlyClassList.addAll(dexNode.getAllClassesNames());
for (DexNode dexNode : dexNodes)
dexNode.loadClasses(this);
for (DexNode dexNode : dexNodes) {
for (ClassNode cls : dexNode.getClasses())
names.put(cls.getFullName(), cls);
classes.addAll(dexNode.getClasses());
}
}
public void init() {
// move inner classes
List<ClassNode> inner = new ArrayList<ClassNode>();
for (ClassNode cls : getClasses()) {
if (cls.getClassInfo().isInner())
inner.add(cls);
}
getClasses().removeAll(inner);
for (ClassNode cls : inner) {
ClassNode parent = resolveClass(cls.getClassInfo().getParentClass());
if (parent == null)
LOG.warn("Can't add inner class: {} to {}", cls, cls.getClassInfo().getParentClass());
else
parent.addInnerClass(cls);
}
inner.clear();
}
public List<ClassNode> getClasses() {
return classes;
}
public ClassNode searchClassByName(String fullName) {
return names.get(fullName);
}
public boolean isClassExists(String fullName) {
return earlyClassList.contains(fullName);
}
public ClassNode resolveClass(ClassInfo cls) {
String fullName = cls.getFullName();
ClassNode rCls = searchClassByName(fullName);
return rCls;
}
public List<DexNode> getDexNodes() {
return dexNodes;
}
public JadxArgs getJadxArgs() {
return jadxArgs;
}
}
@@ -0,0 +1,103 @@
package jadx.dex.nodes.parser;
import jadx.dex.attributes.annotations.Annotation;
import jadx.dex.attributes.annotations.Annotation.Visibility;
import jadx.dex.attributes.annotations.AnnotationsList;
import jadx.dex.attributes.annotations.MethodParameters;
import jadx.dex.nodes.ClassNode;
import jadx.dex.nodes.DexNode;
import jadx.dex.nodes.FieldNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.exceptions.DecodeException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.android.dx.io.DexBuffer.Section;
public class AnnotationsParser {
private final static Logger LOG = LoggerFactory.getLogger(AnnotationsParser.class);
private final DexNode dex;
public AnnotationsParser(ClassNode cls, int offset) throws DecodeException {
this.dex = cls.dex();
Section section = dex.openSection(offset);
// TODO read as unsigned int
int class_annotations_off = section.readInt();
int fields_size = section.readInt();
int annotated_methods_size = section.readInt();
int annotated_parameters_size = section.readInt();
if (class_annotations_off != 0) {
cls.getAttributes().add(readAnnotationSet(class_annotations_off));
}
for (int i = 0; i < fields_size; i++) {
FieldNode f = cls.searchFieldById(section.readInt());
f.getAttributes().add(readAnnotationSet(section.readInt()));
}
for (int i = 0; i < annotated_methods_size; i++) {
MethodNode m = cls.searchMethodById(section.readInt());
m.getAttributes().add(readAnnotationSet(section.readInt()));
// LOG.info(m + " " + m.getAttributes());
}
for (int i = 0; i < annotated_parameters_size; i++) {
MethodNode mth = cls.searchMethodById(section.readInt());
// read annotation ref list
Section ss = dex.openSection(section.readInt());
int size = ss.readInt();
MethodParameters params = new MethodParameters(size);
for (int j = 0; j < size; j++) {
params.getParamList().add(readAnnotationSet(ss.readInt()));
}
mth.getAttributes().add(params);
}
}
private AnnotationsList readAnnotationSet(int offset) throws DecodeException {
Section section = dex.openSection(offset);
int size = section.readInt();
if (size > 100)
section.toString();
List<Annotation> list = new ArrayList<Annotation>(size);
for (int i = 0; i < size; i++) {
Section anSection = dex.openSection(section.readInt());
Annotation a = readAnnotation(dex, anSection, true);
list.add(a);
// LOG.debug(" + " + a);
}
return new AnnotationsList(list);
}
private static final Annotation.Visibility[] visibilities = new Annotation.Visibility[] {
Annotation.Visibility.BUILD,
Annotation.Visibility.RUNTIME,
Annotation.Visibility.SYSTEM
};
public static Annotation readAnnotation(DexNode dex, Section s, boolean readVisibility) throws DecodeException {
EncValueParser ep = new EncValueParser(dex, s);
Visibility visibility = null;
if (readVisibility)
visibility = visibilities[s.readByte()];
int typeIndex = s.readUleb128();
int size = s.readUleb128();
Map<String, Object> values = new HashMap<String, Object>(size);
for (int i = 0; i < size; i++) {
String name = dex.getString(s.readUleb128());
values.put(name, ep.parseValue());
}
return new Annotation(visibility, dex.getType(typeIndex), values);
}
}
@@ -0,0 +1,157 @@
package jadx.dex.nodes.parser;
import jadx.dex.info.LocalVarInfo;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.nodes.DexNode;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.exceptions.DecodeException;
import java.util.List;
import com.android.dx.io.DexBuffer.Section;
public class DebugInfoParser {
private final static int DBG_END_SEQUENCE = 0x00;
private final static int DBG_ADVANCE_PC = 0x01;
private final static int DBG_ADVANCE_LINE = 0x02;
private final static int DBG_START_LOCAL = 0x03;
private final static int DBG_START_LOCAL_EXTENDED = 0x04;
private final static int DBG_END_LOCAL = 0x05;
private final static int DBG_RESTART_LOCAL = 0x06;
private final static int DBG_SET_PROLOGUE_END = 0x07;
private final static int DBG_SET_EPILOGUE_BEGIN = 0x08;
private final static int DBG_SET_FILE = 0x09;
private final static int DBG_FIRST_SPECIAL = 0x0a; // the smallest special opcode
private final static int DBG_LINE_BASE = -4; // the smallest line number increment
private final static int DBG_LINE_RANGE = 15; // the number of line increments represented
private final MethodNode mth;
private final Section section;
private final DexNode dex;
public DebugInfoParser(MethodNode mth, Section section) {
this.mth = mth;
this.section = section;
this.dex = mth.dex();
}
public void process(InsnNode[] insnByOffset) throws DecodeException {
int addr = 0;
int line;
// String source_file;
line = section.readUleb128();
int param_size = section.readUleb128(); // exclude 'this'
List<RegisterArg> mthArgs = mth.getArguments(false);
assert param_size == mthArgs.size();
for (int i = 0; i < param_size; i++) {
int id = section.readUleb128() - 1;
if (id != DexNode.NO_INDEX) {
String name = dex.getString(id);
mthArgs.get(i).getTypedVar().setName(name);
}
}
LocalVarInfo[] locals = new LocalVarInfo[mth.getRegsCount()];
for (RegisterArg arg : mthArgs) {
locals[arg.getRegNum()] = new LocalVarInfo(dex, arg.getRegNum(),
arg.getTypedVar().getName(), arg.getType(), null);
}
int c = section.readByte() & 0xFF;
while (c != DBG_END_SEQUENCE) {
switch (c) {
case DBG_ADVANCE_PC:
addr += section.readUleb128();
break;
case DBG_ADVANCE_LINE:
line += section.readSleb128();
break;
case DBG_START_LOCAL: {
int regNum = section.readUleb128();
int nameId = section.readUleb128() - 1;
int type = section.readUleb128() - 1;
locals[regNum] = new LocalVarInfo(dex, regNum, nameId, type, DexNode.NO_INDEX);
locals[regNum].start(addr, line);
break;
}
case DBG_START_LOCAL_EXTENDED: {
int regNum = section.readUleb128();
int nameId = section.readUleb128() - 1;
int type = section.readUleb128() - 1;
int sign = section.readUleb128() - 1;
locals[regNum] = new LocalVarInfo(dex, regNum, nameId, type, sign);
locals[regNum].start(addr, line);
break;
}
case DBG_RESTART_LOCAL: {
int regNum = section.readUleb128();
if (locals[regNum] != null)
locals[regNum].start(addr, line);
break;
}
case DBG_END_LOCAL: {
int regNum = section.readUleb128();
if (locals[regNum] != null)
locals[regNum].end(addr, line);
break;
}
case DBG_SET_PROLOGUE_END:
break;
case DBG_SET_EPILOGUE_BEGIN:
break;
case DBG_SET_FILE:
section.readUleb128();
// source_file = dex.getString(idx);
break;
default:
if (c >= DBG_FIRST_SPECIAL) {
int adjusted_opcode = c - DBG_FIRST_SPECIAL;
line += DBG_LINE_BASE + (adjusted_opcode % DBG_LINE_RANGE);
addr += (adjusted_opcode / DBG_LINE_RANGE);
fillLocals(insnByOffset[addr], locals);
} else {
throw new DecodeException("Unknown debug insn code: " + c);
}
break;
}
c = section.readByte() & 0xFF;
}
}
private void fillLocals(InsnNode insn, LocalVarInfo[] locals) {
if (insn == null)
return;
if (insn.getResult() != null)
merge(insn.getResult(), locals);
for (InsnArg arg : insn.getArguments())
merge(arg, locals);
}
private void merge(InsnArg arg, LocalVarInfo[] locals) {
if (arg.isRegister()) {
int rn = ((RegisterArg) arg).getRegNum();
for (LocalVarInfo var : locals) {
if (var != null && !var.isEnd()) {
if (var.getRegNum() == rn)
arg.replace(var);
}
}
}
}
}
@@ -0,0 +1,87 @@
package jadx.dex.nodes.parser;
import jadx.dex.info.FieldInfo;
import jadx.dex.info.MethodInfo;
import jadx.dex.nodes.DexNode;
import jadx.utils.exceptions.DecodeException;
import java.util.ArrayList;
import java.util.List;
import com.android.dx.io.DexBuffer.Section;
import com.android.dx.io.EncodedValueReader;
import com.android.dx.util.Leb128Utils;
public class EncValueParser extends EncodedValueReader {
private final DexNode dex;
public EncValueParser(DexNode dex, Section in) {
super(in);
this.dex = dex;
}
public Object parseValue() throws DecodeException {
int argAndType = in.readByte() & 0xff;
int type = argAndType & 0x1f;
int arg = (argAndType & 0xe0) >> 5;
int size = arg + 1;
switch (type) {
case ENCODED_NULL:
return null;
case ENCODED_BOOLEAN:
return Boolean.valueOf(arg == 1);
case ENCODED_BYTE:
return Byte.valueOf((byte) parseNumber(size));
case ENCODED_SHORT:
return Short.valueOf((short) parseNumber(size));
case ENCODED_CHAR:
return Character.valueOf((char) parseNumber(size));
case ENCODED_INT:
return Integer.valueOf((int) parseNumber(size));
case ENCODED_LONG:
return Long.valueOf(parseNumber(size));
case ENCODED_FLOAT:
return Float.intBitsToFloat((int) parseNumber(size));
case ENCODED_DOUBLE:
return Double.longBitsToDouble(parseNumber(size));
case ENCODED_STRING:
return dex.getString((int) parseNumber(size));
case ENCODED_TYPE:
return dex.getType((int) parseNumber(size));
case ENCODED_METHOD:
return MethodInfo.fromDex(dex, (int) parseNumber(size));
case ENCODED_FIELD:
case ENCODED_ENUM:
return FieldInfo.fromDex(dex, (int) parseNumber(size));
case ENCODED_ARRAY:
int count = Leb128Utils.readUnsignedLeb128(in);
List<Object> values = new ArrayList<Object>(count);
for (int i = 0; i < count; i++) {
values.add(parseValue());
}
return values;
case ENCODED_ANNOTATION:
return AnnotationsParser.readAnnotation(dex, (Section) in, false);
}
throw new DecodeException("Unknown encoded value type: 0x" + Integer.toHexString(type));
}
private long parseNumber(int byteCount) {
long result = 0;
int shift = 0;
for (int i = 0; i < byteCount; i++) {
result |= (long) (in.readByte() & 0xff) << shift;
shift += 8;
}
return result;
}
}
@@ -0,0 +1,27 @@
package jadx.dex.nodes.parser;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.IAttribute;
public class FieldValueAttr implements IAttribute {
private final Object value;
public FieldValueAttr(Object value) {
this.value = value;
}
@Override
public AttributeType getType() {
return AttributeType.FIELD_VALUE;
}
public Object getValue() {
return value;
}
@Override
public String toString() {
return "V=" + value;
}
}
@@ -0,0 +1,27 @@
package jadx.dex.nodes.parser;
import jadx.dex.nodes.DexNode;
import jadx.dex.nodes.FieldNode;
import jadx.utils.exceptions.DecodeException;
import java.util.List;
import com.android.dx.io.DexBuffer.Section;
import com.android.dx.util.Leb128Utils;
public class StaticValuesParser extends EncValueParser {
public StaticValuesParser(DexNode dex, Section in) {
super(dex, in);
}
public void processFields(List<FieldNode> fields) throws DecodeException {
int size = Leb128Utils.readUnsignedLeb128(in);
visitArray(size);
for (int i = 0; i < size; i++) {
Object value = parseValue();
fields.get(i).getAttributes().add(new FieldValueAttr(value));
}
}
}
@@ -0,0 +1,19 @@
package jadx.dex.regions;
import jadx.dex.attributes.AttrNode;
import jadx.dex.nodes.IRegion;
public abstract class AbstractRegion extends AttrNode implements IRegion {
private final IRegion parent;
public AbstractRegion(IRegion parent) {
this.parent = parent;
}
@Override
public IRegion getParent() {
return parent;
}
}
@@ -0,0 +1,62 @@
package jadx.dex.regions;
import jadx.dex.instructions.IfNode;
import jadx.dex.nodes.BlockNode;
import jadx.dex.nodes.IContainer;
import jadx.dex.nodes.IRegion;
import java.util.ArrayList;
import java.util.List;
public final class IfRegion extends AbstractRegion {
protected BlockNode header;
protected IContainer thenRegion;
protected IContainer elseRegion;
public IfRegion(IRegion parent, BlockNode header) {
super(parent);
assert header.getInstructions().size() == 1;
this.header = header;
}
public IfNode getIfInsn() {
return (IfNode) header.getInstructions().get(0);
}
public BlockNode getHeader() {
return header;
}
public IContainer getThenRegion() {
return thenRegion;
}
public void setThenRegion(IContainer thenRegion) {
this.thenRegion = thenRegion;
}
public IContainer getElseRegion() {
return elseRegion;
}
public void setElseRegion(IContainer elseRegion) {
this.elseRegion = elseRegion;
}
@Override
public List<IContainer> getSubBlocks() {
ArrayList<IContainer> all = new ArrayList<IContainer>(3);
all.add(header);
if (thenRegion != null)
all.add(thenRegion);
if (elseRegion != null)
all.add(elseRegion);
return all;
}
@Override
public String toString() {
return "IF(" + header + ") then " + thenRegion + " else " + elseRegion;
}
}
@@ -0,0 +1,122 @@
package jadx.dex.regions;
import jadx.dex.instructions.IfNode;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.nodes.BlockNode;
import jadx.dex.nodes.IContainer;
import jadx.dex.nodes.IRegion;
import jadx.dex.nodes.InsnNode;
import java.util.ArrayList;
import java.util.List;
public final class LoopRegion extends AbstractRegion {
// loop header contains one 'if' insn, equals null for infinite loop
private final BlockNode conditionBlock;
// instruction which must be executed before condition in every loop
private BlockNode preCondition = null;
private IContainer body;
private final boolean conditionAtEnd;
public LoopRegion(IRegion parent, BlockNode header, boolean reversed) {
super(parent);
this.conditionBlock = header;
this.conditionAtEnd = reversed;
}
public IfNode getIfInsn() {
return (IfNode) conditionBlock.getInstructions().get(conditionBlock.getInstructions().size() - 1);
}
public BlockNode getHeader() {
return conditionBlock;
}
public BlockNode getConditionBlock() {
return conditionBlock;
}
public IContainer getBody() {
return body;
}
public void setBody(IContainer body) {
this.body = body;
}
public boolean isConditionAtEnd() {
return conditionAtEnd;
}
/**
* Set instructions which must be executed before condition in every loop
*/
public void setPreCondition(BlockNode preCondition) {
this.preCondition = preCondition;
}
/**
* Check if pre-conditions can be inlined into loop condition
*/
public boolean checkPreCondition() {
List<InsnNode> insns = preCondition.getInstructions();
if (insns.isEmpty())
return true;
IfNode ifInsn = getIfInsn();
int size = insns.size();
for (int i = 0; i < size; i++) {
InsnNode insn = insns.get(i);
if (insn.getResult() == null) {
return false;
} else {
RegisterArg res = insn.getResult();
if (res.getTypedVar().getUseList().size() > 2)
return false;
boolean found = false;
// search result arg in other insns
for (int j = i + 1; j < size; j++) {
if (insns.get(i).containsArg(res))
found = true;
}
// or in if insn
if (!found && ifInsn.containsArg(res))
found = true;
if (!found)
return false;
}
}
return true;
}
/**
* Move all preCondition block instructions before conditionBlock instructions
*/
public void mergePreCondition() {
if (preCondition != null && conditionBlock != null) {
preCondition.getInstructions().addAll(conditionBlock.getInstructions());
conditionBlock.getInstructions().clear();
conditionBlock.getInstructions().addAll(preCondition.getInstructions());
preCondition.getInstructions().clear();
}
}
@Override
public List<IContainer> getSubBlocks() {
List<IContainer> all = new ArrayList<IContainer>(3);
if (preCondition != null)
all.add(preCondition);
if (conditionBlock != null)
all.add(conditionBlock);
all.add(body);
return all;
}
@Override
public String toString() {
return "LOOP";
}
}
@@ -0,0 +1,38 @@
package jadx.dex.regions;
import jadx.dex.nodes.BlockNode;
import jadx.dex.nodes.IContainer;
import jadx.dex.nodes.IRegion;
import java.util.ArrayList;
import java.util.List;
public final class Region extends AbstractRegion {
private final List<IContainer> blocks;
public Region(IRegion parent) {
super(parent);
this.blocks = new ArrayList<IContainer>(1);
}
@Override
public List<IContainer> getSubBlocks() {
return blocks;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("R:");
sb.append(blocks.size());
if (blocks.size() != 0) {
for (IContainer cont : blocks) {
if (cont instanceof BlockNode)
sb.append(((BlockNode) cont).getId());
}
}
return sb.toString();
}
}
@@ -0,0 +1,64 @@
package jadx.dex.regions;
import jadx.dex.nodes.BlockNode;
import jadx.dex.nodes.IContainer;
import jadx.dex.nodes.IRegion;
import java.util.ArrayList;
import java.util.List;
public final class SwitchRegion extends AbstractRegion {
private final BlockNode header;
private final List<List<Integer>> keys;
private final List<IContainer> cases;
private IContainer defCase;
public SwitchRegion(IRegion parent, BlockNode header) {
super(parent);
this.header = header;
this.keys = new ArrayList<List<Integer>>();
this.cases = new ArrayList<IContainer>();
}
public BlockNode getHeader() {
return header;
}
public void addCase(List<Integer> keysList, IContainer c) {
keys.add(keysList);
cases.add(c);
}
public void setDefaultCase(IContainer block) {
defCase = block;
}
public IContainer getDefaultCase() {
return defCase;
}
public List<List<Integer>> getKeys() {
return keys;
}
public List<IContainer> getCases() {
return cases;
}
@Override
public List<IContainer> getSubBlocks() {
List<IContainer> all = new ArrayList<IContainer>(cases.size() + 2);
all.add(header);
all.addAll(cases);
if (defCase != null)
all.add(defCase);
return all;
}
@Override
public String toString() {
return "Switch: " + cases.size() + ", default: " + defCase;
}
}
@@ -0,0 +1,37 @@
package jadx.dex.regions;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.nodes.IContainer;
import jadx.dex.nodes.IRegion;
import java.util.List;
public final class SynchronizedRegion extends AbstractRegion {
private final RegisterArg arg;
private final Region region;
public SynchronizedRegion(IRegion parent, RegisterArg arg) {
super(parent);
this.arg = arg;
this.region = new Region(this);
}
public RegisterArg getArg() {
return arg;
}
public Region getRegion() {
return region;
}
@Override
public List<IContainer> getSubBlocks() {
return region.getSubBlocks();
}
@Override
public String toString() {
return "Synchronized:" + region;
}
}
@@ -0,0 +1,28 @@
package jadx.dex.trycatch;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.IAttribute;
public class CatchAttr implements IAttribute {
private final TryCatchBlock tryBlock;
public CatchAttr(TryCatchBlock block) {
this.tryBlock = block;
}
@Override
public AttributeType getType() {
return AttributeType.CATCH_BLOCK;
}
public TryCatchBlock getTryBlock() {
return tryBlock;
}
@Override
public String toString() {
return tryBlock.toString();
}
}
@@ -0,0 +1,33 @@
package jadx.dex.trycatch;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.IAttribute;
public class ExcHandlerAttr implements IAttribute {
private final TryCatchBlock tryBlock;
private final ExceptionHandler handler;
public ExcHandlerAttr(TryCatchBlock block, ExceptionHandler handler) {
this.tryBlock = block;
this.handler = handler;
}
@Override
public AttributeType getType() {
return AttributeType.EXC_HANDLER;
}
public TryCatchBlock getTryBlock() {
return tryBlock;
}
public ExceptionHandler getHandler() {
return handler;
}
@Override
public String toString() {
return "ExcHandler: " + (handler.isCatchAll() ? "all" : handler.getCatchType());
}
}
@@ -0,0 +1,95 @@
package jadx.dex.trycatch;
import jadx.Consts;
import jadx.dex.info.ClassInfo;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.nodes.BlockNode;
import jadx.dex.nodes.IContainer;
import jadx.utils.InsnUtils;
import java.util.ArrayList;
import java.util.List;
public class ExceptionHandler {
private final ClassInfo catchType;
private final int handleOffset;
private BlockNode handleBlock;
private final List<BlockNode> blocks = new ArrayList<BlockNode>();
private IContainer handlerRegion;
private InsnArg arg;
public ExceptionHandler(int addr, ClassInfo type) {
this.handleOffset = addr;
this.catchType = type;
}
public ClassInfo getCatchType() {
return catchType;
}
public boolean isCatchAll() {
return catchType == null || catchType.getFullName().equals(Consts.CLASS_THROWABLE);
}
public int getHandleOffset() {
return handleOffset;
}
public BlockNode getHandleBlock() {
return handleBlock;
}
public void setHandleBlock(BlockNode handleBlock) {
this.handleBlock = handleBlock;
}
public List<BlockNode> getBlocks() {
return blocks;
}
public void addBlock(BlockNode node) {
blocks.add(node);
}
public IContainer getHandlerRegion() {
return handlerRegion;
}
public void setHandlerRegion(IContainer handlerRegion) {
this.handlerRegion = handlerRegion;
}
public InsnArg getArg() {
return arg;
}
public void setArg(InsnArg arg) {
this.arg = arg;
}
@Override
public int hashCode() {
return 31 * (catchType == null ? 0 : catchType.hashCode()) + handleOffset;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
ExceptionHandler other = (ExceptionHandler) obj;
if (catchType == null) {
if (other.catchType != null) return false;
} else if (!catchType.equals(other.catchType)) return false;
return handleOffset == other.handleOffset;
}
@Override
public String toString() {
return (catchType == null ? "all" : catchType.getShortName())
+ " -> " + InsnUtils.formatOffset(handleOffset);
}
}
@@ -0,0 +1,29 @@
package jadx.dex.trycatch;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.IAttribute;
import jadx.dex.nodes.BlockNode;
public class SplitterBlockAttr implements IAttribute {
private final BlockNode block;
public SplitterBlockAttr(BlockNode block) {
this.block = block;
}
public BlockNode getBlock() {
return block;
}
@Override
public AttributeType getType() {
return AttributeType.SPLITTER_BLOCK;
}
@Override
public String toString() {
return "Splitter: " + block;
}
}
@@ -0,0 +1,131 @@
package jadx.dex.trycatch;
import jadx.dex.attributes.AttributeType;
import jadx.dex.info.ClassInfo;
import jadx.dex.nodes.BlockNode;
import jadx.dex.nodes.IContainer;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.Utils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class TryCatchBlock {
private final List<ExceptionHandler> handlers;
private IContainer finalBlock;
// references for fast remove/modify
private final List<InsnNode> insns;
private final CatchAttr attr;
public TryCatchBlock() {
handlers = new ArrayList<ExceptionHandler>(2);
insns = new ArrayList<InsnNode>();
attr = new CatchAttr(this);
}
public Collection<ExceptionHandler> getHandlers() {
return Collections.unmodifiableCollection(handlers);
}
public ExceptionHandler addHandler(MethodNode mth, int addr, ClassInfo type) {
ExceptionHandler handler = new ExceptionHandler(addr, type);
handler = mth.addExceptionHandler(handler);
handlers.add(handler);
return handler;
}
public void removeHandler(MethodNode mth, ExceptionHandler handler) {
for (int i = 0; i < handlers.size(); i++) {
if (handlers.get(i) == handler) {
handlers.remove(i);
break;
}
}
if (handlers.isEmpty()) {
removeWholeBlock(mth);
}
}
private void removeWholeBlock(MethodNode mth) {
// self destruction
for (InsnNode insn : insns)
insn.getAttributes().remove(AttributeType.CATCH_BLOCK);
insns.clear();
for (BlockNode block : mth.getBasicBlocks()) {
block.getAttributes().remove(AttributeType.CATCH_BLOCK);
}
}
public void addInsn(InsnNode insn) {
insns.add(insn);
insn.getAttributes().add(attr);
}
public void removeInsn(InsnNode insn) {
insns.remove(insn);
insn.getAttributes().remove(attr.getType());
}
public Iterable<InsnNode> getInsns() {
return insns;
}
public int getInsnsCount() {
return insns.size();
}
public CatchAttr getCatchAttr() {
return attr;
}
public IContainer getFinalBlock() {
return finalBlock;
}
public void setFinalBlock(IContainer finalBlock) {
this.finalBlock = finalBlock;
}
public void merge(MethodNode mth, TryCatchBlock tryBlock) {
for (InsnNode insn : tryBlock.getInsns())
this.addInsn(insn);
this.handlers.addAll(tryBlock.getHandlers());
// clear
tryBlock.handlers.clear();
tryBlock.removeWholeBlock(mth);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((handlers == null) ? 0 : handlers.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
TryCatchBlock other = (TryCatchBlock) obj;
if (handlers == null) {
if (other.handlers != null) return false;
} else if (!handlers.equals(other.handlers)) return false;
return true;
}
@Override
public String toString() {
return "Catch:{ " + Utils.listToString(handlers) + " }";
}
}
@@ -0,0 +1,18 @@
package jadx.dex.visitors;
import jadx.dex.nodes.ClassNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.exceptions.JadxException;
public class AbstractVisitor implements IDexTreeVisitor {
@Override
public boolean visit(ClassNode cls) throws JadxException {
return true;
}
@Override
public void visit(MethodNode mth) throws JadxException {
}
}
@@ -0,0 +1,393 @@
package jadx.dex.visitors;
import jadx.dex.attributes.AttributeFlag;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.AttributesList;
import jadx.dex.attributes.IAttribute;
import jadx.dex.attributes.JumpAttribute;
import jadx.dex.attributes.LoopAttr;
import jadx.dex.instructions.IfNode;
import jadx.dex.instructions.InsnType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.nodes.BlockNode;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.dex.trycatch.CatchAttr;
import jadx.dex.trycatch.ExceptionHandler;
import jadx.dex.trycatch.SplitterBlockAttr;
import jadx.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BlockMakerVisitor extends AbstractVisitor {
private final static Logger LOG = LoggerFactory.getLogger(BlockMakerVisitor.class);
// leave these instructions alone in block node
private final static Set<InsnType> separateInsns = EnumSet.of(
InsnType.IF,
InsnType.SWITCH,
InsnType.MONITOR_ENTER,
InsnType.MONITOR_EXIT);
@Override
public void visit(MethodNode mth) {
if (mth.isNoCode())
return;
mth.initBasicBlocks();
BlockNode.initialID = 0;
InsnNode prevInsn = null;
Map<Integer, BlockNode> blocksMap = new HashMap<Integer, BlockNode>();
BlockNode curBlock = startNewBlock(mth, 0);
mth.setEnterBlock(curBlock);
// split into blocks
for (InsnNode insn : mth.getInstructions()) {
boolean startNew = false;
if (prevInsn != null) {
InsnType type = prevInsn.getType();
if (type == InsnType.RETURN
|| type == InsnType.GOTO
|| type == InsnType.THROW
|| separateInsns.contains(type)) {
if (type == InsnType.RETURN || type == InsnType.THROW)
mth.addExitBlock(curBlock);
BlockNode block = startNewBlock(mth, insn.getOffset());
if (type == InsnType.MONITOR_ENTER || type == InsnType.MONITOR_EXIT)
connect(curBlock, block);
curBlock = block;
startNew = true;
} else {
type = insn.getType();
startNew = separateInsns.contains(type);
List<IAttribute> pjumps = prevInsn.getAttributes().getAll(AttributeType.JUMP);
if (pjumps.size() > 0) {
for (IAttribute j : pjumps) {
JumpAttribute jump = (JumpAttribute) j;
if (jump.getSrc() == prevInsn.getOffset())
startNew = true;
}
}
List<IAttribute> cjumps = insn.getAttributes().getAll(AttributeType.JUMP);
if (cjumps.size() > 0) {
for (IAttribute j : cjumps) {
JumpAttribute jump = (JumpAttribute) j;
if (jump.getDest() == insn.getOffset())
startNew = true;
}
}
// split 'do-while' block (last instruction: 'if', target this block)
if (type == InsnType.IF) {
IfNode ifs = (IfNode) (insn);
BlockNode targBlock = blocksMap.get(ifs.getTarget());
if (targBlock == curBlock)
startNew = true;
}
if (startNew) {
BlockNode block = startNewBlock(mth, insn.getOffset());
connect(curBlock, block);
curBlock = block;
}
}
}
// for try/catch make empty block for connect handlers
if (insn.getAttributes().contains(AttributeFlag.TRY_ENTER)) {
BlockNode block;
if (insn.getOffset() != 0 && !startNew) {
block = startNewBlock(mth, insn.getOffset());
connect(curBlock, block);
curBlock = block;
}
blocksMap.put(insn.getOffset(), curBlock);
// add this insn in new block
block = startNewBlock(mth, -1);
curBlock.getAttributes().add(AttributeFlag.SYNTHETIC);
block.getAttributes().add(new SplitterBlockAttr(curBlock));
connect(curBlock, block);
curBlock = block;
} else {
blocksMap.put(insn.getOffset(), curBlock);
}
curBlock.getInstructions().add(insn);
prevInsn = insn;
}
// setup missing connections
for (BlockNode block : mth.getBasicBlocks()) {
for (InsnNode insn : block.getInstructions()) {
List<IAttribute> jumps = insn.getAttributes().getAll(AttributeType.JUMP);
for (IAttribute attr : jumps) {
JumpAttribute jump = (JumpAttribute) attr;
BlockNode srcBlock = getBlock(mth, jump.getSrc(), blocksMap);
BlockNode thisblock = getBlock(mth, jump.getDest(), blocksMap);
connect(srcBlock, thisblock);
}
// connect exception handlers
CatchAttr catches = (CatchAttr) insn.getAttributes().get(AttributeType.CATCH_BLOCK);
if (catches != null) {
// get synthetic block for handlers
IAttribute spl = block.getAttributes().get(AttributeType.SPLITTER_BLOCK);
if (spl != null) {
BlockNode connBlock = ((SplitterBlockAttr) spl).getBlock();
for (ExceptionHandler h : catches.getTryBlock().getHandlers()) {
BlockNode destBlock = getBlock(mth, h.getHandleOffset(), blocksMap);
// skip self loop in handler
if (connBlock != destBlock)
// && !connBlock.getPredecessors().contains(destBlock))
connect(connBlock, destBlock);
}
}
}
}
}
computeDominators(mth);
for (BlockNode block : mth.getBasicBlocks()) {
markReturnBlocks(mth, block);
}
int i = 0;
while (modifyBlocksTree(mth)) {
// revert calculations
cleanDomTree(mth);
// recalculate dominators tree
computeDominators(mth);
i++;
if (i > 100)
throw new AssertionError("Can't fix method cfg: " + mth);
}
BlockProcessingHelper.visit(mth);
mth.finishBasicBlocks();
}
private static BlockNode getBlock(MethodNode mth, int offset, Map<Integer, BlockNode> blocksMap) {
BlockNode block = blocksMap.get(offset);
assert block != null;
return block;
}
private static void connect(BlockNode from, BlockNode to) {
if (!from.getSuccessors().contains(to))
from.getSuccessors().add(to);
if (!to.getPredecessors().contains(from))
to.getPredecessors().add(from);
}
private static void removeConnection(BlockNode from, BlockNode to) {
from.getSuccessors().remove(to);
to.getPredecessors().remove(from);
}
private static BlockNode startNewBlock(MethodNode mth, int offset) {
BlockNode block = new BlockNode(mth, offset);
mth.getBasicBlocks().add(block);
return block;
}
private static void computeDominators(MethodNode mth) {
int nBlocks = mth.getBasicBlocks().size();
for (int i = 0; i < nBlocks; i++) {
BlockNode block = mth.getBasicBlocks().get(i);
block.setId(i);
block.setDoms(new BitSet(nBlocks));
block.getDoms().set(0, nBlocks);
}
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 : mth.getBasicBlocks()) {
if (block == entryBlock)
continue;
BitSet d = block.getDoms();
dset.clear();
dset.or(d);
for (BlockNode pred : block.getPredecessors()) {
d.and(pred.getDoms());
}
d.set(block.getId());
if (!d.equals(dset))
changed = true;
}
} while (changed);
markLoops(mth);
// clear self dominance
for (BlockNode block : mth.getBasicBlocks()) {
block.getDoms().clear(block.getId());
}
// calculate immediate dominators
for (BlockNode block : mth.getBasicBlocks()) {
if (block == entryBlock)
continue;
if (block.getPredecessors().size() == 1) {
block.setIDom(block.getPredecessors().get(0));
} else {
BitSet bs = new BitSet(block.getDoms().length());
bs.or(block.getDoms());
for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1)) {
BlockNode dom = mth.getBasicBlocks().get(i);
bs.andNot(dom.getDoms());
}
int c = bs.cardinality();
if (c == 1) {
int id = bs.nextSetBit(0);
BlockNode idom = mth.getBasicBlocks().get(id);
block.setIDom(idom);
idom.getDominatesOn().add(block);
} else if (block != entryBlock) {
throw new JadxRuntimeException("Can't find immediate dominator for block " + block
+ " in " + bs + " prec:" + block.getPredecessors());
}
}
}
}
private static void markLoops(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
for (BlockNode succ : block.getSuccessors()) {
// Every successor that dominates its predecessor
// must be the header of a loop.
// That is, block -> succ is a back edge.
if (block.getDoms().get(succ.getId())) {
succ.getAttributes().add(AttributeFlag.LOOP_START);
block.getAttributes().add(AttributeFlag.LOOP_END);
LoopAttr loop = new LoopAttr(succ, block);
succ.getAttributes().add(loop);
block.getAttributes().add(loop);
}
}
}
}
private static void markReturnBlocks(MethodNode mth, BlockNode block) {
if (block.getInstructions().size() == 1) {
if (block.getInstructions().get(0).getType() == InsnType.RETURN)
block.getAttributes().add(AttributeFlag.RETURN);
}
}
private static boolean modifyBlocksTree(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
if (block.getPredecessors().isEmpty() && block != mth.getEnterBlock()) {
throw new JadxRuntimeException("Unreachable block: " + block);
}
// check loops
List<IAttribute> loops = block.getAttributes().getAll(AttributeType.LOOP);
if (loops.size() > 1) {
boolean oneHeader = true;
for (IAttribute a : loops) {
LoopAttr loop = (LoopAttr) a;
if (loop.getStart() != block) {
oneHeader = false;
break;
}
}
if (oneHeader) {
// several back edges connected to one loop header => make additional block
BlockNode newLoopHeader = startNewBlock(mth, block.getStartOffset());
connect(newLoopHeader, block);
for (IAttribute a : loops) {
LoopAttr la = (LoopAttr) a;
BlockNode node = la.getEnd();
removeConnection(node, block);
connect(node, newLoopHeader);
}
return true;
}
}
// splice return block
if (block.getAttributes().contains(AttributeFlag.RETURN)
&& block.getPredecessors().size() > 1
&& !block.getInstructions().get(0).getAttributes().contains(AttributeType.CATCH_BLOCK)) {
List<BlockNode> preds = new ArrayList<BlockNode>(block.getPredecessors());
BlockNode origRetBlock = block;
origRetBlock.getPredecessors().clear();
origRetBlock.getPredecessors().add(preds.get(0));
preds.remove(0);
InsnNode origReturnInsn = origRetBlock.getInstructions().get(0);
RegisterArg retArg = null;
if (origReturnInsn.getArgsCount() != 0)
retArg = (RegisterArg) origReturnInsn.getArg(0);
for (BlockNode pred : preds) {
pred.getSuccessors().remove(origRetBlock);
// make copy of return block and connect to predecessor
BlockNode newRetBlock = startNewBlock(mth, origRetBlock.getStartOffset());
InsnNode ret = new InsnNode(mth, InsnType.RETURN, 1);
if (retArg != null)
ret.addArg(InsnArg.reg(retArg.getRegNum(), retArg.getType()));
ret.getAttributes().addAll(origReturnInsn.getAttributes());
newRetBlock.getInstructions().add(ret);
newRetBlock.getAttributes().add(AttributeFlag.RETURN);
connect(pred, newRetBlock);
mth.addExitBlock(newRetBlock);
}
return true;
}
// TODO detect ternary operator
}
return false;
}
private void cleanDomTree(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
AttributesList attrs = block.getAttributes();
attrs.remove(AttributeType.LOOP);
attrs.remove(AttributeFlag.LOOP_START);
attrs.remove(AttributeFlag.LOOP_END);
block.setDoms(null);
block.setIDom(null);
block.getDominatesOn().clear();
}
}
}
@@ -0,0 +1,127 @@
package jadx.dex.visitors;
import jadx.dex.attributes.AttributeType;
import jadx.dex.instructions.InsnType;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.nodes.BlockNode;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.dex.trycatch.CatchAttr;
import jadx.dex.trycatch.ExcHandlerAttr;
import jadx.dex.trycatch.ExceptionHandler;
import jadx.utils.BlockUtils;
public class BlockProcessingHelper {
public static void visit(MethodNode mth) {
if (mth.isNoCode())
return;
for (BlockNode block : mth.getBasicBlocks()) {
markExceptionHandlers(mth, block);
}
for (BlockNode block : mth.getBasicBlocks()) {
block.updateCleanSuccessors();
}
for (BlockNode block : mth.getBasicBlocks()) {
processExceptionHandlers(mth, block);
}
for (BlockNode block : mth.getBasicBlocks()) {
processTryCatchBlocks(mth, block);
}
}
/**
* Set exception handler attribute for whole block
*/
private static void markExceptionHandlers(MethodNode mth, BlockNode block) {
if (!block.getInstructions().isEmpty()) {
InsnNode me = block.getInstructions().get(0);
ExcHandlerAttr handlerAttr = (ExcHandlerAttr) me.getAttributes().get(AttributeType.EXC_HANDLER);
if (handlerAttr != null) {
ExceptionHandler excHandler = handlerAttr.getHandler();
assert me.getType() == InsnType.MOVE_EXCEPTION && me.getOffset() == excHandler.getHandleOffset();
// set correct type for 'move-exception' operation
RegisterArg excArg = me.getResult();
if (excHandler.isCatchAll())
excArg.getTypedVar().merge(ArgType.THROWABLE);
else
excArg.getTypedVar().merge(excHandler.getCatchType().getType());
excHandler.setArg(excArg);
block.getAttributes().add(handlerAttr);
}
}
}
private static void processExceptionHandlers(MethodNode mth, BlockNode block) {
ExcHandlerAttr handlerAttr = (ExcHandlerAttr) block.getAttributes().get(AttributeType.EXC_HANDLER);
if (handlerAttr != null) {
ExceptionHandler excHandler = handlerAttr.getHandler();
excHandler.addBlock(block);
for (BlockNode node : BlockUtils.collectBlocksDominatedBy(block, block)) {
excHandler.addBlock(node);
}
for (BlockNode excBlock : excHandler.getBlocks()) {
// remove 'monitor-exit' from exception handler blocks
InstructionRemover remover = new InstructionRemover(excBlock.getInstructions());
for (InsnNode insn : excBlock.getInstructions()) {
if (insn.getType() == InsnType.MONITOR_ENTER)
break;
if (insn.getType() == InsnType.MONITOR_EXIT)
remover.add(insn);
}
remover.perform();
// if 'throw' in exception handler block have 'catch' - merge these catch blocks
for (InsnNode insn : excBlock.getInstructions()) {
if (insn.getType() == InsnType.THROW) {
CatchAttr catchAttr = (CatchAttr) insn.getAttributes().get(AttributeType.CATCH_BLOCK);
if (catchAttr != null) {
handlerAttr.getTryBlock().merge(mth, catchAttr.getTryBlock());
catchAttr.getTryBlock().removeInsn(insn);
}
}
}
}
}
}
private static void processTryCatchBlocks(MethodNode mth, BlockNode block) {
// if all instructions in block have same 'catch' attribute mark it as 'TryCatch' block
CatchAttr commonCatchAttr = null;
for (InsnNode insn : block.getInstructions()) {
CatchAttr catchAttr = (CatchAttr) insn.getAttributes().get(AttributeType.CATCH_BLOCK);
if (catchAttr == null)
continue;
if (commonCatchAttr == null) {
commonCatchAttr = catchAttr;
} else if (commonCatchAttr != catchAttr) {
commonCatchAttr = null;
break;
}
}
if (commonCatchAttr != null) {
block.getAttributes().add(commonCatchAttr);
// connect handler to block
for (ExceptionHandler handler : commonCatchAttr.getTryBlock().getHandlers()) {
connectHandler(mth, handler);
}
}
}
private static void connectHandler(MethodNode mth, ExceptionHandler handler) {
int addr = handler.getHandleOffset();
for (BlockNode block : mth.getBasicBlocks()) {
ExcHandlerAttr bh = (ExcHandlerAttr) block.getAttributes().get(AttributeType.EXC_HANDLER);
if (bh != null && bh.getHandler().getHandleOffset() == addr) {
handler.setHandleBlock(block);
break;
}
}
}
}
@@ -0,0 +1,30 @@
package jadx.dex.visitors;
import jadx.dex.info.AccessInfo;
import jadx.dex.nodes.ClassNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.exceptions.JadxException;
import java.util.Iterator;
public class ClassCheck extends AbstractVisitor {
@Override
public boolean visit(ClassNode cls) throws JadxException {
for (ClassNode inner : cls.getInnerClasses()) {
visit(inner);
}
for (Iterator<MethodNode> it = cls.getMethods().iterator(); it.hasNext();) {
MethodNode mth = it.next();
AccessInfo af = mth.getAccessFlags();
// remove bridge methods
if (af.isBridge() && af.isSynthetic()) {
// TODO make some checks before deleting
it.remove();
}
}
return false;
}
}
@@ -0,0 +1,199 @@
package jadx.dex.visitors;
import jadx.dex.attributes.AttributeFlag;
import jadx.dex.instructions.ArithNode;
import jadx.dex.instructions.ArithOp;
import jadx.dex.instructions.IfNode;
import jadx.dex.instructions.InsnType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.InsnWrapArg;
import jadx.dex.instructions.args.LiteralArg;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.nodes.BlockNode;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.BlockUtils;
import jadx.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CodeShrinker extends AbstractVisitor {
private final static Logger LOG = LoggerFactory.getLogger(CodeShrinker.class);
@Override
public void visit(MethodNode mth) {
if (mth.isNoCode() || mth.getAttributes().contains(AttributeFlag.DONT_SHRINK))
return;
// TODO pretify required inlined consts (made by shrink)
// 'dec' and 'inc' don't need to be inlined => must be replaced before shrink
pretify(mth);
shrink(mth);
pretify(mth);
}
private static void shrink(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
InstructionRemover remover = new InstructionRemover(block.getInstructions());
for (int i = 0; i < block.getInstructions().size(); i++) {
InsnNode insn = block.getInstructions().get(i);
// wrap instructions
if (insn.getResult() != null) {
List<InsnArg> use = insn.getResult().getTypedVar().getUseList();
if (use.size() == 1) {
// variable is used only in this instruction
// TODO not correct sometimes :(
remover.add(insn);
} else if (use.size() == 2) {
InsnArg useInsnArg = use.get(1);
InsnNode useInsn = useInsnArg.getParentInsn();
if (useInsn == null) {
LOG.debug("parent insn null in " + useInsnArg + " from " + insn + " mth: " + mth);
} else if (useInsn != insn) {
boolean wrap = false;
// wrap insn from current block
if (BlockUtils.blockContains(block, useInsn)) {
wrap = true;
} else {
// TODO implement rules for shrink insn from different blocks
BlockNode useBlock = BlockUtils.getBlockByInsn(mth, useInsn);
if (useBlock != null && useBlock.getPredecessors().contains(block)) {
wrap = true;
}
}
if (wrap) {
useInsnArg.wrapInstruction(insn);
remover.add(insn);
}
}
}
}
}
remover.perform();
}
}
private static void pretify(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
for (int i = 0; i < block.getInstructions().size(); i++) {
InsnNode insn = block.getInstructions().get(i);
InsnNode ni = pretifyInsn(mth, insn);
if (ni != null)
block.getInstructions().set(i, ni);
}
}
}
private static InsnNode pretifyInsn(MethodNode mth, InsnNode insn) {
for (InsnArg arg : insn.getArguments()) {
if (arg.isInsnWrap()) {
InsnNode ni = pretifyInsn(mth, ((InsnWrapArg) arg).getWrapInsn());
if (ni != null)
arg.wrapInstruction(ni);
}
}
switch (insn.getType()) {
case ARITH:
ArithNode arith = (ArithNode) insn;
if (arith.getArgsCount() == 2) {
InsnArg litArg = null;
if (arith.getArg(1).isInsnWrap()) {
InsnNode wr = ((InsnWrapArg) arith.getArg(1)).getWrapInsn();
if (wr.getType() == InsnType.CONST)
litArg = wr.getArg(0);
} else if (arith.getArg(1).isLiteral()) {
litArg = arith.getArg(1);
}
if (litArg != null) {
long lit = ((LiteralArg) litArg).getLiteral();
boolean invert = false;
if (arith.getOp() == ArithOp.ADD && lit < 0)
invert = true;
if (false && (lit == 1 || lit == -1) && arith.getArg(0).isRegister()) {
ArithOp op = arith.getOp();
if (op == ArithOp.ADD || op == ArithOp.SUB) {
RegisterArg res = arith.getResult();
RegisterArg v0 = (RegisterArg) arith.getArg(0);
if (v0.equals(res)) {
// use 'a++' instead 'a = a + 1' (similar for minus)
boolean inc = ((op == ArithOp.ADD && lit == 1)
|| (op == ArithOp.SUB && lit == -1));
return new ArithNode(mth, inc ? ArithOp.INC : ArithOp.DEC, null, v0);
}
}
}
// fix 'c + (-1)' => 'c - (1)'
if (invert) {
return new ArithNode(mth, ArithOp.SUB,
arith.getResult(), insn.getArg(0),
InsnArg.lit(-lit, litArg.getType()));
}
}
}
break;
case IF:
// simplify 'cmp' instruction in if condition
IfNode ifb = (IfNode) insn;
InsnArg f = ifb.getArg(0);
if (f.isInsnWrap()) {
InsnNode wi = ((InsnWrapArg) f).getWrapInsn();
if (wi.getType() == InsnType.CMP_L || wi.getType() == InsnType.CMP_G) {
if (ifb.isZeroCmp()
|| ((LiteralArg) ifb.getArg(1)).getLiteral() == 0) {
ifb.changeCondition(wi.getArg(0), wi.getArg(1), ifb.getOp());
} else {
LOG.warn("TODO: cmp" + ifb);
}
}
}
break;
default:
break;
}
return null;
}
public static InsnArg inlineArgument(MethodNode mth, RegisterArg arg) {
InsnNode assignInsn = arg.getAssignInsn();
if (assignInsn == null)
return null;
// recursively wrap all instructions
List<RegisterArg> list = new ArrayList<RegisterArg>();
List<RegisterArg> args = mth.getArguments(false);
int i = 0;
do {
list.clear();
assignInsn.getRegisterArgs(list);
for (RegisterArg rarg : list) {
InsnNode ai = rarg.getAssignInsn();
if (ai != assignInsn && ai != null
&& rarg.getParentInsn() != ai)
rarg.wrapInstruction(ai);
}
// remove method args
if (list.size() != 0 & args.size() != 0) {
list.removeAll(args);
}
i++;
if (i > 10000)
throw new JadxRuntimeException("Can't inline arguments for: " + arg + " insn:" + assignInsn);
} while (!list.isEmpty());
return arg.wrapInstruction(assignInsn);
}
}
@@ -0,0 +1,167 @@
package jadx.dex.visitors;
import jadx.dex.info.FieldInfo;
import jadx.dex.instructions.IfNode;
import jadx.dex.instructions.IndexInsnNode;
import jadx.dex.instructions.InsnType;
import jadx.dex.instructions.InvokeNode;
import jadx.dex.instructions.args.ArgType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.LiteralArg;
import jadx.dex.nodes.BlockNode;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.BlockUtils;
import jadx.utils.exceptions.JadxException;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
public class ConstInlinerVisitor extends AbstractVisitor {
@Override
public void visit(MethodNode mth) throws JadxException {
if (mth.isNoCode())
return;
for (BlockNode block : mth.getBasicBlocks()) {
for (Iterator<InsnNode> it = block.getInstructions().iterator(); it.hasNext();) {
InsnNode insn = it.next();
if (checkInsn(mth, block, insn))
it.remove();
}
}
}
public static boolean checkInsn(MethodNode mth, BlockNode block, InsnNode insn) {
if (insn.getType() == InsnType.CONST) {
if (insn.getArgsCount() == 1
&& insn.getArg(0).isLiteral()
&& insn.getResult().getType().getRegCount() == 1 /* process only narrow types */) {
// literal arg
LiteralArg litArg = (LiteralArg) insn.getArg(0);
long lit = litArg.getLiteral();
if (lit == 0 || lit == 1) {
return replaceConst(mth, block, insn, lit);
}
}
// TODO process string const
}
return false;
}
private static boolean replaceConst(MethodNode mth, BlockNode block, InsnNode insn, long literal) {
List<InsnArg> use = insn.getResult().getTypedVar().getUseList();
int replace = 0;
for (InsnArg arg : use) {
InsnNode useInsn = arg.getParentInsn();
if (useInsn == null)
continue;
BlockNode useBlock = BlockUtils.getBlockByInsn(mth, useInsn);
if (useBlock == block || useBlock.isDominator(block)) {
if (arg != insn.getResult()
&& !registerReassignOnPath(block, useBlock, insn)) {
// in most cases type not equal arg.getType()
// just set unknown type and run type fixer
LiteralArg litArg = InsnArg.lit(literal, ArgType.NARROW);
if (useInsn.replaceArg(arg, litArg)) {
// if (useInsn.getType() == InsnType.MOVE) {
// // 'move' became 'const'
// InsnNode constInsn = new InsnNode(mth, InsnType.CONST, 1);
// constInsn.setResult(useInsn.getResult());
// constInsn.addArg(litArg);
// ModVisitor.replaceInsn(useBlock, useInsn, constInsn);
// fixTypes(mth, constInsn);
// }
fixTypes(mth, useInsn);
replace++;
}
}
}
}
return (replace + 1) == use.size();
}
private static boolean registerReassignOnPath(BlockNode block, BlockNode useBlock, InsnNode assignInsn) {
if (block == useBlock)
return false;
Set<BlockNode> blocks = BlockUtils.getAllPathsBlocks(block, useBlock);
// TODO store list of assign insn for each register
int regNum = assignInsn.getResult().getRegNum();
for (BlockNode b : blocks) {
for (InsnNode insn : b.getInstructions()) {
if (insn.getResult() != null
&& insn != assignInsn
&& insn.getResult().getRegNum() == regNum)
return true;
}
}
return false;
}
/**
* This is method similar to PostTypeResolver.visit method,
* but contains some expensive operations needed only after consts inlining
*/
private static void fixTypes(MethodNode mth, InsnNode insn) {
switch (insn.getType()) {
case CONST:
if (insn.getArgsCount() > 0) {
insn.getArg(0).merge(insn.getResult());
}
break;
case MOVE:
insn.getResult().merge(insn.getArg(0));
insn.getArg(0).merge(insn.getResult());
break;
case IPUT:
case SPUT: {
IndexInsnNode node = (IndexInsnNode) insn;
insn.getArg(0).merge(((FieldInfo) node.getIndex()).getType());
break;
}
case IF:
IfNode ifnode = (IfNode) insn;
if (!ifnode.isZeroCmp()) {
insn.getArg(1).merge(insn.getArg(0));
insn.getArg(0).merge(insn.getArg(1));
}
break;
case RETURN:
if (insn.getArgsCount() != 0) {
insn.getArg(0).merge(mth.getMethodInfo().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(type);
}
k++;
}
break;
default:
break;
}
}
}
@@ -0,0 +1,33 @@
package jadx.dex.visitors;
import jadx.dex.nodes.ClassNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.ErrorsCounter;
public class DepthTraverser {
public static void visit(IDexTreeVisitor visitor, ClassNode cls) {
// if (!cls.toString().contains("ProcessClass"))
// return;
try {
if (visitor.visit(cls)) {
for (ClassNode inCls : cls.getInnerClasses())
visit(visitor, inCls);
for (MethodNode mth : cls.getMethods())
visit(visitor, mth);
}
} catch (Throwable e) {
ErrorsCounter.classError(cls,
e.getClass().getSimpleName() + " in pass: " + visitor.getClass().getSimpleName(), e);
}
}
public static void visit(IDexTreeVisitor visitor, MethodNode mth) {
try {
visitor.visit(mth);
} catch (Throwable e) {
ErrorsCounter.methodError(mth,
e.getClass().getSimpleName() + " in pass: " + visitor.getClass().getSimpleName(), e);
}
}
}
@@ -0,0 +1,183 @@
package jadx.dex.visitors;
import jadx.codegen.CodeWriter;
import jadx.codegen.MethodGen;
import jadx.dex.attributes.IAttributeNode;
import jadx.dex.nodes.BlockNode;
import jadx.dex.nodes.IContainer;
import jadx.dex.nodes.IRegion;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.dex.trycatch.ExceptionHandler;
import jadx.utils.InsnUtils;
import jadx.utils.Utils;
import java.io.File;
public class DotGraphVisitor extends AbstractVisitor {
private static final String NL = "\\l";
private static final boolean PRINT_REGISTERS_STATES = false;
private final File dir;
private final boolean useRegions;
private final boolean rawInsn;
public DotGraphVisitor(File outDir, boolean useRegions, boolean rawInsn) {
this.dir = outDir;
this.useRegions = useRegions;
this.rawInsn = rawInsn;
}
public DotGraphVisitor(File outDir, boolean useRegions) {
this(outDir, useRegions, false);
}
@Override
public void visit(MethodNode mth) {
if (mth.isNoCode())
return;
CodeWriter dot = new CodeWriter();
CodeWriter conn = new CodeWriter();
dot.startLine("digraph \"CFG for"
+ escape(mth.getParentClass().getFullName() + "." + mth.getMethodInfo().getShortId())
+ "\" {");
if (useRegions) {
if (mth.getRegion() == null)
return;
processRegion(mth, mth.getRegion(), dot, conn);
if (mth.getExceptionHandlers() != null) {
for (ExceptionHandler h : mth.getExceptionHandlers())
if (h.getHandlerRegion() != null)
processRegion(mth, h.getHandlerRegion(), dot, conn);
}
} else {
for (BlockNode block : mth.getBasicBlocks())
processBlock(mth, block, dot, conn);
}
String attrs = attributesString(mth);
dot.startLine("MethodNode[shape=record,label=\"{"
+ escape(mth.getAccessFlags().makeString())
+ escape(mth.getMethodInfo().getReturnType() + " "
+ mth.getParentClass().getFullName() + "." + mth.getName()
+ "(" + Utils.listToString(mth.getArguments(true)) + ") ")
+ (attrs.length() == 0 ? "" : " | " + attrs)
+ (mth.getSuperCall() != null ? "| Super call: " + escape(mth.getSuperCall().toString()) : "")
+ "}\"];");
dot.startLine("MethodNode -> " + makeName(mth.getEnterBlock()) + ";");
dot.add(conn);
dot.startLine("}");
dot.startLine();
String fileName = Utils.escape(mth.getMethodInfo().getShortId())
+ (useRegions ? ".regions" : "")
+ (rawInsn ? ".raw" : "")
+ ".dot";
dot.save(dir, mth.getParentClass().getClassInfo().getFullPath() + "_graphs", fileName);
}
private void processRegion(MethodNode mth, IContainer region, CodeWriter dot, CodeWriter conn) {
if (region instanceof IRegion) {
IRegion r = (IRegion) region;
String attrs = attributesString(r);
dot.startLine("subgraph " + makeName(region) + " {");
dot.startLine("label = \"" + r.toString()
+ (attrs.length() == 0 ? "" : " | " + attrs)
+ "\";");
dot.startLine("node [shape=record,color=blue];");
for (IContainer c : r.getSubBlocks()) {
processRegion(mth, c, dot, conn);
}
dot.startLine("}");
} else if (region instanceof BlockNode) {
processBlock(mth, (BlockNode) region, dot, conn);
}
}
private void processBlock(MethodNode mth, BlockNode block, CodeWriter dot, CodeWriter conn) {
String attrs = attributesString(block);
if (PRINT_REGISTERS_STATES) {
if (block.getStartState() != null) {
if (attrs.length() != 0)
attrs += "|";
attrs += escape("RS: " + block.getStartState()) + NL;
attrs += escape("RE: " + block.getEndState()) + NL;
}
}
String insns = insertInsns(mth, block);
dot.startLine(makeName(block) + " [shape=record,label=\"{"
+ block.getId() + "\\:\\ "
+ InsnUtils.formatOffset(block.getStartOffset())
+ (attrs.length() == 0 ? "" : "|" + attrs)
+ (insns.length() == 0 ? "" : "|" + insns)
+ "}\"];");
for (BlockNode next : block.getSuccessors())
conn.startLine(makeName(block) + " -> " + makeName(next) + ";");
for (BlockNode next : block.getDominatesOn())
conn.startLine(makeName(block) + " -> " + makeName(next) + "[style=dotted];");
// // add all dominators connections
// for (BlockNode next : BlockUtils.bitsetToBlocks(mth, block.getDoms()))
// conn.startLine(makeName(block) + " -> " + makeName(next) + "[style=dotted, color=green];");
}
private String attributesString(IAttributeNode block) {
StringBuilder attrs = new StringBuilder();
for (String attr : block.getAttributes().getAttributeStrings()) {
attrs.append(escape(attr)).append(NL);
}
return attrs.toString();
}
private String makeName(IContainer c) {
String name;
if (c instanceof BlockNode) {
name = "Node_" + ((BlockNode) c).getId();
} else {
name = "cluster_" + c.getClass().getSimpleName() + "_" + c.hashCode();
}
return name;
}
private String insertInsns(MethodNode mth, BlockNode block) {
if (rawInsn) {
StringBuilder str = new StringBuilder();
for (InsnNode insn : block.getInstructions()) {
str.append(escape(insn.toString() + " " + insn.getAttributes()));
str.append(NL);
}
return str.toString();
} else {
CodeWriter code = new CodeWriter(0);
MethodGen.makeFallbackInsns(code, mth, block.getInstructions(), false);
String str = escape(code.endl().toString());
if (str.startsWith(NL))
str = str.substring(NL.length());
return str;
}
}
private String escape(String string) {
return string
.replace(">", "\\>").replace("<", "\\<")
.replace("{", "\\{").replace("}", "\\}")
.replace("\"", "\\\"")
.replace("-", "\\-")
.replace("\n", NL);
}
}
@@ -0,0 +1,144 @@
package jadx.dex.visitors;
import jadx.dex.attributes.AttributeFlag;
import jadx.dex.attributes.EnumClassAttr;
import jadx.dex.attributes.EnumClassAttr.EnumField;
import jadx.dex.info.ClassInfo;
import jadx.dex.info.FieldInfo;
import jadx.dex.info.MethodInfo;
import jadx.dex.instructions.IndexInsnNode;
import jadx.dex.instructions.InsnType;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.instructions.mods.ConstructorInsn;
import jadx.dex.nodes.BlockNode;
import jadx.dex.nodes.ClassNode;
import jadx.dex.nodes.FieldNode;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.exceptions.JadxException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class EnumVisitor extends AbstractVisitor {
@Override
public boolean visit(ClassNode cls) throws JadxException {
if (!cls.getAccessFlags().isEnum()
|| !cls.getSuperClass().getFullName().equals("java.lang.Enum"))
return true;
// collect enum fields, remove synthetic
List<FieldNode> enumFields = new ArrayList<FieldNode>();
for (Iterator<FieldNode> it = cls.getFields().iterator(); it.hasNext();) {
FieldNode f = it.next();
if (f.getAccessFlags().isEnum()) {
enumFields.add(f);
it.remove();
} else if (f.getAccessFlags().isSynthetic()) {
it.remove();
}
}
MethodNode staticMethod = null;
// remove synthetic methods
for (Iterator<MethodNode> it = cls.getMethods().iterator(); it.hasNext();) {
MethodNode mth = it.next();
MethodInfo mi = mth.getMethodInfo();
if (mi.isClassInit()) {
staticMethod = mth;
} else if (mi.isConstructor() && !mth.getAccessFlags().isSynthetic()) {
if (mi.getShortId().equals("<init>(Ljava/lang/String;I)"))
it.remove();
} else if (mth.getAccessFlags().isSynthetic()
|| mi.getShortId().equals("values()")
|| mi.getShortId().equals("valueOf(Ljava/lang/String;)")) {
it.remove();
}
}
if (staticMethod == null)
throw new JadxException("Enum class init method not found");
EnumClassAttr attr = new EnumClassAttr(enumFields.size());
cls.getAttributes().add(attr);
attr.setStaticMethod(staticMethod);
// move enum specific instruction from static method to separate list
BlockNode staticBlock = staticMethod.getBasicBlocks().get(0);
List<InsnNode> insns = new ArrayList<InsnNode>();
List<InsnNode> list = staticBlock.getInstructions();
int size = list.size();
for (int i = 0; i < size; i++) {
InsnNode insn = list.get(i);
insns.add(insn);
if (insn.getType() == InsnType.SPUT) {
IndexInsnNode fp = (IndexInsnNode) insn;
FieldInfo f = (FieldInfo) fp.getIndex();
if (f.getName().equals("$VALUES")) {
if (i == size - 1)
cls.getMethods().remove(staticMethod);
else
list.subList(0, i + 1).clear();
break;
}
}
}
for (InsnNode insn : insns) {
if (insn.getType() == InsnType.CONSTRUCTOR) {
ConstructorInsn co = (ConstructorInsn) insn;
if (insn.getArgsCount() < 2)
continue;
ClassInfo clsInfo = co.getClassType();
ClassNode constrCls = cls.dex().resolveClass(clsInfo);
if (constrCls == null)
continue;
if (!clsInfo.equals(cls.getClassInfo()) && !constrCls.getAccessFlags().isEnum())
continue;
RegisterArg nameArg = (RegisterArg) insn.getArg(0);
InsnArg pos = insn.getArg(1);
// TODO add check: pos == j
String name = (String) nameArg.getConstValue();
EnumField field = new EnumField(name, insn.getArgsCount() - 2);
attr.getFields().add(field);
for (int i = 2; i < insn.getArgsCount(); i++) {
InsnArg constrArg;
InsnArg iArg = insn.getArg(i);
if (iArg.isLiteral()) {
constrArg = iArg;
} else {
constrArg = CodeShrinker.inlineArgument(staticMethod, (RegisterArg) iArg);
assert constrArg != null;
}
field.getArgs().add(constrArg);
}
if (co.getClassType() != cls.getClassInfo()) {
// enum contains additional methods
for (ClassNode innerCls : cls.getInnerClasses()) {
if (innerCls.getClassInfo().equals(co.getClassType())) {
// remove constructor, because it is anonymous class
for (Iterator<?> mit = innerCls.getMethods().iterator(); mit.hasNext();) {
MethodNode innerMth = (MethodNode) mit.next();
if (innerMth.getAccessFlags().isConstructor())
mit.remove();
}
field.setCls(innerCls);
innerCls.getAttributes().add(AttributeFlag.DONT_GENERATE);
}
}
}
}
}
return false;
}
}
@@ -0,0 +1,40 @@
package jadx.dex.visitors;
import jadx.dex.attributes.AttributeType;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.dex.trycatch.CatchAttr;
import jadx.utils.exceptions.JadxException;
public class FallbackModeVisitor extends AbstractVisitor {
@Override
public void visit(MethodNode mth) throws JadxException {
if (mth.isNoCode())
return;
for (InsnNode insn : mth.getInstructions()) {
// remove 'exception catch' for instruction which don't throw any exceptions
CatchAttr catchAttr = (CatchAttr) insn.getAttributes().get(AttributeType.CATCH_BLOCK);
if (catchAttr != null) {
switch (insn.getType()) {
case RETURN:
case IF:
case GOTO:
case MOVE:
case MOVE_EXCEPTION:
case ARITH: // ??
case NEG:
case CONST:
case CMP_L:
case CMP_G:
catchAttr.getTryBlock().removeInsn(insn);
break;
default:
break;
}
}
}
}
}
@@ -0,0 +1,26 @@
package jadx.dex.visitors;
import jadx.dex.nodes.ClassNode;
import jadx.dex.nodes.MethodNode;
import jadx.utils.exceptions.JadxException;
/**
* Visitor interface for traverse dex tree
*/
public interface IDexTreeVisitor {
/**
* Visit class
*
* @return false for disable child methods and inner classes traversal
* @throws JadxException
*/
boolean visit(ClassNode cls) throws JadxException;
/**
* Visit method
*
* @throws JadxException
*/
void visit(MethodNode mth) throws JadxException;
}
@@ -0,0 +1,101 @@
package jadx.dex.visitors;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.nodes.BlockNode;
import jadx.dex.nodes.InsnNode;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Helper class for correct instructions removing,
* can be used while iterating over instructions list
*/
public class InstructionRemover {
private final List<InsnNode> insns;
private final List<InsnNode> toRemove;
public InstructionRemover(List<InsnNode> instructions) {
this.insns = instructions;
this.toRemove = new ArrayList<InsnNode>();
}
public void add(InsnNode insn) {
toRemove.add(insn);
}
public void perform() {
removeAll(insns, toRemove);
toRemove.clear();
}
public static void unbindInsnList(List<InsnNode> unbind) {
for (InsnNode rem : unbind)
unbindInsn(rem);
}
public static void unbindInsn(InsnNode insn) {
if (insn.getResult() != null) {
InsnArg res = insn.getResult();
res.getTypedVar().getUseList().remove(res);
}
for (InsnArg arg : insn.getArguments()) {
if (arg.isRegister()) {
arg.getTypedVar().getUseList().remove(arg);
}
}
}
public static void removeAll(BlockNode block, List<InsnNode> toRemove) {
removeAll(block.getInstructions(), toRemove);
}
// Don't use 'insns.removeAll(toRemove)' because it will remove instructions by content
// and here can be several instructions with same content
public static void removeAll(List<InsnNode> insns, List<InsnNode> toRemove) {
if (insns == toRemove) {
for (InsnNode rem : toRemove)
unbindInsn(rem);
return;
}
for (InsnNode rem : toRemove) {
unbindInsn(rem);
for (Iterator<InsnNode> it = insns.iterator(); it.hasNext();) {
InsnNode insn = it.next();
if (insn == rem) {
it.remove();
break;
}
}
}
}
public static void remove(BlockNode block, InsnNode insn) {
unbindInsn(insn);
// remove by pointer (don't use equals)
for (Iterator<InsnNode> it = block.getInstructions().iterator(); it.hasNext();) {
InsnNode ir = it.next();
if (ir == insn) {
it.remove();
return;
}
}
}
public static void removeAllByContent(BlockNode block, List<InsnNode> toRemove) {
for (InsnNode rem : toRemove) {
unbindInsn(rem);
}
block.getInstructions().removeAll(toRemove);
}
public static void remove(BlockNode block, int index) {
InsnNode insn = block.getInstructions().get(index);
unbindInsn(insn);
block.getInstructions().remove(index);
}
}
@@ -0,0 +1,235 @@
package jadx.dex.visitors;
import jadx.Consts;
import jadx.dex.attributes.AttributeType;
import jadx.dex.attributes.IAttribute;
import jadx.dex.info.MethodInfo;
import jadx.dex.instructions.IndexInsnNode;
import jadx.dex.instructions.InsnType;
import jadx.dex.instructions.InvokeNode;
import jadx.dex.instructions.args.InsnArg;
import jadx.dex.instructions.args.RegisterArg;
import jadx.dex.instructions.mods.ConstructorInsn;
import jadx.dex.nodes.BlockNode;
import jadx.dex.nodes.FieldNode;
import jadx.dex.nodes.InsnContainer;
import jadx.dex.nodes.InsnNode;
import jadx.dex.nodes.MethodNode;
import jadx.dex.trycatch.ExcHandlerAttr;
import jadx.dex.trycatch.ExceptionHandler;
import jadx.dex.trycatch.TryCatchBlock;
import jadx.utils.BlockUtils;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Visitor for modify method instructions
* (remove, replace, process exception handlers)
*/
public class ModVisitor extends AbstractVisitor {
private final static Logger LOG = LoggerFactory.getLogger(ModVisitor.class);
@Override
public void visit(MethodNode mth) {
if (mth.isNoCode())
return;
removeStep(mth);
replaceStep(mth);
for (BlockNode block : mth.getBasicBlocks()) {
processExceptionHander(mth, block);
}
}
private void replaceStep(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
InstructionRemover remover = new InstructionRemover(block.getInstructions());
int size = block.getInstructions().size();
for (int i = 0; i < size; i++) {
InsnNode insn = block.getInstructions().get(i);
switch (insn.getType()) {
case INVOKE:
InvokeNode inv = (InvokeNode) insn;
MethodInfo callMth = inv.getCallMth();
if (callMth.isConstructor()) {
ConstructorInsn co = new ConstructorInsn(mth, inv);
// don't call 'super' if parent 'Object'
if (co.isSuper()) {
if (!co.getClassType().getFullName().equals(Consts.CLASS_OBJECT)) {
for (int j = 0; j < co.getArgsCount(); j++) {
InsnArg arg = co.getArg(j);
if (arg.isRegister()) {
CodeShrinker.inlineArgument(mth, (RegisterArg) arg);
}
}
if (!mth.getParentClass().getAccessFlags().isEnum())
mth.setSuperCall(co);
}
remover.add(insn);
} else if (co.isThis() && co.getArgsCount() == 0) {
MethodNode defCo = mth.getParentClass().searchMethodById(co.getCallMth().getShortId());
if (defCo == null || defCo.isNoCode()) {
// default constructor not implemented
remover.add(insn);
} else {
replaceInsn(block, i, co);
}
} else {
replaceInsn(block, i, co);
}
}
break;
case CONST:
if (insn.getArgsCount() == 0) {
// const-string
IndexInsnNode node = (IndexInsnNode) insn;
FieldNode f = mth.getParentClass().getConstFields().get(node.getIndex());
if (f != null) {
InsnNode inode = new IndexInsnNode(mth, InsnType.SGET, f, 0);
inode.setResult(insn.getResult());
replaceInsn(block, i, inode);
}
}
break;
default:
break;
}
}
remover.perform();
}
}
/**
* Remove unnecessary instructions
*/
private void removeStep(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
InstructionRemover remover = new InstructionRemover(block.getInstructions());
int size = block.getInstructions().size();
for (int i = 0; i < size; i++) {
InsnNode insn = block.getInstructions().get(i);
switch (insn.getType()) {
case NOP:
case GOTO:
case NEW_INSTANCE:
remover.add(insn);
break;
case NEW_ARRAY:
// create array in 'fill-array' instruction
int next = i + 1;
if (next < size) {
InsnNode ni = block.getInstructions().get(next);
if (ni.getType() == InsnType.FILL_ARRAY) {
ni.getResult().merge(insn.getResult());
remover.add(insn);
}
}
break;
case RETURN:
if (insn.getArgsCount() == 0
&& mth.getBasicBlocks().size() == 1
&& i == size - 1)
remover.add(insn);
break;
default:
break;
}
}
remover.perform();
}
}
private void processExceptionHander(MethodNode mth, BlockNode block) {
ExcHandlerAttr handlerAttr = (ExcHandlerAttr) block.getAttributes().get(AttributeType.EXC_HANDLER);
if (handlerAttr == null)
return;
TryCatchBlock tryBlock = handlerAttr.getTryBlock();
ExceptionHandler excHandler = handlerAttr.getHandler();
List<InsnNode> blockInsns = block.getInstructions();
int size = blockInsns.size();
if (size > 0 && blockInsns.get(0).getType() == InsnType.MOVE_EXCEPTION) {
InstructionRemover.remove(block, 0);
}
int totalSize = 0;
boolean noExitNode = true; // check if handler has exit edge to block not from this handler
for (BlockNode excBlock : excHandler.getBlocks()) {
List<InsnNode> insns = excBlock.getInstructions();
size = insns.size();
if (noExitNode)
noExitNode = excHandler.getBlocks().containsAll(excBlock.getCleanSuccessors());
if (excHandler.isCatchAll()
&& size > 0
&& insns.get(size - 1).getType() == InsnType.THROW) {
InstructionRemover.remove(excBlock, size - 1);
size = insns.size();
// move not removed instructions to 'finally' block
if (size != 0) {
InsnContainer cont = new InsnContainer();
List<InsnNode> finalBlockInsns = new ArrayList<InsnNode>(insns);
cont.setInstructions(finalBlockInsns);
tryBlock.setFinalBlock(cont);
InstructionRemover.unbindInsnList(finalBlockInsns);
// remove these instructions from other handlers
for (ExceptionHandler h : tryBlock.getHandlers()) {
for (BlockNode ehb : h.getBlocks())
ehb.getInstructions().removeAll(finalBlockInsns);
}
// remove from blocks with this catch
for (BlockNode b : mth.getBasicBlocks()) {
IAttribute ca = b.getAttributes().get(AttributeType.CATCH_BLOCK);
if (tryBlock.getCatchAttr() == ca)
b.getInstructions().removeAll(finalBlockInsns);
}
size = insns.size();
}
}
totalSize += size;
}
if (totalSize == 0 && noExitNode)
tryBlock.removeHandler(mth, excHandler);
}
/**
* 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.getAttributes().addAll(prevInsn.getAttributes());
block.getInstructions().set(i, insn);
}
/**
* Replace oldInsn in block by newInsn,
*/
public static boolean replaceInsn(BlockNode block, InsnNode oldInsn, InsnNode newInsn) {
int pos = BlockUtils.insnIndex(block, oldInsn);
if (pos == -1)
return false;
replaceInsn(block, pos, newInsn);
return true;
}
}
@@ -0,0 +1,21 @@
package jadx.dex.visitors.regions;
import jadx.dex.nodes.IBlock;
import jadx.dex.nodes.IRegion;
import jadx.dex.nodes.MethodNode;
public abstract class AbstractRegionVisitor implements IRegionVisitor {
@Override
public void enterRegion(MethodNode mth, IRegion region) {
}
@Override
public void processBlock(MethodNode mth, IBlock container) {
}
@Override
public void leaveRegion(MethodNode mth, IRegion region) {
}
}
@@ -0,0 +1,60 @@
package jadx.dex.visitors.regions;
import jadx.dex.attributes.AttributeFlag;
import jadx.dex.nodes.BlockNode;
import jadx.dex.nodes.IBlock;
import jadx.dex.nodes.IRegion;
import jadx.dex.nodes.MethodNode;
import jadx.dex.regions.LoopRegion;
import jadx.dex.visitors.AbstractVisitor;
import jadx.utils.ErrorsCounter;
import jadx.utils.exceptions.JadxException;
import java.util.HashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CheckRegions extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(CheckRegions.class);
@Override
public void visit(MethodNode mth) throws JadxException {
if (mth.isNoCode() || mth.getBasicBlocks().size() == 0)
return;
// check if all blocks included in regions
final Set<BlockNode> blocksInRegions = new HashSet<BlockNode>();
IRegionVisitor collectBlocks = new AbstractRegionVisitor() {
@Override
public void processBlock(MethodNode mth, IBlock container) {
if (container instanceof BlockNode)
blocksInRegions.add((BlockNode) container);
else
LOG.warn("Not block node : " + container.getClass().getSimpleName());
}
};
DepthRegionTraverser.traverseAll(mth, collectBlocks);
if (mth.getBasicBlocks().size() != blocksInRegions.size())
mth.getAttributes().add(AttributeFlag.INCONSISTENT_CODE);
// check loop conditions
IRegionVisitor checkLoops = new AbstractRegionVisitor() {
@Override
public void enterRegion(MethodNode mth, IRegion region) {
if (region instanceof LoopRegion) {
LoopRegion loop = (LoopRegion) region;
if (loop.getConditionBlock() != null
&& loop.getConditionBlock().getInstructions().size() != 1) {
ErrorsCounter.methodError(mth, "Incorrect condition in loop: " + loop.getConditionBlock());
mth.getAttributes().add(AttributeFlag.INCONSISTENT_CODE);
}
}
}
};
DepthRegionTraverser.traverseAll(mth, checkLoops);
}
}
@@ -0,0 +1,32 @@
package jadx.dex.visitors.regions;
import jadx.dex.nodes.IBlock;
import jadx.dex.nodes.IContainer;
import jadx.dex.nodes.IRegion;
import jadx.dex.nodes.MethodNode;
import jadx.dex.trycatch.ExceptionHandler;
public class DepthRegionTraverser {
public static void traverse(MethodNode mth, IRegionVisitor visitor, IContainer container) {
if (container instanceof IBlock) {
visitor.processBlock(mth, (IBlock) container);
} else if (container instanceof IRegion) {
IRegion region = (IRegion) container;
visitor.enterRegion(mth, region);
for (IContainer subCont : region.getSubBlocks()) {
traverse(mth, visitor, subCont);
}
visitor.leaveRegion(mth, region);
}
}
public static void traverseAll(MethodNode mth, IRegionVisitor visitor) {
traverse(mth, visitor, mth.getRegion());
if (mth.getExceptionHandlers() != null) {
for (ExceptionHandler h : mth.getExceptionHandlers())
traverse(mth, visitor, h.getHandlerRegion());
}
}
}

Some files were not shown because too many files have changed in this diff Show More