Commit d01a280d authored by Skylot's avatar Skylot

Add source files and samples

parent 0f520dfb
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";
}
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;
}
}
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);
}
}
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();
}
}
}
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() + ")");
}
}
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;
}
}
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();
}
}
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);
}
}
}
This diff is collapsed.
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);
}
}
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);
}
}
}
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;
}
}
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);
}
}
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;
}
}
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
}
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;
}
}
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) + "}";
}
}
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();
}
}
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);
}
}
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;
}
}
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());
}
}
package jadx.dex.attributes;
public interface IAttribute {
AttributeType getType();
}
package jadx.dex.attributes;
public interface IAttributeNode {
AttributesList getAttributes();
}
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();
}
}
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;
}
}
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;
}
}
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 + "]";
}
}
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());
}
}
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);
}
}
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();
}
}
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;
}
}
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;
}
}
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;
}
}
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) + ")";
}
}
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);
}
}
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;
}
}
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;
}
}
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);
}
}
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);
}
}
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;
}
}
}
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);
}
}
This diff is collapsed.
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
}
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;
}
}
package jadx.dex.instructions;
public enum InvokeType {
STATIC,
DIRECT,
VIRTUAL,
INTERFACE,
SUPER,
}
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;
}
}
This diff is collapsed.
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;
}
}
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 + ")";
}
}
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 + ")";
}
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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());
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
package jadx.dex.nodes;
import java.util.List;
public interface IBlock extends IContainer {
public List<InsnNode> getInstructions();
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment