Add source files and samples
This commit is contained in:
@@ -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";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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() + ")");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()) + ")";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user