Add jadx-gui, restructure src directory

This commit is contained in:
Skylot
2013-07-10 22:57:39 +04:00
parent cbbb73355b
commit ce7d6f0156
168 changed files with 1533 additions and 1041 deletions
@@ -0,0 +1,150 @@
package jadx.api;
import jadx.core.Jadx;
import jadx.core.ProcessClass;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.files.InputFile;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class Decompiler {
private static final Logger LOG = LoggerFactory.getLogger(Decompiler.class);
private final IJadxArgs args;
private final List<InputFile> inputFiles = new ArrayList<InputFile>();
private RootNode root;
private List<IDexTreeVisitor> passes;
private int errorsCount;
public Decompiler(IJadxArgs jadxArgs) {
this.args = jadxArgs;
this.passes = Jadx.getPassesList(args);
}
public void processAndSaveAll() {
try {
loadInput();
parseDex();
saveAll();
} catch (Throwable e) {
LOG.error("jadx error:", e);
} finally {
errorsCount = ErrorsCounter.getErrorCount();
if (errorsCount != 0)
ErrorsCounter.printReport();
}
}
public void loadFile(File file) throws IOException, DecodeException {
List<File> files = args.getInput();
files.clear();
files.add(file);
loadInput();
parseDex();
}
public List<JavaClass> getClasses() {
List<JavaClass> classes = new ArrayList<JavaClass>(root.getClasses().size());
for (ClassNode classNode : root.getClasses()) {
classes.add(new JavaClass(this, classNode));
}
return classes;
}
public List<JavaPackage> getPackages() {
List<JavaClass> classes = getClasses();
Map<String, List<JavaClass>> map = new HashMap<String, List<JavaClass>>();
for (JavaClass javaClass : classes) {
String pkg = javaClass.getPackage();
List<JavaClass> clsList = map.get(pkg);
if (clsList == null) {
clsList = new ArrayList<JavaClass>();
map.put(pkg, clsList);
}
clsList.add(javaClass);
}
List<JavaPackage> packages = new ArrayList<JavaPackage>(map.size());
for (Map.Entry<String, List<JavaClass>> entry : map.entrySet()) {
packages.add(new JavaPackage(entry.getKey(), entry.getValue()));
}
Collections.sort(packages);
return packages;
}
public void saveAll() throws InterruptedException {
int threadsCount = args.getThreadsCount();
LOG.debug("processing threads count: {}", threadsCount);
ArrayList<IDexTreeVisitor> passList = new ArrayList<IDexTreeVisitor>(passes);
SaveCode savePass = new SaveCode(args);
passList.add(savePass);
LOG.info("processing ...");
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
for (ClassNode cls : root.getClasses()) {
if (cls.getCode() == null) {
ProcessClass job = new ProcessClass(cls, passList);
executor.execute(job);
} else {
try {
savePass.visit(cls);
} catch (CodegenException e) {
LOG.error("Can't save class {}", cls, e);
}
}
}
executor.shutdown();
executor.awaitTermination(100, TimeUnit.DAYS);
}
private void loadInput() throws IOException, DecodeException {
for (File file : args.getInput()) {
inputFiles.add(new InputFile(file));
}
}
private void parseDex() throws DecodeException {
ClassInfo.clearCache();
ErrorsCounter.reset();
root = new RootNode(args, inputFiles);
LOG.info("loading ...");
root.load();
root.init();
}
void processClass(ClassNode cls) {
try {
ProcessClass job = new ProcessClass(cls, passes);
LOG.info("processing class {} ...", cls);
job.run();
} catch (Throwable e) {
LOG.error("Process class error", e);
}
}
public int getErrorsCount() {
return errorsCount;
}
}
@@ -0,0 +1,22 @@
package jadx.api;
import java.io.File;
import java.util.List;
public interface IJadxArgs {
List<File> getInput();
File getOutDir();
int getThreadsCount();
boolean isCFGOutput();
boolean isRawCFGOutput();
boolean isFallbackMode();
boolean isVerbose();
boolean isPrintHelp();
}
@@ -0,0 +1,41 @@
package jadx.api;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.nodes.ClassNode;
public final class JavaClass {
private final Decompiler decompiler;
private final ClassNode cls;
JavaClass(Decompiler decompiler, ClassNode classNode) {
this.decompiler = decompiler;
this.cls = classNode;
}
public String getCode() {
CodeWriter code = cls.getCode();
if(code == null) {
decompiler.processClass(cls);
code = cls.getCode();
}
return code != null ? code.toString() : "error processing class";
}
public String getFullName() {
return cls.getFullName();
}
public String getShortName() {
return cls.getShortName();
}
public String getPackage() {
return cls.getPackage();
}
@Override
public String toString() {
return getFullName();
}
}
@@ -0,0 +1,31 @@
package jadx.api;
import java.util.List;
public final class JavaPackage implements Comparable<JavaPackage> {
private final String name;
private final List<JavaClass> classes;
JavaPackage(String name, List<JavaClass> classes) {
this.name = name;
this.classes = classes;
}
public String getName() {
return name;
}
public List<JavaClass> getClasses() {
return classes;
}
@Override
public String toString() {
return name;
}
@Override
public int compareTo(JavaPackage o) {
return name.compareTo(o.name);
}
}
@@ -0,0 +1,25 @@
package jadx.core;
import jadx.core.utils.Utils;
public class Consts {
public static final String JADX_VERSION = Utils.getJadxVersion();
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";
public static final String CLASS_STRING_BUILDER = "java.lang.StringBuilder";
public static final String DALVIK_ANNOTATION_PKG = "dalvik.annotation.";
public static final String DALVIK_SIGNATURE = "dalvik.annotation.Signature";
public static final String DALVIK_INNER_CLASS = "dalvik.annotation.InnerClass";
public static final String DALVIK_THROWS = "dalvik.annotation.Throws";
public static final String DALVIK_ANNOTATION_DEFAULT = "dalvik.annotation.AnnotationDefault";
public static final String DEFAULT_PACKAGE_NAME = "defpackage";
}
@@ -0,0 +1,75 @@
package jadx.core;
import jadx.api.IJadxArgs;
import jadx.core.codegen.CodeGen;
import jadx.core.dex.visitors.BlockMakerVisitor;
import jadx.core.dex.visitors.ClassModifier;
import jadx.core.dex.visitors.CodeShrinker;
import jadx.core.dex.visitors.ConstInlinerVisitor;
import jadx.core.dex.visitors.DotGraphVisitor;
import jadx.core.dex.visitors.EnumVisitor;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.MethodInlinerVisitor;
import jadx.core.dex.visitors.ModVisitor;
import jadx.core.dex.visitors.regions.CheckRegions;
import jadx.core.dex.visitors.regions.CleanRegions;
import jadx.core.dex.visitors.regions.PostRegionVisitor;
import jadx.core.dex.visitors.regions.ProcessVariables;
import jadx.core.dex.visitors.regions.RegionMakerVisitor;
import jadx.core.dex.visitors.typeresolver.FinishTypeResolver;
import jadx.core.dex.visitors.typeresolver.TypeResolver;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Jadx {
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
static {
if (Consts.DEBUG)
LOG.info("debug enabled");
if (Jadx.class.desiredAssertionStatus())
LOG.info("assertions enabled");
}
public static List<IDexTreeVisitor> getPassesList(IJadxArgs 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());
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 MethodInlinerVisitor());
passes.add(new ClassModifier());
passes.add(new CleanRegions());
}
passes.add(new CodeGen(args));
return passes;
}
}
@@ -0,0 +1,37 @@
package jadx.core;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.DepthTraverser;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.exceptions.DecodeException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class ProcessClass implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(ProcessClass.class);
private final ClassNode cls;
private final List<IDexTreeVisitor> passes;
public 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,184 @@
package jadx.core.codegen;
import jadx.core.Consts;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.StringUtils;
import jadx.core.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(Consts.DALVIK_ANNOTATION_PKG)) {
// skip
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) {
Annotation an = mth.getAttributes().getAnnotation(Consts.DALVIK_THROWS);
if (an != null) {
Object exs = an.getDefaultValue();
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) {
Annotation an = cls.getAttributes().getAnnotation(Consts.DALVIK_ANNOTATION_DEFAULT);
if (an != null) {
Annotation defAnnotation = (Annotation) an.getDefaultValue();
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,395 @@
package jadx.core.codegen;
import jadx.core.Consts;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.EnumClassAttr;
import jadx.core.dex.attributes.EnumClassAttr.EnumField;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.attributes.SourceFileAttr;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.parser.FieldValueAttr;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.Utils;
import jadx.core.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.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.android.dx.rop.code.AccessFlags;
public class ClassGen {
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;
if (cls.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE))
code.startLine("// jadx: inconsistent code");
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());
makeGenericMap(clsCode, cls.getGenericMap());
clsCode.add(' ');
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)).add(' ');
}
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(", ");
}
if (!cls.getInterfaces().isEmpty())
clsCode.add(' ');
}
}
public boolean makeGenericMap(CodeWriter code, Map<ArgType, List<ArgType>> gmap) {
if (gmap == null || gmap.isEmpty())
return false;
code.add('<');
int i = 0;
for (Entry<ArgType, List<ArgType>> e : gmap.entrySet()) {
ArgType type = e.getKey();
List<ArgType> list = e.getValue();
if (i != 0) {
code.add(", ");
}
code.add(useClass(type));
if (list != null && !list.isEmpty()) {
code.add(" extends ");
for (Iterator<ArgType> it = list.iterator(); it.hasNext(); ) {
ArgType g = it.next();
code.add(useClass(g));
if (it.hasNext()) {
code.add(" & ");
}
}
}
i++;
}
code.add('>');
return true;
}
public void makeClassBody(CodeWriter clsCode) throws CodegenException {
clsCode.add('{');
insertSourceFileInfo(clsCode, cls);
CodeWriter mthsCode = makeMethods(clsCode, cls.getMethods());
CodeWriter fieldsCode = makeFields(clsCode, cls, cls.getFields());
clsCode.add(fieldsCode);
if (fieldsCode.notEmpty() && mthsCode.notEmpty())
clsCode.endl();
// insert inner classes code
if (cls.getInnerClasses().size() != 0) {
clsCode.add(makeInnerClasses(cls, clsCode.getIndent()));
if (mthsCode.notEmpty())
clsCode.endl();
}
clsCode.add(mthsCode);
clsCode.startLine('}');
}
private CodeWriter makeInnerClasses(ClassNode cls, 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) {
CodeWriter code = new CodeWriter(clsCode.getIndent() + 1);
for (Iterator<MethodNode> it = mthList.iterator(); it.hasNext(); ) {
MethodNode mth = it.next();
if (mth.getAttributes().contains(AttributeFlag.DONT_GENERATE))
continue;
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(" {");
insertSourceFileInfo(code, mth);
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) {
InsnGen igen = null;
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();
if (igen == null) {
// don't init mth gen if this is simple enum
MethodGen mthGen = new MethodGen(this, enumFields.getStaticMethod());
igen = new InsnGen(mthGen, enumFields.getStaticMethod(), false);
}
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(';');
}
return code;
}
public String useClass(ArgType clsType) {
if (clsType.isGenericType()) {
return clsType.getObject();
}
return useClass(ClassInfo.fromType(clsType));
}
public String useClass(ClassInfo classInfo) {
String baseClass = useClassInternal(classInfo);
ArgType[] generics = classInfo.getType().getGenericTypes();
if (generics != null) {
StringBuilder sb = new StringBuilder();
sb.append(baseClass);
sb.append('<');
int len = generics.length;
for (int i = 0; i < len; i++) {
if (i != 0) {
sb.append(", ");
}
ArgType gt = generics[i];
if (gt.isTypeKnown())
sb.append(TypeGen.translate(this, gt));
else
sb.append('?');
}
sb.append('>');
return sb.toString();
} else {
return baseClass;
}
}
private String useClassInternal(ClassInfo classInfo) {
if (parentGen != null)
return parentGen.useClassInternal(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;
}
private void insertSourceFileInfo(CodeWriter code, AttrNode node) {
IAttribute sourceFileAttr = node.getAttributes().get(AttributeType.SOURCE_FILE);
if(sourceFileAttr != null) {
code.startLine(1, "// compiled from: ");
code.add(((SourceFileAttr)sourceFileAttr).getFileName());
}
}
public Set<ClassInfo> getImports() {
return imports;
}
public ClassGen getParentGen() {
return parentGen;
}
public AnnotationGen getAnnotationGen() {
return annotationGen;
}
public boolean isFallbackMode() {
return fallback;
}
}
@@ -0,0 +1,39 @@
package jadx.core.codegen;
import jadx.api.IJadxArgs;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.utils.exceptions.CodegenException;
import java.io.File;
public class CodeGen extends AbstractVisitor {
private final File dir;
private final IJadxArgs args;
public CodeGen(IJadxArgs 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();
cls.setCode(clsCode);
// 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,191 @@
package jadx.core.codegen;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.io.File;
import java.io.PrintWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CodeWriter {
private static final Logger LOG = LoggerFactory.getLogger(CodeWriter.class);
private static final int MAX_FILENAME_LENGTH = 128;
public static final String NL = System.getProperty("line.separator");
private static final 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(char c) {
buf.append(NL);
buf.append(indentStr);
buf.append(c);
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[] INDENT_CACHE = new String[] {
"",
INDENT,
INDENT + INDENT,
INDENT + INDENT + INDENT,
INDENT + INDENT + INDENT + INDENT,
INDENT + INDENT + INDENT + INDENT + INDENT,
};
private void updateIndent() {
int curIndent = indent;
if (curIndent < 6) {
this.indentStr = INDENT_CACHE[curIndent];
} else {
StringBuilder s = new StringBuilder(curIndent * INDENT.length());
for (int i = 0; i < curIndent; 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;
}
}
public boolean notEmpty() {
return buf.length() != 0;
}
@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, "UTF-8");
String code = buf.toString();
code = removeFirstEmptyLine(code);
out.print(code);
} catch (Exception 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,591 @@
package jadx.core.codegen;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.attributes.MethodInlineAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ArithOp;
import jadx.core.dex.instructions.FillArrayOp;
import jadx.core.dex.instructions.GotoNode;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.InvokeType;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.CodegenException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class InsnGen {
private static final 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,
}
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 arg((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 arg(RegisterArg arg) {
return mgen.makeArgName(arg);
}
public String assignVar(InsnNode insn) {
RegisterArg arg = insn.getResult();
if (insn.getAttributes().contains(AttributeType.DECLARE_VARIABLE)) {
return declareVar(arg);
} else {
return arg(arg);
}
}
public String declareVar(RegisterArg arg) {
return useType(arg.getType()) + " " + mgen.assignArg(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) {
return TypeGen.translate(mgen.getClassGen(), type);
}
public boolean makeInsn(InsnNode insn, CodeWriter code) throws CodegenException {
return makeInsn(insn, code, false);
}
private boolean makeInsn(InsnNode insn, CodeWriter code, boolean bodyOnly) throws CodegenException {
try {
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 false;
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(';');
}
} catch (Throwable th) {
throw new CodegenException(mth, "Error generate insn: " + insn, th);
}
return true;
}
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(useType(((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 ").add(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 ").add(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)).add(" = ").add(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)).add(" = ").add(arg(node.getArg(0)));
break;
case STR_CONCAT:
// TODO: wrap in braces only if necessary
code.add('(');
for (Iterator<InsnArg> it = insn.getArguments().iterator(); it.hasNext(); ) {
code.add(arg(it.next()));
if (it.hasNext())
code.add(" + ");
}
code.add(')');
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;
case ARGS:
code.add(arg(insn.getArg(0)));
break;
case NOP:
state.add(InsnGenState.SKIP);
break;
/* fallback mode instructions */
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(useType(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();
// inline method if METHOD_INLINE attribute is attached
MethodNode callMthNode = mth.dex().resolveMethod(callMth);
if (callMthNode != null
&& callMthNode.getAttributes().contains(AttributeType.METHOD_INLINE)) {
inlineMethod(callMthNode, insn, code);
return;
}
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 inlineMethod(MethodNode callMthNode, InvokeNode insn, CodeWriter code) throws CodegenException {
IAttribute mia = callMthNode.getAttributes().get(AttributeType.METHOD_INLINE);
InsnNode inl = ((MethodInlineAttr) mia).getInsn();
if (callMthNode.getMethodInfo().getArgumentsTypes().isEmpty()) {
makeInsn(inl, code, true);
} else {
// remap args
InsnArg[] regs = new InsnArg[callMthNode.getRegsCount()];
List<RegisterArg> callArgs = callMthNode.getArguments(true);
for (int i = 0; i < callArgs.size(); i++) {
InsnArg arg = insn.getArg(i);
RegisterArg callArg = callArgs.get(i);
regs[callArg.getRegNum()] = arg;
}
// replace args
List<RegisterArg> inlArgs = new ArrayList<RegisterArg>();
inl.getRegisterArgs(inlArgs);
Map<RegisterArg, InsnArg> toRevert = new HashMap<RegisterArg, InsnArg>();
for (RegisterArg r : inlArgs) {
if (r.getRegNum() >= regs.length) {
LOG.warn("Unknown register number {} in method call: {}, {}", r, callMthNode, mth);
} else {
InsnArg repl = regs[r.getRegNum()];
if (repl == null) {
LOG.warn("Not passed register {} in method call: {}, {}", r, callMthNode, mth);
} else {
inl.replaceArg(r, repl);
toRevert.put(r, repl);
}
}
}
makeInsn(inl, code, true);
// revert changes
for (Entry<RegisterArg, InsnArg> e : toRevert.entrySet()) {
inl.replaceArg(e.getValue(), e.getKey());
}
}
}
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, 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));
String v2 = arg(insn.getArg(1));
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('(').add(v1).add(' ').add(op.getSymbol()).add(' ').add(v2).add(')');
} else {
String res = arg(insn.getResult());
if (res.equals(v1) && insn.getResult().equals(insn.getArg(0))) {
state.add(InsnGenState.NO_RESULT);
// "++" or "--"
if (insn.getArg(1).isLiteral() && (op == ArithOp.ADD || op == ArithOp.SUB)) {
LiteralArg lit = (LiteralArg) insn.getArg(1);
if (Math.abs(lit.getLiteral()) == 1 && lit.isInteger()) {
code.add(assignVar(insn)).add(op.getSymbol()).add(op.getSymbol());
return;
}
}
// +=, -= ...
code.add(assignVar(insn)).add(' ').add(op.getSymbol()).add("= ").add(v2);
} else {
code.add(v1).add(' ').add(op.getSymbol()).add(' ').add(v2);
}
}
}
}
@@ -0,0 +1,319 @@
package jadx.core.codegen;
import jadx.core.Consts;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AttributesList;
import jadx.core.dex.attributes.JadxErrorAttr;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.visitors.DepthTraverser;
import jadx.core.dex.visitors.FallbackModeVisitor;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.CodegenException;
import jadx.core.utils.exceptions.DecodeException;
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 MethodGen {
private static final Logger LOG = LoggerFactory.getLogger(MethodGen.class);
private final MethodNode mth;
private final ClassGen classGen;
private final boolean fallback;
private final AnnotationGen annotationGen;
private final Set<String> varNames = new HashSet<String>();
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);
for (RegisterArg arg : args) {
varNames.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)
&& !mth.getAttributes().contains(AttributeType.JADX_ERROR)) {
code.startLine("// jadx: inconsistent code");
}
annotationGen.addForMethod(code, mth);
AccessInfo clsAccFlags = mth.getParentClass().getAccessFlags();
AccessInfo ai = mth.getAccessFlags();
// don't add 'abstract' to methods in interface
if (clsAccFlags.isInterface()) {
ai = ai.remove(AccessFlags.ACC_ABSTRACT);
}
// don't add 'public' for annotations
if (clsAccFlags.isAnnotation()) {
ai = ai.remove(AccessFlags.ACC_PUBLIC);
}
code.startLine(ai.makeString());
if (classGen.makeGenericMap(code, mth.getGenericMap()))
code.add(' ');
if (mth.getAccessFlags().isConstructor()) {
code.add(classGen.getClassNode().getShortName()); // constructor
} else {
code.add(TypeGen.translate(classGen, mth.getReturnType()));
code.add(' ');
code.add(mth.getName());
}
code.add('(');
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 (varNames.add(name))
return name;
if (fallback)
return name;
name = getUniqVarName(name);
arg.getTypedVar().setName(name);
return name;
}
private String getUniqVarName(String name) {
String r;
int i = 2;
do {
r = name + "_" + i;
i++;
} while (varNames.contains(r));
varNames.add(r);
return r;
}
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 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("// jadx: method processing error");
Throwable cause = err.getCause();
if (cause != null) {
code.endl();
code.add("/*");
code.startLine("Error: ").add(Utils.getStackTrace(cause));
code.add("*/");
}
makeMethodDump(code, mth);
} else {
if (mth.getRegion() != null) {
CodeWriter insns = new CodeWriter(mthIndent + 1);
(new RegionGen(this, mth)).makeRegion(insns, mth.getRegion());
if (mth.getAttributes().contains(AttributeFlag.INCONSISTENT_CODE)) {
LOG.debug(ErrorsCounter.formatErrorMsg(mth, " Inconsistent code"));
// makeMethodDump(code, mth);
}
makeInitCode(code);
code.add(insns);
} else {
makeFallbackMethod(code, mth);
}
}
return code;
}
public void makeMethodDump(CodeWriter code, MethodNode mth) {
code.startLine("/*");
getFallbackMethodGen(mth).addDefinition(code);
code.add(" {");
code.incIndent();
makeFallbackMethod(code, mth);
code.decIndent();
code.startLine('}');
code.startLine("*/");
}
private void makeFallbackMethod(CodeWriter code, MethodNode mth) {
if (mth.getInstructions() == null) {
// loadFile original instructions
try {
mth.load();
DepthTraverser.visit(new FallbackModeVisitor(), mth);
} catch (DecodeException e) {
// ignore
code.startLine("Can't loadFile method instructions");
return;
}
}
if (mth.getThisArg() != null) {
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 {
if (insnGen.makeInsn(insn, code)) {
CatchAttr _catch = (CatchAttr) attrs.get(AttributeType.CATCH_BLOCK);
if (_catch != null)
code.add("\t //" + _catch);
}
} catch (CodegenException e) {
code.startLine("// error: " + insn);
}
}
}
/**
* 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,279 @@
package jadx.core.codegen;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.DeclareVariableAttr;
import jadx.core.dex.attributes.ForceReturnAttr;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.instructions.IfOp;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.regions.IfCondition;
import jadx.core.dex.regions.IfRegion;
import jadx.core.dex.regions.LoopRegion;
import jadx.core.dex.regions.Region;
import jadx.core.dex.regions.SwitchRegion;
import jadx.core.dex.regions.SynchronizedRegion;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlock;
import jadx.core.utils.ErrorsCounter;
import jadx.core.utils.RegionUtils;
import jadx.core.utils.exceptions.CodegenException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RegionGen extends InsnGen {
private static final 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) {
code.startLine();
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) {
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 {
code.add("if (").add(makeCondition(region.getCondition())).add(") {");
makeRegionIndent(code, region.getThenRegion());
code.startLine('}');
IContainer els = region.getElseRegion();
if (els != null && RegionUtils.notEmpty(els)) {
code.add(" else ");
// connect if-else-if block
if (els instanceof Region) {
Region re = (Region) els;
List<IContainer> subBlocks = re.getSubBlocks();
if (subBlocks.size() == 1 && subBlocks.get(0) instanceof IfRegion) {
makeIf((IfRegion) subBlocks.get(0), code);
return;
}
}
code.add('{');
makeRegionIndent(code, els);
code.startLine('}');
}
}
private CodeWriter makeLoop(LoopRegion region, CodeWriter code) throws CodegenException {
IfCondition condition = region.getCondition();
if (condition == null) {
// infinite loop
code.startLine("while (true) {");
makeRegionIndent(code, region.getBody());
code.startLine('}');
return code;
}
if (region.isConditionAtEnd()) {
code.startLine("do {");
makeRegionIndent(code, region.getBody());
code.startLine("} while (").add(makeCondition(condition)).add(");");
} else {
code.startLine("while (").add(makeCondition(condition)).add(") {");
makeRegionIndent(code, region.getBody());
code.startLine('}');
}
return code;
}
private void makeSynchronizedRegion(SynchronizedRegion cont, CodeWriter code) throws CodegenException {
code.startLine("synchronized(").add(arg(cont.getInsn().getArg(0))).add(") {");
makeRegionIndent(code, cont.getRegion());
code.startLine('}');
}
private String makeCondition(IfCondition condition) throws CodegenException {
switch (condition.getMode()) {
case COMPARE:
return makeCompare(condition.getCompare());
case NOT:
return "!" + makeCondition(condition.getArgs().get(0));
case AND:
case OR:
String mode = condition.getMode() == IfCondition.MODE.AND ? " && " : " || ";
CodeWriter cw = new CodeWriter();
for (IfCondition arg : condition.getArgs()) {
if (cw.notEmpty()) {
cw.add(mode);
}
cw.add('(').add(makeCondition(arg)).add(')');
}
return cw.toString();
default:
return "??" + condition.toString();
}
}
private String makeCompare(IfCondition.Compare compare) throws CodegenException {
IfOp op = compare.getOp();
InsnArg firstArg = compare.getA();
InsnArg secondArg = compare.getB();
if (firstArg.getType().equals(ArgType.BOOLEAN)
&& secondArg.isLiteral()
&& secondArg.getType().equals(ArgType.BOOLEAN)) {
LiteralArg lit = (LiteralArg) secondArg;
if (lit.getLiteral() == 0)
op = op.invert();
if (op == IfOp.EQ) {
return arg(firstArg); // == true
} else if (op == IfOp.NE) {
return "!" + arg(firstArg); // != true
}
LOG.warn(ErrorsCounter.formatErrorMsg(mth, "Unsupported boolean condition " + op.getSymbol()));
}
return arg(firstArg) + " " + op.getSymbol() + " " + arg(secondArg);
}
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 ");
code.add(TypeGen.literalToString(k, arg.getType()));
code.add(':');
}
makeCaseBlock(c, code);
}
if (sw.getDefaultCase() != null) {
code.startLine("default:");
makeCaseBlock(sw.getDefaultCase(), code);
}
code.decIndent();
code.startLine('}');
return code;
}
private void makeCaseBlock(IContainer c, CodeWriter code) throws CodegenException {
code.add(" {");
if (RegionUtils.notEmpty(c)) {
makeRegionIndent(code, c);
if (RegionUtils.hasExitEdge(c)) {
code.startLine(1, "break;");
}
} else {
code.startLine(1, "break;");
}
code.startLine('}');
}
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(mgen.assignArg(handler.getArg()));
code.add(") {");
makeRegionIndent(code, region);
}
}
}
@@ -0,0 +1,114 @@
package jadx.core.codegen;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.utils.StringUtils;
import jadx.core.utils.Utils;
import jadx.core.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 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.core.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.core.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,20 @@
package jadx.core.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,
SKIP,
INCONSISTENT_CODE, // warning about incorrect decompilation
}
@@ -0,0 +1,62 @@
package jadx.core.dex.attributes;
public enum AttributeType {
/* Multi attributes */
JUMP(false),
LOOP(false),
CATCH_BLOCK(false),
/* Uniq attributes */
EXC_HANDLER(true),
SPLITTER_BLOCK(true),
FORCE_RETURN(true),
FIELD_VALUE(true),
JADX_ERROR(true),
METHOD_INLINE(true),
ENUM_CLASS(true),
ANNOTATION_LIST(true),
ANNOTATION_MTH_PARAMETERS(true),
SOURCE_FILE(true),
DECLARE_VARIABLE(true);
private static final int notUniqCount;
private final boolean uniq;
static {
// place all not unique attributes at first
int last = -1;
AttributeType[] vals = AttributeType.values();
for (int i = 0; i < vals.length; i++) {
AttributeType type = vals[i];
if (type.notUniq())
last = i;
}
notUniqCount = last + 1;
}
public static int getNotUniqCount() {
return notUniqCount;
}
private AttributeType(boolean isUniq) {
this.uniq = isUniq;
}
public boolean isUniq() {
return uniq;
}
public boolean notUniq() {
return !uniq;
}
}
@@ -0,0 +1,192 @@
package jadx.core.dex.attributes;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.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;
/**
* Storage for different attribute types:
* 1. flags - boolean attribute (set or not)
* 2. attribute - class instance associated for attribute type,
* only one attached to node for unique attributes, multiple for others
*/
public final 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>(0);
attrCount = new int[AttributeType.getNotUniqCount()];
}
// Flags
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);
}
// Attributes
public void add(IAttribute attr) {
if (attr.getType().isUniq())
uniqAttr.put(attr.getType(), attr);
else
addMultiAttribute(attr);
}
private void addMultiAttribute(IAttribute attr) {
attributes.add(attr);
attrCount[attr.getType().ordinal()]++;
}
private int getMultiCountInternal(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 getMultiCountInternal(type) != 0;
}
public IAttribute get(AttributeType type) {
if (type.isUniq()) {
return uniqAttr.get(type);
} else {
if (getMultiCountInternal(type) != 0) {
for (IAttribute attr : attributes)
if (attr.getType() == type)
return attr;
}
return null;
}
}
public int getCount(AttributeType type) {
if (type.isUniq()) {
return uniqAttr.containsKey(type) ? 1 : 0;
} else {
return getMultiCountInternal(type);
}
}
public Annotation getAnnotation(String cls) {
AnnotationsList aList = (AnnotationsList) get(AttributeType.ANNOTATION_LIST);
if (aList == null || aList.size() == 0)
return null;
return aList.get(cls);
}
public List<IAttribute> getAll(AttributeType type) {
assert type.notUniq();
int count = getMultiCountInternal(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 remove(IAttribute attr) {
AttributeType type = attr.getType();
if (type.isUniq()) {
IAttribute a = uniqAttr.get(type);
if (a == attr)
uniqAttr.remove(type);
} else {
if (getMultiCountInternal(type) == 0)
return;
for (Iterator<IAttribute> it = attributes.iterator(); it.hasNext();) {
IAttribute a = it.next();
if (a == attr) {
it.remove();
attrCount[type.ordinal()]--;
}
}
}
}
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.core.dex.attributes;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.TypedVar;
import jadx.core.dex.nodes.MethodNode;
public final 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.core.dex.attributes;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.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.core.dex.attributes;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.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.core.dex.attributes;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.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.core.dex.attributes;
public interface IAttribute {
AttributeType getType();
}
@@ -0,0 +1,7 @@
package jadx.core.dex.attributes;
public interface IAttributeNode {
AttributesList getAttributes();
}
@@ -0,0 +1,38 @@
package jadx.core.dex.attributes;
import jadx.core.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.core.dex.attributes;
import jadx.core.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.core.dex.attributes;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.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,26 @@
package jadx.core.dex.attributes;
import jadx.core.dex.nodes.InsnNode;
public class MethodInlineAttr implements IAttribute {
private final InsnNode insn;
public MethodInlineAttr(InsnNode insn) {
this.insn = insn;
}
public InsnNode getInsn() {
return insn;
}
@Override
public AttributeType getType() {
return AttributeType.METHOD_INLINE;
}
@Override
public String toString() {
return "INLINE: " + insn;
}
}
@@ -0,0 +1,24 @@
package jadx.core.dex.attributes;
public class SourceFileAttr implements IAttribute {
private final String fileName;
public SourceFileAttr(String fileName) {
this.fileName = fileName;
}
public String getFileName() {
return fileName;
}
@Override
public AttributeType getType() {
return AttributeType.SOURCE_FILE;
}
@Override
public String toString() {
return "SOURCE:" + fileName;
}
}
@@ -0,0 +1,48 @@
package jadx.core.dex.attributes.annotations;
import jadx.core.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;
}
public Object getDefaultValue() {
return values.get("value");
}
@Override
public String toString() {
return "Annotation[" + visibility + ", " + atype + ", " + values + "]";
}
}
@@ -0,0 +1,45 @@
package jadx.core.dex.attributes.annotations;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.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.core.dex.attributes.annotations;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.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,172 @@
package jadx.core.dex.info;
import jadx.core.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 isPublic() {
return (accFlags & AccessFlags.ACC_PUBLIC) != 0;
}
public boolean isProtected() {
return (accFlags & AccessFlags.ACC_PROTECTED) != 0;
}
public boolean isPrivate() {
return (accFlags & AccessFlags.ACC_PRIVATE) != 0;
}
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 (isPublic())
code.append("public ");
if (isPrivate())
code.append("private ");
if (isProtected())
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();
}
@Override
public String toString() {
return "AccessInfo: " + type + " " + accFlags + " (" + makeString() + ")";
}
}
@@ -0,0 +1,155 @@
package jadx.core.dex.info;
import jadx.core.Consts;
import jadx.core.deobf.NameMapper;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import java.io.File;
import java.util.Map;
import java.util.WeakHashMap;
public final class ClassInfo {
private static final Map<ArgType, ClassInfo> CLASSINFO_CACHE = new WeakHashMap<ArgType, ClassInfo>();
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(type);
}
public static ClassInfo fromName(String clsName) {
return fromType(ArgType.object(clsName));
}
public static ClassInfo fromType(ArgType type) {
ClassInfo cls = CLASSINFO_CACHE.get(type);
if (cls == null) {
cls = new ClassInfo(type);
CLASSINFO_CACHE.put(type, cls);
}
return cls;
}
public static void clearCache() {
CLASSINFO_CACHE.clear();
}
private final ArgType type;
private String pkg;
private String name;
private String fullName;
private ClassInfo parentClass; // not equals null if this is inner class
private ClassInfo(ArgType type) {
assert type.isObject() : "Not class type: " + type;
this.type = type;
splitNames(true);
}
private void splitNames(boolean canBeInner) {
String fullObjectName = type.getObject();
assert fullObjectName.indexOf('/') == -1 : "Raw type: " + type;
String name;
int dot = fullObjectName.lastIndexOf('.');
if (dot == -1) {
// rename default package if it used from class with package (often for obfuscated apps),
pkg = Consts.DEFAULT_PACKAGE_NAME;
name = fullObjectName;
} else {
pkg = fullObjectName.substring(0, dot);
name = fullObjectName.substring(dot + 1);
}
int sep = name.lastIndexOf('$');
if (canBeInner && sep > 0 && sep != name.length() - 1) {
String parClsName = pkg + '.' + name.substring(0, sep);
parentClass = fromName(parClsName);
name = name.substring(sep + 1);
} else {
parentClass = null;
}
char firstChar = name.charAt(0);
if (Character.isDigit(firstChar)) {
name = "InnerClass_" + name;
} else if(firstChar == '$') {
name = "_" + name;
}
if (NameMapper.isReserved(name)) {
name += "_";
}
this.fullName = (parentClass != null ? parentClass.getFullName() : pkg) + "." + name;
this.name = name;
}
public String getFullPath() {
return pkg.replace('.', File.separatorChar)
+ File.separatorChar
+ getNameWithoutPackage().replace('.', '_');
}
public String getFullName() {
return fullName;
}
public String getShortName() {
return name;
}
public String getPackage() {
return pkg;
}
public boolean isPackageDefault() {
return pkg.isEmpty() || pkg.equals(Consts.DEFAULT_PACKAGE_NAME);
}
public String getNameWithoutPackage() {
return (parentClass != null ? parentClass.getNameWithoutPackage() + "." : "") + name;
}
public ClassInfo getParentClass() {
return parentClass;
}
public boolean isInner() {
return parentClass != null;
}
public void notInner() {
splitNames(false);
}
public ArgType getType() {
return type;
}
@Override
public String toString() {
return getFullName();
}
@Override
public int hashCode() {
return type.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj instanceof ClassInfo) {
ClassInfo other = (ClassInfo) obj;
return this.getFullName().equals(other.getFullName());
}
return false;
}
}
@@ -0,0 +1,46 @@
package jadx.core.dex.info;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import com.android.dx.io.FieldId;
public class FieldInfo {
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);
}
private 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,115 @@
package jadx.core.dex.info;
import jadx.core.codegen.TypeGen;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode;
import jadx.core.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;
}
public String getFullId() {
return declClass.getFullName() + "." + shortId;
}
/**
* 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 int getArgsCount() {
return args.size();
}
public boolean isConstructor() {
return name.equals("<init>");
}
public boolean isClassInit() {
return name.equals("<clinit>");
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + declClass.hashCode();
result = prime * result + retType.hashCode();
result = prime * result + shortId.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;
MethodInfo other = (MethodInfo) obj;
if (!shortId.equals(other.shortId)) return false;
if (!retType.equals(other.retType)) return false;
if (!declClass.equals(other.declClass)) return false;
return true;
}
@Override
public String toString() {
return retType + " " + declClass.getFullName() + "." + name
+ "(" + Utils.listToString(args) + ")";
}
}
@@ -0,0 +1,74 @@
package jadx.core.dex.instructions;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import com.android.dx.io.instructions.DecodedInstruction;
public class ArithNode extends InsnNode {
private final ArithOp op;
public ArithNode(DecodedInstruction insn, ArithOp op, ArgType type, boolean literal) {
super(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(ArithOp op, RegisterArg res, InsnArg a, InsnArg b) {
super(InsnType.ARITH, 2);
this.op = op;
setResult(res);
addArg(a);
addArg(b);
}
public ArithNode(ArithOp op, RegisterArg res, InsnArg a) {
super(InsnType.ARITH, 1);
this.op = op;
setResult(res);
addArg(a);
}
public ArithOp getOp() {
return op;
}
@Override
public String toString() {
return InsnUtils.formatOffset(offset) + ": "
+ InsnUtils.insnTypeToString(insnType)
+ getResult() + " = "
+ getArg(0) + " "
+ op.getSymbol() + " "
+ (getArgsCount() == 2 ? getArg(1) : "");
}
}
@@ -0,0 +1,28 @@
package jadx.core.dex.instructions;
public enum ArithOp {
ADD("+"),
SUB("-"),
MUL("*"),
DIV("/"),
REM("%"),
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,43 @@
package jadx.core.dex.instructions;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.nodes.InsnNode;
import com.android.dx.io.instructions.FillArrayDataPayloadDecodedInstruction;
public class FillArrayOp extends InsnNode {
private final Object data;
public FillArrayOp(int resReg, FillArrayDataPayloadDecodedInstruction payload) {
super(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,27 @@
package jadx.core.dex.instructions;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
public class GotoNode extends InsnNode {
protected int target;
public GotoNode(int target) {
this(InsnType.GOTO, target);
}
protected GotoNode(InsnType type, int target) {
super(type);
this.target = target;
}
public int getTarget() {
return target;
}
@Override
public String toString() {
return super.toString() + "-> " + InsnUtils.formatOffset(target);
}
}
@@ -0,0 +1,77 @@
package jadx.core.dex.instructions;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.utils.InsnUtils;
import com.android.dx.io.instructions.DecodedInstruction;
public class IfNode extends GotoNode {
protected boolean zeroCmp;
protected IfOp op;
public IfNode(int targ, InsnArg then, InsnArg els) {
super(InsnType.IF, targ);
addArg(then);
if (els == null) {
zeroCmp = true;
} else {
zeroCmp = false;
addArg(els);
}
}
public IfNode(DecodedInstruction insn, IfOp op) {
super(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,44 @@
package jadx.core.dex.instructions;
import jadx.core.utils.exceptions.JadxRuntimeException;
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:
throw new JadxRuntimeException("Unknown if operations type: " + this);
}
}
}
@@ -0,0 +1,23 @@
package jadx.core.dex.instructions;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
public class IndexInsnNode extends InsnNode {
private final Object index;
public IndexInsnNode(InsnType type, Object index, int argCount) {
super(type, argCount);
this.index = index;
}
public Object getIndex() {
return index;
}
@Override
public String toString() {
return super.toString() + " " + InsnUtils.indexToString(index);
}
}
@@ -0,0 +1,711 @@
package jadx.core.dex.instructions;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.PrimitiveType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.DecodeException;
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 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() throws DecodeException {
InsnNode[] instructions = new InsnNode[insnArr.length];
for (int i = 0; i < insnArr.length; i++) {
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;
}
}
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(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(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(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_LIT8:
case Opcodes.RSUB_INT: // LIT16
return new ArithNode(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(insn, IfOp.EQ);
case Opcodes.IF_NE:
case Opcodes.IF_NEZ:
return new IfNode(insn, IfOp.NE);
case Opcodes.IF_GT:
case Opcodes.IF_GTZ:
return new IfNode(insn, IfOp.GT);
case Opcodes.IF_GE:
case Opcodes.IF_GEZ:
return new IfNode(insn, IfOp.GE);
case Opcodes.IF_LT:
case Opcodes.IF_LTZ:
return new IfNode(insn, IfOp.LT);
case Opcodes.IF_LE:
case Opcodes.IF_LEZ:
return new IfNode(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(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(InsnType.RETURN, 0);
case Opcodes.RETURN:
case Opcodes.RETURN_WIDE:
case Opcodes.RETURN_OBJECT:
return insn(InsnType.RETURN,
null,
InsnArg.reg(insn, 0, method.getReturnType()));
case Opcodes.INSTANCE_OF: {
InsnNode node = new IndexInsnNode(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(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(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(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(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(InsnType.SPUT, field, 1);
node.addArg(InsnArg.reg(insn, 0, field.getType()));
return node;
}
case Opcodes.ARRAY_LENGTH: {
InsnNode node = new InsnNode(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(InsnArg.reg(insn, 0, ArgType.NARROW), keys, targets, nextOffset);
}
private InsnNode fillArray(DecodedInstruction insn) {
DecodedInstruction payload = insnArr[insn.getTarget()];
return new FillArrayOp(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(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(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);
MethodInfo mth = MethodInfo.fromDex(dex, insn.getIndex());
return new InvokeNode(mth, insn, type, isRange, resReg);
}
private InsnNode arrayGet(DecodedInstruction insn, ArgType argType) {
InsnNode inode = new InsnNode(InsnType.AGET, 2);
inode.setResult(InsnArg.reg(insn, 0, argType));
inode.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
inode.addArg(InsnArg.reg(insn, 2, ArgType.INT));
return inode;
}
private InsnNode arrayPut(DecodedInstruction insn, ArgType argType) {
InsnNode inode = new InsnNode(InsnType.APUT, 3);
inode.addArg(InsnArg.reg(insn, 1, ArgType.unknown(PrimitiveType.ARRAY)));
inode.addArg(InsnArg.reg(insn, 2, ArgType.INT));
inode.addArg(InsnArg.reg(insn, 0, argType));
return inode;
}
private InsnNode arith(DecodedInstruction insn, ArithOp op, ArgType type) {
return new ArithNode(insn, op, type, false);
}
private InsnNode arith_lit(DecodedInstruction insn, ArithOp op, ArgType type) {
return new ArithNode(insn, op, type, true);
}
private InsnNode neg(DecodedInstruction insn, ArgType type) {
InsnNode inode = new InsnNode(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(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,60 @@
package jadx.core.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,
STR_CONCAT, // strings concatenation
TERNARY,
ARGS, // just generate arguments
NEW_MULTIDIM_ARRAY // TODO: now multidimensional arrays created using Array.newInstance function
}
@@ -0,0 +1,55 @@
package jadx.core.dex.instructions;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
import com.android.dx.io.instructions.DecodedInstruction;
public class InvokeNode extends InsnNode {
private final InvokeType type;
private final MethodInfo mth;
public InvokeNode(MethodInfo mth, DecodedInstruction insn, InvokeType type, boolean isRange, int resReg) {
super(InsnType.INVOKE, mth.getArgsCount() + (type != InvokeType.STATIC ? 1 : 0));
this.mth = mth;
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.core.dex.instructions;
public enum InvokeType {
STATIC,
DIRECT,
VIRTUAL,
INTERFACE,
SUPER,
}
@@ -0,0 +1,51 @@
package jadx.core.dex.instructions;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.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(InsnArg arg, int[] keys, int[] targets, int def) {
super(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,581 @@
package jadx.core.dex.instructions.args;
import jadx.core.Consts;
import jadx.core.utils.Utils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class ArgType {
private static final Logger LOG = LoggerFactory.getLogger(ArgType.class);
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 UNKNOWN_OBJECT = unknown(PrimitiveType.OBJECT, PrimitiveType.ARRAY);
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);
protected int hash;
private static ArgType primitive(PrimitiveType stype) {
return new PrimitiveArg(stype);
}
public static ArgType object(String obj) {
return new ObjectArg(obj);
}
public static ArgType genericType(String type) {
return new GenericTypeArg(type);
}
public static ArgType generic(String sign) {
return parseSignature(sign);
}
public static ArgType generic(String obj, ArgType[] generics) {
return new GenericObjectArg(obj, generics);
}
public static ArgType array(ArgType vtype) {
return new ArrayArg(vtype);
}
public static ArgType unknown(PrimitiveType... types) {
return new UnknownArg(types);
}
private static abstract class KnownTypeArg extends ArgType {
@Override
public boolean isTypeKnown() {
return true;
}
}
private static final class PrimitiveArg extends KnownTypeArg {
private final PrimitiveType type;
public PrimitiveArg(PrimitiveType type) {
this.type = type;
this.hash = type.hashCode();
}
@Override
public PrimitiveType getPrimitiveType() {
return type;
}
@Override
public boolean isPrimitive() {
return true;
}
@Override
public String toString() {
return type.toString();
}
}
private static class ObjectArg extends KnownTypeArg {
private final String object;
public ObjectArg(String obj) {
this.object = Utils.cleanObjectName(obj);
this.hash = obj.hashCode();
}
@Override
public String getObject() {
return object;
}
@Override
public boolean isObject() {
return true;
}
@Override
public PrimitiveType getPrimitiveType() {
return PrimitiveType.OBJECT;
}
@Override
public String toString() {
return object;
}
}
private static final class GenericTypeArg extends ObjectArg {
public GenericTypeArg(String obj) {
super(obj);
}
@Override
public boolean isGenericType() {
return true;
}
}
private static final class GenericObjectArg extends ObjectArg {
private final ArgType[] generics;
public GenericObjectArg(String obj, ArgType[] generics) {
super(obj);
this.generics = generics;
this.hash = obj.hashCode() + 31 * Arrays.hashCode(generics);
}
@Override
public ArgType[] getGenericTypes() {
return generics;
}
@Override
public String toString() {
return super.toString() + "<" + Utils.arrayToString(generics) + ">";
}
}
private static final class ArrayArg extends KnownTypeArg {
private final ArgType arrayElement;
public ArrayArg(ArgType arrayElement) {
this.arrayElement = arrayElement;
this.hash = arrayElement.hashCode();
}
@Override
public ArgType getArrayElement() {
return arrayElement;
}
@Override
public boolean isArray() {
return true;
}
@Override
public PrimitiveType getPrimitiveType() {
return PrimitiveType.ARRAY;
}
@Override
public int getArrayDimension() {
return 1 + arrayElement.getArrayDimension();
}
@Override
public ArgType getArrayRootElement() {
return arrayElement.getArrayRootElement();
}
@Override
public String toString() {
return arrayElement.toString() + "[]";
}
}
private static final class UnknownArg extends ArgType {
private final PrimitiveType possibleTypes[];
public UnknownArg(PrimitiveType[] types) {
this.possibleTypes = types;
this.hash = Arrays.hashCode(possibleTypes);
}
@Override
public PrimitiveType[] getPossibleTypes() {
return possibleTypes;
}
@Override
public boolean isTypeKnown() {
return false;
}
@Override
public boolean contains(PrimitiveType type) {
for (PrimitiveType t : possibleTypes)
if (t == type)
return true;
return false;
}
@Override
public ArgType selectFirst() {
PrimitiveType f = possibleTypes[0];
if (f == PrimitiveType.OBJECT || f == PrimitiveType.ARRAY)
return object(Consts.CLASS_OBJECT);
else
return primitive(f);
}
@Override
public String toString() {
if (possibleTypes.length == PrimitiveType.values().length)
return "?";
else
return "?" + Arrays.toString(possibleTypes);
}
}
public boolean isTypeKnown() {
return false;
}
public PrimitiveType getPrimitiveType() {
return null;
}
public boolean isPrimitive() {
return false;
}
public String getObject() {
throw new UnsupportedOperationException();
}
public boolean isObject() {
return false;
}
public boolean isGenericType() {
return false;
}
public ArgType[] getGenericTypes() {
return null;
}
public boolean isArray() {
return false;
}
public int getArrayDimension() {
return 0;
}
public ArgType getArrayElement() {
return null;
}
public ArgType getArrayRootElement() {
return this;
}
public boolean contains(PrimitiveType type) {
throw new UnsupportedOperationException();
}
public ArgType selectFirst() {
throw new UnsupportedOperationException();
}
public PrimitiveType[] getPossibleTypes() {
return null;
}
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.isTypeKnown()) {
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.getPossibleTypes()) {
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())) {
if (a.getGenericTypes() != null)
return a;
else
return b;
} else if (a.getObject().equals(OBJECT.getObject()))
return b;
else if (b.getObject().equals(OBJECT.getObject()))
return a;
else
// different objects
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) {
char f = type.charAt(0);
switch (f) {
case 'L':
return object(type);
case 'T':
return genericType(type.substring(1, type.length() - 1));
case '[':
return array(parse(type.substring(1)));
default:
return parse(f);
}
}
public static ArgType parseSignature(String sign) {
int b = sign.indexOf('<');
if (b == -1)
return parse(sign);
if (sign.charAt(0) == '[')
return array(parseSignature(sign.substring(1)));
String obj = sign.substring(0, b) + ";";
String genericsStr = sign.substring(b + 1, sign.length() - 2);
List<ArgType> generics = parseSignatureList(genericsStr);
if (generics != null)
return generic(obj, generics.toArray(new ArgType[generics.size()]));
else
return object(obj);
}
public static List<ArgType> parseSignatureList(String str) {
try {
return parseSignatureListInner(str, true);
} catch (Throwable e) {
LOG.warn("Signature parse exception: {}", str, e);
return null;
}
}
private static List<ArgType> parseSignatureListInner(String str, boolean parsePrimitives) {
if (str.isEmpty()) {
return Collections.emptyList();
}
if (str.equals("*")) {
return Arrays.asList(UNKNOWN);
}
List<ArgType> signs = new ArrayList<ArgType>(3);
int obj = 0;
int objStart = 0;
int gen = 0;
int arr = 0;
int pos = 0;
ArgType type = null;
while (pos < str.length()) {
char c = str.charAt(pos);
switch (c) {
case 'L':
case 'T':
if (obj == 0 && gen == 0) {
obj++;
objStart = pos;
}
break;
case ';':
if (obj == 1 && gen == 0) {
obj--;
String o = str.substring(objStart, pos + 1);
type = parseSignature(o);
}
break;
case ':': // generic types map separator
if (gen == 0) {
obj = 0;
String o = str.substring(objStart, pos);
if (o.length() > 0)
type = genericType(o);
}
break;
case '<':
gen++;
break;
case '>':
gen--;
break;
case '[':
if (obj == 0 && gen == 0) {
arr++;
}
break;
default:
if (parsePrimitives && obj == 0 && gen == 0) {
type = parse(c);
}
break;
}
if (type != null) {
if (arr == 0) {
signs.add(type);
} else {
for (int i = 0; i < arr; i++) {
type = array(type);
}
signs.add(type);
arr = 0;
}
type = null;
objStart = pos + 1;
}
pos++;
}
return signs;
}
public static Map<ArgType, List<ArgType>> parseGenericMap(String gen) {
try {
Map<ArgType, List<ArgType>> genericMap = null;
List<ArgType> genTypes = parseSignatureListInner(gen, false);
if (genTypes != null) {
genericMap = new LinkedHashMap<ArgType, List<ArgType>>(2);
ArgType prev = null;
List<ArgType> genList = new ArrayList<ArgType>(2);
for (ArgType arg : genTypes) {
if (arg.isGenericType()) {
if (prev != null) {
genericMap.put(prev, genList);
genList = new ArrayList<ArgType>();
}
prev = arg;
} else {
if (!arg.getObject().equals(Consts.CLASS_OBJECT))
genList.add(arg);
}
}
if (prev != null) {
genericMap.put(prev, genList);
}
}
return genericMap;
} catch (Throwable e) {
LOG.warn("Generic map parse exception: {}", gen, e);
return null;
}
}
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;
}
return null;
}
public int getRegCount() {
if (isPrimitive()) {
PrimitiveType type = getPrimitiveType();
if (type == PrimitiveType.LONG || type == PrimitiveType.DOUBLE)
return 2;
}
return 1;
}
@Override
public String toString() {
return "ARG_TYPE";
}
@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;
// TODO: don't use toString
return toString().equals(obj.toString());
}
}
@@ -0,0 +1,79 @@
package jadx.core.dex.instructions.args;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.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;
}
public int getRegNum() {
throw new UnsupportedOperationException("Must be called from RegisterArg");
}
}
@@ -0,0 +1,34 @@
package jadx.core.dex.instructions.args;
import jadx.core.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,44 @@
package jadx.core.dex.instructions.args;
import jadx.core.codegen.TypeGen;
import jadx.core.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;
}
public boolean isInteger() {
PrimitiveType type = typedVar.getType().getPrimitiveType();
return (type == PrimitiveType.INT
|| type == PrimitiveType.BYTE
|| type == PrimitiveType.CHAR
|| type == PrimitiveType.SHORT
|| type == PrimitiveType.LONG);
}
@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.core.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 longName;
}
}
@@ -0,0 +1,103 @@
package jadx.core.dex.instructions.args;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.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;
}
@Override
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.core.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,86 @@
package jadx.core.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 newType) {
if (newType != null && !type.equals(newType)) {
type = newType;
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 (!type.equals(other.type)) return false;
if (name == null) {
if (other.name != null) return false;
} else if (!name.equals(other.name)) {
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if(name != null)
sb.append('\'').append(name).append("' ");
sb.append(type);
return sb.toString();
}
}
@@ -0,0 +1,76 @@
package jadx.core.dex.instructions.mods;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.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 CallType callType;
public ConstructorInsn(MethodNode mth, InvokeNode invoke) {
super(InsnType.CONSTRUCTOR, invoke.getArgsCount() - 1);
this.callMth = invoke.getCallMth();
ClassInfo classType = callMth.getDeclClass();
if (invoke.getArg(0).isThis()) {
if (classType.equals(mth.getParentClass().getClassInfo())) {
if (callMth.getShortId().equals(mth.getMethodInfo().getShortId())) {
// self constructor
callType = CallType.SELF;
} else if (mth.getMethodInfo().isConstructor()) {
callType = CallType.THIS;
}
} else {
callType = CallType.SUPER;
}
}
if (callType == null) {
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,30 @@
package jadx.core.dex.instructions.mods;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IfOp;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.utils.InsnUtils;
import jadx.core.utils.Utils;
public class TernaryInsn extends IfNode {
public TernaryInsn(IfOp op, InsnNode then, InsnNode els) {
super(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,170 @@
package jadx.core.dex.nodes;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.BlockRegState;
import jadx.core.dex.attributes.LoopAttr;
import jadx.core.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 int id;
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 List<BlockNode> cleanSuccessors;
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;
public BlockNode(int id, int offset) {
this.id = id;
this.startOffset = offset;
}
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;
}
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,344 @@
package jadx.core.dex.nodes;
import jadx.core.Consts;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.SourceFileAttr;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.AccessInfo.AFType;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.parser.AnnotationsParser;
import jadx.core.dex.nodes.parser.FieldValueAttr;
import jadx.core.dex.nodes.parser.StaticValuesParser;
import jadx.core.utils.Utils;
import jadx.core.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 static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
private final DexNode dex;
private final ClassInfo clsInfo;
private ClassInfo superClass;
private List<ClassInfo> interfaces;
private Map<ArgType, List<ArgType>> genericMap;
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>();
private CodeWriter code; // generated code
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 loadFile
} 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);
parseClassSignature();
setFieldsTypesFromSignature();
int sfIdx = cls.getSourceFileIndex();
if (sfIdx != DexNode.NO_INDEX) {
String fileName = dex.getString(sfIdx);
if (!this.getFullName().contains(fileName.replace(".java", ""))) {
this.getAttributes().add(new SourceFileAttr(fileName));
LOG.debug("Class '{}' compiled from '{}'", this, fileName);
}
}
int accFlagsValue;
Annotation a = getAttributes().getAnnotation(Consts.DALVIK_INNER_CLASS);
if (a != null)
accFlagsValue = (Integer) a.getValues().get("accessFlags");
else
accFlagsValue = cls.getAccessFlags();
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) {
AccessInfo accFlags = f.getAccessFlags();
if (accFlags.isStatic() && accFlags.isFinal()) {
FieldValueAttr fv = (FieldValueAttr) f.getAttributes().get(AttributeType.FIELD_VALUE);
if (fv != null && fv.getValue() != null) {
if (accFlags.isPublic())
dex.getConstFields().put(fv.getValue(), f);
else
constFields.put(fv.getValue(), f);
}
}
}
}
}
@SuppressWarnings("unchecked")
private void parseClassSignature() {
Annotation a = this.getAttributes().getAnnotation(Consts.DALVIK_SIGNATURE);
if (a == null)
return;
String sign = Utils.mergeSignature((List<String>) a.getDefaultValue());
// parse generic map
int end = Utils.getGenericEnd(sign);
if (end != -1) {
String gen = sign.substring(1, end);
genericMap = ArgType.parseGenericMap(gen);
sign = sign.substring(end + 1);
}
// parse super class signature and interfaces
List<ArgType> list = ArgType.parseSignatureList(sign);
if (list != null && !list.isEmpty()) {
try {
ArgType st = list.remove(0);
this.superClass = ClassInfo.fromType(st);
int i = 0;
for (ArgType it : list) {
ClassInfo interf = ClassInfo.fromType(it);
interfaces.set(i, interf);
i++;
}
} catch (Throwable e) {
LOG.warn("Can't set signatures for class: {}, sign: {}", this, sign, e);
}
}
}
@SuppressWarnings("unchecked")
private void setFieldsTypesFromSignature() {
for (FieldNode field : fields) {
Annotation a = field.getAttributes().getAnnotation(Consts.DALVIK_SIGNATURE);
if (a == null)
continue;
String sign = Utils.mergeSignature((List<String>) a.getDefaultValue());
ArgType gType = ArgType.parseSignature(sign);
if (gType != null)
field.setType(gType);
}
}
@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 Map<ArgType, List<ArgType>> getGenericMap() {
return genericMap;
}
public List<MethodNode> getMethods() {
return methods;
}
public List<FieldNode> getFields() {
return fields;
}
public FieldNode getConstField(Object o) {
FieldNode field = constFields.get(o);
if(field == null)
field = dex.getConstFields().get(o);
return field;
}
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 searchMethod(MethodInfo mth) {
for (MethodNode m : methods) {
if (m.getMethodInfo().equals(mth))
return m;
}
return null;
}
public MethodNode searchMethodByName(String shortId) {
for (MethodNode m : methods) {
if (m.getMethodInfo().getShortId().equals(shortId))
return m;
}
return null;
}
public MethodNode searchMethodById(int id) {
return searchMethodByName(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 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();
}
public void setCode(CodeWriter code) {
this.code = code;
}
public CodeWriter getCode() {
return code;
}
@Override
public String toString() {
return getFullName();
}
}
@@ -0,0 +1,121 @@
package jadx.core.dex.nodes;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.files.InputFile;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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 static final int NO_INDEX = -1;
private final RootNode root;
private final DexBuffer dexBuf;
private final List<ClassNode> classes = new ArrayList<ClassNode>();
private final String[] strings;
private final Map<Object, FieldNode> constFields = new HashMap<Object, FieldNode>();
public DexNode(RootNode root, InputFile input) {
this.root = root;
this.dexBuf = input.getDexBuffer();
List<String> stringList = dexBuf.strings();
this.strings = stringList.toArray(new String[stringList.size()]);
}
public void loadClasses() 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);
}
public MethodNode resolveMethod(MethodInfo mth) {
ClassNode cls = resolveClass(mth.getDeclClass());
if (cls != null) {
return cls.searchMethod(mth);
}
return null;
}
public Map<Object, FieldNode> getConstFields() {
return constFields;
}
// DexBuffer wrappers
public String getString(int index) {
return strings[index];
}
public ArgType getType(int index) {
return ArgType.parse(getString(dexBuf.typeIds().get(index)));
}
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,48 @@
package jadx.core.dex.nodes;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.AccessInfo.AFType;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.args.ArgType;
import com.android.dx.io.ClassData.Field;
public class FieldNode extends AttrNode {
private final FieldInfo fieldInfo;
private final AccessInfo accFlags;
private ArgType type; // store signature
public FieldNode(ClassNode cls, Field field) {
this.fieldInfo = FieldInfo.fromDex(cls.dex(), field.getFieldIndex());
this.type = fieldInfo.getType();
this.accFlags = new AccessInfo(field.getAccessFlags(), AFType.FIELD);
}
public FieldInfo getFieldInfo() {
return fieldInfo;
}
public AccessInfo getAccessFlags() {
return accFlags;
}
public String getName() {
return fieldInfo.getName();
}
public ArgType getType() {
return type;
}
public void setType(ArgType type) {
this.type = type;
}
@Override
public String toString() {
return fieldInfo.getDeclClass() + "." + fieldInfo.getName() + " " + type;
}
}
@@ -0,0 +1,8 @@
package jadx.core.dex.nodes;
import java.util.List;
public interface IBlock extends IContainer {
public List<InsnNode> getInstructions();
}
@@ -0,0 +1,11 @@
package jadx.core.dex.nodes;
import jadx.core.dex.attributes.AttributesList;
import jadx.core.dex.attributes.IAttributeNode;
public interface IContainer extends IAttributeNode {
@Override
public AttributesList getAttributes();
}
@@ -0,0 +1,19 @@
package jadx.core.dex.nodes;
import jadx.core.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.core.dex.nodes;
import java.util.List;
public interface IRegion extends IContainer {
public IRegion getParent();
public List<IContainer> getSubBlocks();
}
@@ -0,0 +1,20 @@
package jadx.core.dex.nodes;
import jadx.core.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,167 @@
package jadx.core.dex.nodes;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.utils.InsnUtils;
import jadx.core.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(InsnType type) {
this(type, 1);
}
public InsnNode(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() && 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++) {
InsnArg arg = arguments.get(i);
if (arg == from) {
// TODO correct remove from use list
// from.getTypedVar().getUseList().remove(from);
setArg(i, to);
return true;
} else if (arg.isInsnWrap()) {
if (((InsnWrapArg) arg).getWrapInsn().replaceArg(from, 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 (obj == null) return false;
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,487 @@
package jadx.core.dex.nodes;
import jadx.core.Consts;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.JumpAttribute;
import jadx.core.dex.attributes.LoopAttr;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.AccessInfo.AFType;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.GotoNode;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.InsnDecoder;
import jadx.core.dex.instructions.SwitchNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.parser.DebugInfoParser;
import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlock;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.DecodeException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
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 static final 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 ArgType retType;
private RegisterArg thisArg;
private List<RegisterArg> argsList;
private Map<ArgType, List<ArgType>> genericMap;
private List<BlockNode> blocks;
private BlockNode enterBlock;
private List<BlockNode> exitBlocks;
private ConstructorInsn superCall;
private IContainer region;
private List<ExceptionHandler> exceptionHandlers;
private List<LoopAttr> loops = Collections.emptyList();
public MethodNode(ClassNode classNode, Method mthData) {
this.mthInfo = MethodInfo.fromDex(classNode.dex(), mthData.getMethodIndex());
this.parentClass = classNode;
this.accFlags = new AccessInfo(mthData.getAccessFlags(), AFType.METHOD);
this.noCode = (mthData.getCodeOffset() == 0);
this.methodData = (noCode ? null : mthData);
}
@Override
public void load() throws DecodeException {
try {
if (noCode) {
regsCount = 0;
initMethodTypes();
return;
}
DexNode dex = parentClass.dex();
Code mthCode = dex.readCode(methodData);
regsCount = mthCode.getRegistersSize();
initMethodTypes();
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();
initTryCatches(mthCode, insnByOffset);
initJumps(insnByOffset);
if (mthCode.getDebugInfoOffset() > 0) {
(new DebugInfoParser(this, mthCode.getDebugInfoOffset(), insnByOffset)).process();
}
} catch (Exception e) {
throw new DecodeException(this, "Load method exception", e);
}
}
private void initMethodTypes() {
if (!parseSignature()) {
retType = mthInfo.getReturnType();
initArguments(mthInfo.getArgumentsTypes());
}
}
@Override
public void unload() {
if (noCode)
return;
if (instructions != null) instructions.clear();
blocks = null;
exitBlocks = null;
if (exceptionHandlers != null) exceptionHandlers.clear();
getAttributes().clear();
noCode = true;
}
@SuppressWarnings("unchecked")
private boolean parseSignature() {
Annotation a = getAttributes().getAnnotation(Consts.DALVIK_SIGNATURE);
if (a == null)
return false;
String sign = Utils.mergeSignature((List<String>) a.getDefaultValue());
// parse generic map
int end = Utils.getGenericEnd(sign);
if (end != -1) {
String gen = sign.substring(1, end);
genericMap = ArgType.parseGenericMap(gen);
sign = sign.substring(end + 1);
}
int firstBracket = sign.indexOf('(');
int lastBracket = sign.lastIndexOf(')');
String argsTypesStr = sign.substring(firstBracket + 1, lastBracket);
String returnType = sign.substring(lastBracket + 1);
retType = ArgType.parseSignature(returnType);
if (retType == null) {
LOG.warn("Signature parse error: {}", returnType);
return false;
}
List<ArgType> argsTypes = ArgType.parseSignatureList(argsTypesStr);
if (argsTypes == null)
return false;
if (argsTypes.size() != mthInfo.getArgumentsTypes().size()) {
if (argsTypes.isEmpty()) {
return false;
}
if (!mthInfo.isConstructor()) {
LOG.warn("Wrong signature parse result: " + sign + " -> " + argsTypes
+ ", not generic version: " + mthInfo.getArgumentsTypes());
return false;
} else if (getParentClass().getAccessFlags().isEnum()) {
// TODO:
argsTypes.add(0, mthInfo.getArgumentsTypes().get(0));
argsTypes.add(1, mthInfo.getArgumentsTypes().get(1));
} else {
// add synthetic arg for outer class
argsTypes.add(0, mthInfo.getArgumentsTypes().get(0));
}
if (argsTypes.size() != mthInfo.getArgumentsTypes().size()) {
return false;
}
}
initArguments(argsTypes);
return true;
}
private void initArguments(List<ArgType> args) {
int pos;
if (noCode) {
pos = 1;
} else {
pos = regsCount;
for (ArgType arg : args)
pos -= arg.getRegCount();
}
if (accFlags.isStatic()) {
thisArg = null;
} else {
thisArg = InsnArg.reg(pos - 1, parentClass.getClassInfo().getType());
thisArg.getTypedVar().setName("this");
}
if (args.isEmpty()) {
argsList = Collections.emptyList();
return;
}
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;
}
public ArgType getReturnType() {
return retType;
}
public Map<ArgType, List<ArgType>> getGenericMap() {
return genericMap;
}
// TODO: 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()) {
addJump(insnByOffset, offset, target);
}
// default case
int next = InsnDecoder.getNextInsnOffset(insnByOffset, offset);
if (next != -1)
addJump(insnByOffset, offset, next);
break;
}
case IF:
int next = InsnDecoder.getNextInsnOffset(insnByOffset, offset);
if (next != -1)
addJump(insnByOffset, offset, next);
addJump(insnByOffset, offset, ((IfNode) insn).getTarget());
break;
case GOTO:
addJump(insnByOffset, offset, ((GotoNode) insn).getTarget());
break;
default:
break;
}
}
}
private static void addJump(InsnNode[] insnByOffset, int offset, int target) {
insnByOffset[target].getAttributes().add(new JumpAttribute(offset, target));
}
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 void registerLoop(LoopAttr loop) {
if(loops.isEmpty()) {
loops = new ArrayList<LoopAttr>(5);
}
loops.add(loop);
}
public LoopAttr getLoopForBlock(BlockNode block) {
for (LoopAttr loop : loops) {
if(loop.getLoopBlocks().contains(block))
return loop;
}
return null;
}
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 retType
+ " " + parentClass.getFullName() + "." + mthInfo.getName()
+ "(" + Utils.listToString(mthInfo.getArgumentsTypes()) + ")";
}
}
@@ -0,0 +1,93 @@
package jadx.core.dex.nodes;
import jadx.api.IJadxArgs;
import jadx.core.dex.info.ClassInfo;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.files.InputFile;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RootNode {
private static final Logger LOG = LoggerFactory.getLogger(RootNode.class);
private final IJadxArgs args;
private final List<InputFile> dexFiles;
private List<DexNode> dexNodes;
private final List<ClassNode> classes = new ArrayList<ClassNode>();
private final Map<String, ClassNode> names = new HashMap<String, ClassNode>();
public RootNode(IJadxArgs args, List<InputFile> dexFiles) {
this.args = args;
this.dexFiles =dexFiles;
}
public void load() throws DecodeException {
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)
dexNode.loadClasses();
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);
}
for (ClassNode cls : inner) {
ClassNode parent = resolveClass(cls.getClassInfo().getParentClass());
if (parent == null) {
cls.getClassInfo().notInner();
} else {
parent.addInnerClass(cls);
getClasses().remove(cls);
}
}
}
public List<ClassNode> getClasses() {
return classes;
}
public ClassNode searchClassByName(String fullName) {
return names.get(fullName);
}
public ClassNode resolveClass(ClassInfo cls) {
String fullName = cls.getFullName();
ClassNode rCls = searchClassByName(fullName);
return rCls;
}
public List<DexNode> getDexNodes() {
return dexNodes;
}
public IJadxArgs getJadxArgs() {
return args;
}
}
@@ -0,0 +1,97 @@
package jadx.core.dex.nodes.parser;
import jadx.core.dex.attributes.annotations.Annotation;
import jadx.core.dex.attributes.annotations.Annotation.Visibility;
import jadx.core.dex.attributes.annotations.AnnotationsList;
import jadx.core.dex.attributes.annotations.MethodParameters;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.DecodeException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.android.dx.io.DexBuffer.Section;
public class AnnotationsParser {
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);
}
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,219 @@
package jadx.core.dex.nodes.parser;
import jadx.core.dex.attributes.SourceFileAttr;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.DecodeException;
import java.util.List;
import com.android.dx.io.DexBuffer.Section;
public class DebugInfoParser {
private static final int DBG_END_SEQUENCE = 0x00;
private static final int DBG_ADVANCE_PC = 0x01;
private static final int DBG_ADVANCE_LINE = 0x02;
private static final int DBG_START_LOCAL = 0x03;
private static final int DBG_START_LOCAL_EXTENDED = 0x04;
private static final int DBG_END_LOCAL = 0x05;
private static final int DBG_RESTART_LOCAL = 0x06;
private static final int DBG_SET_PROLOGUE_END = 0x07;
private static final int DBG_SET_EPILOGUE_BEGIN = 0x08;
private static final int DBG_SET_FILE = 0x09;
private static final int DBG_FIRST_SPECIAL = 0x0a; // the smallest special opcode
private static final int DBG_LINE_BASE = -4; // the smallest line number increment
private static final int DBG_LINE_RANGE = 15; // the number of line increments represented
private final MethodNode mth;
private final Section section;
private final DexNode dex;
private final LocalVar[] locals;
private final InsnArg[] activeRegisters;
private final InsnNode[] insnByOffset;
public DebugInfoParser(MethodNode mth, int debugOffset, InsnNode[] insnByOffset) {
this.mth = mth;
this.dex = mth.dex();
this.section = dex.openSection(debugOffset);
this.locals = new LocalVar[mth.getRegsCount()];
this.activeRegisters = new InsnArg[mth.getRegsCount()];
this.insnByOffset = insnByOffset;
}
public void process() throws DecodeException {
int addr = 0;
int line;
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);
}
}
for (RegisterArg arg : mthArgs) {
int rn = arg.getRegNum();
locals[rn] = new LocalVar(arg);
activeRegisters[rn] = arg;
}
addrChange(-1, 1); // process '0' instruction
int c = section.readByte() & 0xFF;
while (c != DBG_END_SEQUENCE) {
switch (c) {
case DBG_ADVANCE_PC: {
int addrInc = section.readUleb128();
addr = addrChange(addr, addrInc);
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;
LocalVar var = new LocalVar(dex, regNum, nameId, type, DexNode.NO_INDEX);
startVar(var, 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;
LocalVar var = new LocalVar(dex, regNum, nameId, type, sign);
startVar(var, addr, line);
break;
}
case DBG_RESTART_LOCAL: {
int regNum = section.readUleb128();
LocalVar var = locals[regNum];
if (var != null) {
var.end(addr, line);
setVar(var);
var.start(addr, line);
}
break;
}
case DBG_END_LOCAL: {
int regNum = section.readUleb128();
LocalVar var = locals[regNum];
if (var != null) {
var.end(addr, line);
setVar(var);
}
break;
}
case DBG_SET_PROLOGUE_END:
case DBG_SET_EPILOGUE_BEGIN:
// do nothing
break;
case DBG_SET_FILE: {
int idx = section.readUleb128() - 1;
if (idx != DexNode.NO_INDEX) {
String sourceFile = dex.getString(idx);
mth.getAttributes().add(new SourceFileAttr(sourceFile));
}
break;
}
default: {
if (c >= DBG_FIRST_SPECIAL) {
int adjusted_opcode = c - DBG_FIRST_SPECIAL;
line += DBG_LINE_BASE + (adjusted_opcode % DBG_LINE_RANGE);
int addrInc = (adjusted_opcode / DBG_LINE_RANGE);
addr = addrChange(addr, addrInc);
} else {
throw new DecodeException("Unknown debug insn code: " + c);
}
break;
}
}
c = section.readByte() & 0xFF;
}
for (LocalVar var : locals) {
if (var != null && !var.isEnd()) {
var.end(addr, line);
setVar(var);
}
}
}
private int addrChange(int addr, int addrInc) {
int newAddr = addr + addrInc;
for (int i = addr + 1; i <= newAddr; i++) {
InsnNode insn = insnByOffset[i];
if (insn == null)
continue;
for (InsnArg arg : insn.getArguments())
if (arg.isRegister()) {
activeRegisters[arg.getRegNum()] = arg;
}
RegisterArg res = insn.getResult();
if (res != null)
activeRegisters[res.getRegNum()] = res;
}
return newAddr;
}
private void startVar(LocalVar var, int addr, int line) {
int regNum = var.getRegNum();
LocalVar prev = locals[regNum];
if (prev != null && !prev.isEnd()) {
prev.end(addr, line);
setVar(prev);
}
var.start(addr, line);
locals[regNum] = var;
}
private void setVar(LocalVar var) {
int start = var.getStartAddr();
int end = var.getEndAddr();
for (int i = start; i <= end; i++) {
InsnNode insn = insnByOffset[i];
if (insn != null)
fillLocals(insn, var);
}
merge(activeRegisters[var.getRegNum()], var);
}
private static void fillLocals(InsnNode insn, LocalVar var) {
if (insn.getResult() != null)
merge(insn.getResult(), var);
for (InsnArg arg : insn.getArguments())
merge(arg, var);
}
private static void merge(InsnArg arg, LocalVar var) {
if (arg != null && arg.isRegister()) {
if (var.getRegNum() == arg.getRegNum())
arg.setTypedVar(var.getTypedVar());
}
}
}
@@ -0,0 +1,87 @@
package jadx.core.dex.nodes.parser;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.nodes.DexNode;
import jadx.core.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.core.dex.nodes.parser;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.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,67 @@
package jadx.core.dex.nodes.parser;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.args.TypedVar;
import jadx.core.dex.nodes.DexNode;
import jadx.core.utils.InsnUtils;
final class LocalVar extends RegisterArg {
private boolean isEnd;
private int startAddr;
private int endAddr;
public LocalVar(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 LocalVar(RegisterArg arg) {
super(arg.getRegNum());
init(arg.getTypedVar().getName(), arg.getType(), null);
}
private void init(String name, ArgType type, String sign) {
if (sign != null) {
type = ArgType.generic(sign);
}
TypedVar tv = new TypedVar(type);
tv.setName(name);
setTypedVar(tv);
}
public void start(int addr, int line) {
this.isEnd = false;
this.startAddr = addr;
}
public void end(int addr, int line) {
this.isEnd = true;
this.endAddr = addr;
}
public boolean isEnd() {
return isEnd;
}
public int getStartAddr() {
return startAddr;
}
public int getEndAddr() {
return endAddr;
}
@Override
public String toString() {
return super.toString() + " " + (isEnd
? "end: " + InsnUtils.formatOffset(startAddr) + "-" + InsnUtils.formatOffset(endAddr)
: "active: " + InsnUtils.formatOffset(startAddr));
}
}
@@ -0,0 +1,27 @@
package jadx.core.dex.nodes.parser;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.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.core.dex.regions;
import jadx.core.dex.attributes.AttrNode;
import jadx.core.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,114 @@
package jadx.core.dex.regions;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IfOp;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.nodes.BlockNode;
import java.util.Arrays;
import java.util.List;
public final class IfCondition {
public static IfCondition fromIfBlock(BlockNode header) {
if (header == null)
return null;
IfNode ifNode = (IfNode) header.getInstructions().get(0);
return new IfCondition(new Compare(ifNode));
}
public static IfCondition not(IfCondition a) {
return new IfCondition(MODE.NOT, Arrays.asList(a));
}
public static IfCondition and(IfCondition a, IfCondition b) {
return new IfCondition(MODE.AND, Arrays.asList(a, b));
}
public static IfCondition or(IfCondition a, IfCondition b) {
return new IfCondition(MODE.OR, Arrays.asList(a, b));
}
public static final class Compare {
private final IfNode insn;
public Compare(IfNode ifNode) {
this.insn = ifNode;
}
public IfOp getOp() {
return insn.getOp();
}
public InsnArg getA() {
return insn.getArg(0);
}
public InsnArg getB() {
if (insn.isZeroCmp())
return InsnArg.lit(0, getA().getType());
else
return insn.getArg(1);
}
@Override
public String toString() {
return getA() + " " + getOp().getSymbol() + " " + getB();
}
}
public static enum MODE {
COMPARE,
NOT,
AND,
OR
}
private final MODE mode;
private final List<IfCondition> args;
private final Compare compare;
private IfCondition(Compare compare) {
this.mode = MODE.COMPARE;
this.compare = compare;
this.args = null;
}
private IfCondition(MODE mode, List<IfCondition> args) {
this.mode = mode;
this.args = args;
this.compare = null;
}
public MODE getMode() {
return mode;
}
public List<IfCondition> getArgs() {
return args;
}
public boolean isCompare() {
return mode == MODE.COMPARE;
}
public Compare getCompare() {
return compare;
}
@Override
public String toString() {
switch (mode) {
case COMPARE:
return compare.toString();
case NOT:
return "!" + args;
case AND:
return "&& " + args;
case OR:
return "||" + args;
}
return "??";
}
}
@@ -0,0 +1,65 @@
package jadx.core.dex.regions;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final class IfRegion extends AbstractRegion {
private final BlockNode header;
private IfCondition condition;
private IContainer thenRegion;
private IContainer elseRegion;
public IfRegion(IRegion parent, BlockNode header) {
super(parent);
assert header.getInstructions().size() == 1;
this.header = header;
this.condition = IfCondition.fromIfBlock(header);
}
public IfCondition getCondition() {
return condition;
}
public void setCondition(IfCondition condition) {
this.condition = condition;
}
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 Collections.unmodifiableList(all);
}
@Override
public String toString() {
return "IF(" + condition + ") then " + thenRegion + " else " + elseRegion;
}
}
@@ -0,0 +1,125 @@
package jadx.core.dex.regions;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public final class LoopRegion extends AbstractRegion {
// loop header contains one 'if' insn, equals null for infinite loop
private final IfCondition condition;
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.condition = IfCondition.fromIfBlock(header);
this.conditionAtEnd = reversed;
}
public IfCondition getCondition() {
return condition;
}
public BlockNode getHeader() {
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;
}
private IfNode getIfInsn() {
return (IfNode) conditionBlock.getInstructions().get(conditionBlock.getInstructions().size() - 1);
}
/**
* 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 Collections.unmodifiableList(all);
}
@Override
public String toString() {
return "LOOP";
}
}
@@ -0,0 +1,38 @@
package jadx.core.dex.regions;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer;
import jadx.core.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,65 @@
package jadx.core.dex.regions;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import java.util.ArrayList;
import java.util.Collections;
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 Collections.unmodifiableList(all);
}
@Override
public String toString() {
return "Switch: " + cases.size() + ", default: " + defCase;
}
}
@@ -0,0 +1,37 @@
package jadx.core.dex.regions;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import java.util.List;
public final class SynchronizedRegion extends AbstractRegion {
private final InsnNode insn;
private final Region region;
public SynchronizedRegion(IRegion parent, InsnNode insn) {
super(parent);
this.insn = insn;
this.region = new Region(this);
}
public InsnNode getInsn() {
return insn;
}
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.core.dex.trycatch;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.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,35 @@
package jadx.core.dex.trycatch;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.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())
+ " " + handler.getArg();
}
}
@@ -0,0 +1,105 @@
package jadx.core.dex.trycatch;
import jadx.core.Consts;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer;
import jadx.core.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 RegisterArg arg;
private TryCatchBlock tryBlock;
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 RegisterArg getArg() {
return arg;
}
public void setArg(RegisterArg arg) {
this.arg = arg;
}
public void setTryBlock(TryCatchBlock tryBlock) {
this.tryBlock = tryBlock;
}
public TryCatchBlock getTryBlock() {
return tryBlock;
}
@Override
public int hashCode() {
return (catchType == null ? 0 : 31 * 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.core.dex.trycatch;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.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,172 @@
package jadx.core.dex.trycatch;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IBlock;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.InsnContainer;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.visitors.InstructionRemover;
import jadx.core.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);
handler.setTryBlock(this);
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) {
if (finalBlock != null) {
// search catch attr
for (BlockNode block : mth.getBasicBlocks()) {
CatchAttr cb = (CatchAttr) block.getAttributes().get(AttributeType.CATCH_BLOCK);
if (cb == attr) {
for (ExceptionHandler eh : mth.getExceptionHandlers()) {
if (eh.getBlocks().contains(block)) {
TryCatchBlock tb = eh.getTryBlock();
tb.setFinalBlockFromInsns(mth, ((IBlock) finalBlock).getInstructions());
}
}
}
}
return;
}
// self destruction
for (InsnNode insn : insns)
insn.getAttributes().remove(attr);
insns.clear();
for (BlockNode block : mth.getBasicBlocks())
block.getAttributes().remove(attr);
}
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 setFinalBlockFromInsns(MethodNode mth, List<InsnNode> insns) {
InsnContainer cont = new InsnContainer();
List<InsnNode> finalBlockInsns = new ArrayList<InsnNode>(insns);
cont.setInstructions(finalBlockInsns);
setFinalBlock(cont);
InstructionRemover.unbindInsnList(finalBlockInsns);
// remove these instructions from other handlers
for (ExceptionHandler h : 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 (attr == ca)
b.getInstructions().removeAll(finalBlockInsns);
}
}
public void merge(MethodNode mth, TryCatchBlock tryBlock) {
for (InsnNode insn : tryBlock.getInsns())
this.addInsn(insn);
this.handlers.addAll(tryBlock.getHandlers());
for (ExceptionHandler eh : handlers)
eh.setTryBlock(this);
// 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.equals(other.handlers)) return false;
return true;
}
@Override
public String toString() {
return "Catch:{ " + Utils.listToString(handlers) + " }";
}
}
@@ -0,0 +1,18 @@
package jadx.core.dex.visitors;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.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,406 @@
package jadx.core.dex.visitors;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.attributes.AttributesList;
import jadx.core.dex.attributes.IAttribute;
import jadx.core.dex.attributes.JumpAttribute;
import jadx.core.dex.attributes.LoopAttr;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.SplitterBlockAttr;
import jadx.core.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;
public class BlockMakerVisitor extends AbstractVisitor {
// leave these instructions alone in block node
private static final Set<InsnType> separateInsns = EnumSet.of(
InsnType.RETURN,
InsnType.IF,
InsnType.SWITCH,
InsnType.MONITOR_ENTER,
InsnType.MONITOR_EXIT);
private static int nextBlockId;
@Override
public void visit(MethodNode mth) {
if (mth.isNoCode())
return;
mth.initBasicBlocks();
makeBasicBlocks(mth);
BlockProcessingHelper.visit(mth);
mth.finishBasicBlocks();
}
private static void makeBasicBlocks(MethodNode mth) {
nextBlockId = 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(jump.getSrc(), blocksMap);
BlockNode thisblock = getBlock(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(h.getHandleOffset(), blocksMap);
// skip self loop in handler
if (connBlock != destBlock)
connect(connBlock, destBlock);
}
}
}
}
}
computeDominators(mth);
markReturnBlocks(mth);
int i = 0;
while (modifyBlocksTree(mth)) {
// revert calculations
cleanDomTree(mth);
// recalculate dominators tree
computeDominators(mth);
markReturnBlocks(mth);
i++;
if (i > 100)
throw new AssertionError("Can't fix method cfg: " + mth);
}
registerLoops(mth);
}
private static BlockNode getBlock(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(++nextBlockId, offset);
mth.getBasicBlocks().add(block);
return block;
}
private static void computeDominators(MethodNode mth) {
List<BlockNode> basicBlocks = mth.getBasicBlocks();
int nBlocks = basicBlocks.size();
for (int i = 0; i < nBlocks; i++) {
BlockNode block = basicBlocks.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 : basicBlocks) {
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 : basicBlocks) {
block.getDoms().clear(block.getId());
}
// calculate immediate dominators
for (BlockNode block : basicBlocks) {
if (block == entryBlock)
continue;
List<BlockNode> preds = block.getPredecessors();
if (preds.size() == 1) {
block.setIDom(preds.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 = basicBlocks.get(i);
bs.andNot(dom.getDoms());
}
int c = bs.cardinality();
if (c == 1) {
int id = bs.nextSetBit(0);
BlockNode idom = basicBlocks.get(id);
block.setIDom(idom);
idom.getDominatesOn().add(block);
} else {
throw new JadxRuntimeException("Can't find immediate dominator for block " + block
+ " in " + bs + " preds:" + preds);
}
}
}
}
private static void markLoops(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
for (BlockNode succ : block.getSuccessors()) {
// Every successor that dominates its predecessor is a header of a loop,
// 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) {
for (BlockNode block : mth.getBasicBlocks()) {
List<InsnNode> insns = block.getInstructions();
if (insns.size() == 1) {
if (insns.get(0).getType() == InsnType.RETURN)
block.getAttributes().add(AttributeFlag.RETURN);
}
}
}
private static void registerLoops(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
AttributesList attributes = block.getAttributes();
IAttribute loop = attributes.get(AttributeType.LOOP);
if(loop != null && attributes.contains(AttributeFlag.LOOP_START)) {
mth.registerLoop((LoopAttr) loop);
}
}
}
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 several precessors presents
if (false && 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(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 static 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,132 @@
package jadx.core.dex.visitors;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.dex.trycatch.ExcHandlerAttr;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.dex.trycatch.TryCatchBlock;
import jadx.core.utils.BlockUtils;
public class BlockProcessingHelper {
public static void visit(MethodNode mth) {
if (mth.isNoCode())
return;
for (BlockNode block : mth.getBasicBlocks()) {
markExceptionHandlers(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(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().forceSetType(ArgType.THROWABLE);
else
excArg.getTypedVar().forceSetType(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) {
TryCatchBlock handlerBlock = handlerAttr.getTryBlock();
TryCatchBlock catchBlock = catchAttr.getTryBlock();
if (handlerBlock != catchBlock) { // TODO: why it can be?
handlerBlock.merge(mth, catchBlock);
catchBlock.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,70 @@
package jadx.core.dex.visitors;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.JadxException;
import java.util.Iterator;
import java.util.List;
public class ClassModifier 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()) {
if (!isMethodUniq(cls, mth)) {
// TODO add more checks before method deletion
it.remove();
}
}
// remove public empty constructors
if (af.isConstructor()
&& af.isPublic()
&& mth.getArguments(false).isEmpty()) {
if (mth.getSuperCall() == null) {
List<BlockNode> bb = mth.getBasicBlocks();
if (bb.isEmpty() || allBlocksEmpty(bb)) {
it.remove();
}
}
}
}
return false;
}
private static boolean allBlocksEmpty(List<BlockNode> blocks) {
for (BlockNode block : blocks) {
if (block.getInstructions().size() != 0)
return false;
}
return true;
}
private static boolean isMethodUniq(ClassNode cls, MethodNode mth) {
MethodInfo mi = mth.getMethodInfo();
for (MethodNode otherMth : cls.getMethods()) {
MethodInfo omi = otherMth.getMethodInfo();
if (omi.getName().equals(mi.getName())
&& otherMth != mth) {
if (omi.getArgumentsTypes().size() == mi.getArgumentsTypes().size()) {
// TODO: check to args objects types
return false;
}
}
}
return true;
}
}
@@ -0,0 +1,242 @@
package jadx.core.dex.visitors;
import jadx.core.Consts;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.ArithNode;
import jadx.core.dex.instructions.ArithOp;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CodeShrinker extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(CodeShrinker.class);
@Override
public void visit(MethodNode mth) {
if (mth.isNoCode() || mth.getAttributes().contains(AttributeFlag.DONT_SHRINK))
return;
shrink(mth);
pretify(mth);
}
private static void shrink(MethodNode mth) {
for (BlockNode block : mth.getBasicBlocks()) {
List<InsnNode> insnList = block.getInstructions();
InstructionRemover remover = new InstructionRemover(insnList);
for (InsnNode insn : insnList) {
// wrap instructions
RegisterArg result = insn.getResult();
if (result != null) {
List<InsnArg> useList = result.getTypedVar().getUseList();
if (useList.size() == 1) {
// variable is used only in this instruction
// TODO not correct sometimes :(
remover.add(insn);
} else if (useList.size() == 2) {
InsnArg useInsnArg = selectOther(useList, result);
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;
// TODO
if (false && result.getTypedVar().getName() != null) {
// don't wrap if result variable has name from debug info
wrap = false;
} else if (BlockUtils.blockContains(block, useInsn)) {
// TODO don't reorder methods invocations
// wrap insn from current block
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) {
if (useInsn.getType() == InsnType.MOVE) {
// TODO
// remover.add(useInsn);
} else {
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;
// fix 'c + (-1)' => 'c - (1)'
if (invert) {
return new ArithNode(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;
case INVOKE:
MethodInfo callMth = ((InvokeNode) insn).getCallMth();
if (callMth.getDeclClass().getFullName().equals(Consts.CLASS_STRING_BUILDER)
&& callMth.getShortId().equals("toString()")
&& insn.getArg(0).isInsnWrap()) {
List<InsnNode> chain = flattenInsnChain(insn);
if (chain.size() > 1 && chain.get(0).getType() == InsnType.CONSTRUCTOR) {
ConstructorInsn constr = (ConstructorInsn) chain.get(0);
if (constr.getClassType().getFullName().equals(Consts.CLASS_STRING_BUILDER)
&& constr.getArgsCount() == 0) {
int len = chain.size();
InsnNode concatInsn = new InsnNode(InsnType.STR_CONCAT, len - 1);
for (int i = 1; i < len; i++) {
concatInsn.addArg(chain.get(i).getArg(1));
}
concatInsn.setResult(insn.getResult());
return concatInsn;
}
}
}
break;
default:
break;
}
return null;
}
private static List<InsnNode> flattenInsnChain(InsnNode insn) {
List<InsnNode> chain = new ArrayList<InsnNode>();
InsnArg i = insn.getArg(0);
while (i.isInsnWrap()) {
InsnNode wrapInsn = ((InsnWrapArg) i).getWrapInsn();
chain.add(wrapInsn);
if(wrapInsn.getArgsCount() == 0)
break;
i = wrapInsn.getArg(0);
}
Collections.reverse(chain);
return chain;
}
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 > 1000)
throw new JadxRuntimeException("Can't inline arguments for: " + arg + " insn: " + assignInsn);
} while (!list.isEmpty());
return arg.wrapInstruction(assignInsn);
}
private static InsnArg selectOther(List<InsnArg> list, RegisterArg insn) {
InsnArg first = list.get(0);
if (first == insn)
return list.get(1);
else
return first;
}
}
@@ -0,0 +1,163 @@
package jadx.core.dex.visitors;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.IfNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.InvokeNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.LiteralArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.BlockUtils;
import jadx.core.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();
}
}
}
private 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 */) {
long lit = ((LiteralArg) insn.getArg(0)).getLiteral();
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 constant inline
*/
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.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,31 @@
package jadx.core.dex.visitors;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.ErrorsCounter;
public class DepthTraverser {
public static void visit(IDexTreeVisitor visitor, ClassNode cls) {
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,189 @@
package jadx.core.dex.visitors;
import jadx.core.codegen.CodeWriter;
import jadx.core.codegen.MethodGen;
import jadx.core.dex.attributes.IAttributeNode;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.IContainer;
import jadx.core.dex.nodes.IRegion;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.ExceptionHandler;
import jadx.core.utils.BlockUtils;
import jadx.core.utils.InsnUtils;
import jadx.core.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.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
if (false) {
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("\\", "") // TODO replace \"
.replace("/", "\\/")
.replace(">", "\\>").replace("<", "\\<")
.replace("{", "\\{").replace("}", "\\}")
.replace("\"", "\\\"")
.replace("-", "\\-")
.replace("|", "\\|")
.replace("\n", NL);
}
}
@@ -0,0 +1,156 @@
package jadx.core.dex.visitors;
import jadx.core.dex.attributes.AttributeFlag;
import jadx.core.dex.attributes.EnumClassAttr;
import jadx.core.dex.attributes.EnumClassAttr.EnumField;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.InsnType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.instructions.mods.ConstructorInsn;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.utils.exceptions.JadxException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class EnumVisitor extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(EnumVisitor.class);
@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();
}
}
EnumClassAttr attr = new EnumClassAttr(enumFields.size());
cls.getAttributes().add(attr);
if (staticMethod == null) {
LOG.warn("Enum class init method not found: {}", cls);
// for this broken enum puts found fields and mark as inconsistent
cls.getAttributes().add(AttributeFlag.INCONSISTENT_CODE);
for (FieldNode field : enumFields) {
attr.getFields().add(new EnumField(field.getName(), 0));
}
return false;
}
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);
if (constrArg == null)
throw new JadxException("Can't inline constructor arg in enum: " + cls);
}
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.core.dex.visitors;
import jadx.core.dex.attributes.AttributeType;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.trycatch.CatchAttr;
import jadx.core.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;
}
}
}
}
}

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