diff --git a/.gitignore b/.gitignore index 117777baf..3065caeca 100644 --- a/.gitignore +++ b/.gitignore @@ -17,8 +17,9 @@ idea/ .gradle/ gradle.properties +*-tmp/ + *.dex -*.jar *.class *.dump *.log diff --git a/build.gradle b/build.gradle index 432a7c19b..0479a2482 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,6 @@ subprojects { dependencies { compile 'org.slf4j:slf4j-api:1.7.5' - compile 'ch.qos.logback:logback-classic:1.0.13' testCompile 'junit:junit:4.11' } diff --git a/jadx-core/build.gradle b/jadx-core/build.gradle index 6a2f8a945..4d905fe6c 100644 --- a/jadx-core/build.gradle +++ b/jadx-core/build.gradle @@ -1,4 +1,9 @@ +ext.jadxClasspath = 'clsp-data/android-4.3.jar' + dependencies { compile 'com.google.android.tools:dx:1.7' + compile 'ch.qos.logback:logback-classic:1.0.13' + + runtime files(jadxClasspath) } diff --git a/jadx-core/clsp-data/android-4.3.jar b/jadx-core/clsp-data/android-4.3.jar new file mode 100644 index 000000000..d5b7e6299 Binary files /dev/null and b/jadx-core/clsp-data/android-4.3.jar differ diff --git a/jadx-core/src/main/java/jadx/api/Decompiler.java b/jadx-core/src/main/java/jadx/api/Decompiler.java index 2e1c3d117..9acc7cdd0 100644 --- a/jadx-core/src/main/java/jadx/api/Decompiler.java +++ b/jadx-core/src/main/java/jadx/api/Decompiler.java @@ -64,8 +64,9 @@ public final class Decompiler { } public List getClasses() { - List classes = new ArrayList(root.getClasses().size()); - for (ClassNode classNode : root.getClasses()) { + List classNodeList = root.getClasses(false); + List classes = new ArrayList(classNodeList.size()); + for (ClassNode classNode : classNodeList) { classes.add(new JavaClass(this, classNode)); } return Collections.unmodifiableList(classes); @@ -101,7 +102,7 @@ public final class Decompiler { LOG.info("processing ..."); ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(threadsCount); - for (ClassNode cls : root.getClasses()) { + for (ClassNode cls : root.getClasses(false)) { if (cls.getCode() == null) { ProcessClass job = new ProcessClass(cls, passList); executor.execute(job); @@ -133,10 +134,9 @@ public final class Decompiler { ClassInfo.clearCache(); ErrorsCounter.reset(); - root = new RootNode(args, inputFiles); + root = new RootNode(); LOG.info("loading ..."); - root.load(); - root.init(); + root.load(inputFiles); } void processClass(ClassNode cls) { diff --git a/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java b/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java new file mode 100644 index 000000000..7b151a2af --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/clsp/ClsSet.java @@ -0,0 +1,218 @@ +package jadx.core.clsp; + +import jadx.core.dex.info.ClassInfo; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.RootNode; +import jadx.core.utils.Utils; +import jadx.core.utils.exceptions.DecodeException; + +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Classes list for import into classpath graph + */ +public class ClsSet { + private static final Logger LOG = LoggerFactory.getLogger(ClsSet.class); + + private static final String CLST_EXTENSION = ".jcst"; + private static final String CLST_FILENAME = "core" + CLST_EXTENSION; + private static final String CLST_PKG_PATH = ClsSet.class.getPackage().getName().replace('.', '/'); + + private static final String JADX_CLS_SET_HEADER = "jadx-cst"; + private static final int VERSION = 1; + + private NClass[] classes; + + public void load(RootNode root) { + List list = root.getClasses(true); + Map names = new HashMap(list.size()); + int k = 0; + for (ClassNode cls : list) { + String clsRawName = cls.getRawName(); + if (cls.getAccessFlags().isPublic()) { + NClass nClass = new NClass(clsRawName, k); + if (names.put(clsRawName, nClass) != null) { + throw new RuntimeException("Duplicate class: " + clsRawName); + } + k++; + } else { + names.put(clsRawName, null); + } + } + classes = new NClass[k]; + k = 0; + for (ClassNode cls : list) { + if (cls.getAccessFlags().isPublic()) { + NClass nClass = getCls(cls.getRawName(), names); + nClass.setParents(makeParentsArray(cls, names)); + classes[k] = nClass; + k++; + } + } + } + + public static NClass[] makeParentsArray(ClassNode cls, Map names) { + List parents = new ArrayList(1 + cls.getInterfaces().size()); + ClassInfo superClass = cls.getSuperClass(); + if (superClass != null) { + NClass c = getCls(superClass.getRawName(), names); + if (c != null) { + parents.add(c); + } + } + for (ClassInfo iface : cls.getInterfaces()) { + NClass c = getCls(iface.getRawName(), names); + if (c != null) { + parents.add(c); + } + } + return parents.toArray(new NClass[parents.size()]); + } + + private static NClass getCls(String fullName, Map names) { + NClass id = names.get(fullName); + if (id == null && !names.containsKey(fullName)) { + LOG.warn("Class not found: " + fullName); + } + return id; + } + + void save(File output) throws IOException { + Utils.makeDirsForFile(output); + + BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output)); + String outputName = output.getName(); + if (outputName.endsWith(CLST_EXTENSION)) { + save(outputStream); + } else if (outputName.endsWith(".jar")) { + ZipOutputStream out = new ZipOutputStream(outputStream); + try { + out.putNextEntry(new ZipEntry(CLST_PKG_PATH + "/" + CLST_FILENAME)); + save(out); + out.closeEntry(); + } finally { + out.close(); + outputStream.close(); + } + } else { + throw new RuntimeException("Unknown file format: " + outputName); + } + } + + public void save(OutputStream output) throws IOException { + DataOutputStream out = new DataOutputStream(output); + out.writeBytes(JADX_CLS_SET_HEADER); + out.writeByte(VERSION); + + LOG.info("Classes count: " + classes.length); + out.writeInt(classes.length); + for (NClass cls : classes) { + writeString(out, cls.getName()); + } + for (NClass cls : classes) { + NClass[] parents = cls.getParents(); + out.writeByte(parents.length); + for (NClass parent : parents) { + out.writeInt(parent.getId()); + } + } + } + + public void load() throws IOException, DecodeException { + InputStream input = getClass().getResourceAsStream(CLST_FILENAME); + if (input == null) { + throw new RuntimeException("Can't load classpath file: " + CLST_FILENAME); + } + load(input); + } + + public void load(File input) throws IOException, DecodeException { + String name = input.getName(); + InputStream inputStream = new FileInputStream(input); + if (name.endsWith(CLST_EXTENSION)) { + load(inputStream); + } else if (name.endsWith(".jar")) { + ZipInputStream in = new ZipInputStream(inputStream); + try { + ZipEntry entry = in.getNextEntry(); + while (entry != null) { + if (entry.getName().endsWith(CLST_EXTENSION)) { + load(in); + } + entry = in.getNextEntry(); + } + } finally { + in.close(); + } + } else { + throw new RuntimeException("Unknown file format: " + name); + } + } + + public void load(InputStream input) throws IOException, DecodeException { + DataInputStream in = new DataInputStream(input); + byte[] header = new byte[JADX_CLS_SET_HEADER.length()]; + in.read(header); + int version = in.readByte(); + if (!JADX_CLS_SET_HEADER.equals(new String(header)) || version != VERSION) { + throw new DecodeException("Wrong jadx class set header"); + } + int count = in.readInt(); + classes = new NClass[count]; + for (int i = 0; i < count; i++) { + String name = readString(in); + classes[i] = new NClass(name, i); + } + for (int i = 0; i < count; i++) { + int pCount = in.readByte(); + NClass[] parents = new NClass[pCount]; + for (int j = 0; j < pCount; j++) { + parents[j] = classes[in.readInt()]; + } + classes[i].setParents(parents); + } + } + + private void writeString(DataOutputStream out, String name) throws IOException { + byte[] bytes = name.getBytes(); + out.writeByte(bytes.length); + out.write(bytes); + } + + private static String readString(DataInputStream in) throws IOException { + int len = in.readByte(); + byte[] bytes = new byte[len]; + int count = in.read(bytes); + while (count != len) { + int res = in.read(bytes, count, len - count); + if (res == -1) { + throw new IOException("String read error"); + } else { + count += res; + } + } + return new String(bytes); + } + + public NClass[] getClasses() { + return classes; + } +} diff --git a/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java b/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java new file mode 100644 index 000000000..6a880977f --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java @@ -0,0 +1,115 @@ +package jadx.core.clsp; + +import jadx.core.dex.nodes.ClassNode; +import jadx.core.utils.exceptions.DecodeException; + +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Classes hierarchy graph + */ +public class ClspGraph { + private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class); + + private Map nameMap; + private Map> ancestorCache = new WeakHashMap>(); + + public void load() throws IOException, DecodeException { + ClsSet set = new ClsSet(); + set.load(); + addClasspath(set); + } + + public void addClasspath(ClsSet set) { + NClass[] arr = set.getClasses(); + if (nameMap == null) { + nameMap = new HashMap(arr.length); + for (NClass cls : arr) { + nameMap.put(cls.getName(), cls); + } + } else { + throw new RuntimeException("Classpath already loaded"); + } + } + + public void addApp(List classes) { + if (nameMap == null) { + throw new RuntimeException("Classpath must be loaded first"); + } + int size = classes.size(); + NClass[] nClasses = new NClass[size]; + for (int i = 0; i < size; i++) { + ClassNode cls = classes.get(i); + NClass nClass = new NClass(cls.getRawName(), -1); + nClasses[i] = nClass; + nameMap.put(cls.getRawName(), nClass); + } + for (int i = 0; i < size; i++) { + nClasses[i].setParents(ClsSet.makeParentsArray(classes.get(i), nameMap)); + } + } + + public boolean isImplements(String clsName, String implClsName) { + Set anc = getAncestors(clsName); + return anc.contains(implClsName); + } + + public String getCommonAncestor(String clsName, String implClsName) { + if (isImplements(clsName, implClsName)) { + return implClsName; + } + Set anc = getAncestors(clsName); + NClass cls = nameMap.get(implClsName); + if(cls != null) { + return searchCommonParent(anc, cls); + } else { + LOG.debug("Missing class: {}", implClsName); + return null; + } + } + + private String searchCommonParent(Set anc, NClass cls) { + for (NClass p : cls.getParents()) { + String name = p.getName(); + if (anc.contains(name)) { + return name; + } else { + String r = searchCommonParent(anc, p); + if (r != null) + return r; + } + } + return null; + } + + private Set getAncestors(String clsName) { + Set result = ancestorCache.get(clsName); + if (result == null) { + result = new HashSet(); + ancestorCache.put(clsName, result); + NClass cls = nameMap.get(clsName); + if(cls != null) { + addAncestorsNames(cls, result); + } else { + LOG.debug("Missing class: {}", clsName); + } + } + return result; + } + + private void addAncestorsNames(NClass cls, Set result) { + result.add(cls.getName()); + for (NClass p : cls.getParents()) { + addAncestorsNames(p, result); + } + } +} diff --git a/jadx-core/src/main/java/jadx/core/clsp/ConvertToClsSet.java b/jadx-core/src/main/java/jadx/core/clsp/ConvertToClsSet.java new file mode 100644 index 000000000..9ea88a360 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/clsp/ConvertToClsSet.java @@ -0,0 +1,67 @@ +package jadx.core.clsp; + +import jadx.core.dex.nodes.RootNode; +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.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Utility class for convert dex or jar to jadx classes set (.jcst) + */ +public class ConvertToClsSet { + private static final Logger LOG = LoggerFactory.getLogger(ConvertToClsSet.class); + + public static void usage() { + LOG.info(" "); + } + + public static void main(String[] args) throws IOException, DecodeException { + if (args.length < 2) { + usage(); + System.exit(1); + } + File output = new File(args[0]); + + List inputFiles = new ArrayList(args.length - 1); + for (int i = 1; i < args.length; i++) { + File f = new File(args[i]); + if (f.isDirectory()) { + addFilesFromDirectory(f, inputFiles); + } else { + inputFiles.add(new InputFile(f)); + } + } + for (InputFile inputFile : inputFiles) { + LOG.info("Loaded: " + inputFile.getFile()); + } + + RootNode root = new RootNode(); + root.load(inputFiles); + + ClsSet set = new ClsSet(); + set.load(root); + set.save(output); + LOG.info("Output: " + output); + LOG.info("done"); + } + + private static void addFilesFromDirectory(File dir, List inputFiles) throws IOException, DecodeException { + File[] files = dir.listFiles(); + for (File file : files) { + if (file.isDirectory()) { + addFilesFromDirectory(file, inputFiles); + } + if (file.getName().endsWith(".dex")) { + inputFiles.add(new InputFile(file)); + } + } + } + +} diff --git a/jadx-core/src/main/java/jadx/core/clsp/NClass.java b/jadx-core/src/main/java/jadx/core/clsp/NClass.java new file mode 100644 index 000000000..5f3487b1a --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/clsp/NClass.java @@ -0,0 +1,55 @@ +package jadx.core.clsp; + +/** + * Class node in classpath graph + */ +public class NClass { + + private final String name; + private NClass[] parents; + private int id; + + public NClass(String name, int id) { + this.name = name; + this.id = id; + } + + public String getName() { + return name; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public NClass[] getParents() { + return parents; + } + + public void setParents(NClass[] parents) { + this.parents = parents; + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + NClass nClass = (NClass) o; + if (!name.equals(nClass.name)) return false; + return true; + } + + @Override + public String toString() { + return name; + } +} diff --git a/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java b/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java index 88cb1c0c9..37df2686a 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java +++ b/jadx-core/src/main/java/jadx/core/codegen/CodeWriter.java @@ -1,7 +1,7 @@ package jadx.core.codegen; import jadx.core.dex.attributes.LineAttrNode; -import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.core.utils.Utils; import java.io.File; import java.io.PrintWriter; @@ -214,7 +214,7 @@ public class CodeWriter { PrintWriter out = null; try { - makeDirsForFile(file); + Utils.makeDirsForFile(file); out = new PrintWriter(file, "UTF-8"); String code = buf.toString(); code = removeFirstEmptyLine(code); @@ -227,13 +227,4 @@ public class CodeWriter { } } - 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); - } - } } diff --git a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java index c459e77de..5993df22b 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java @@ -172,7 +172,11 @@ public class MethodGen { else return name; } else { - return base + "_" + Utils.escape(TypeGen.translate(classGen, arg.getType())); + ArgType type = arg.getType(); + if (type.isPrimitive()) + return base + type.getPrimitiveType().getShortName().toLowerCase(); + else + return base + "_" + Utils.escape(TypeGen.translate(classGen, arg.getType())); } } } diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java index bfcea7b95..48a1112eb 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java @@ -105,6 +105,10 @@ public final class ClassInfo { return name; } + public String getRawName() { + return type.getObject(); + } + public String getPackage() { return pkg; } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java index f0c077e20..bc5e9ce6d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/ArgType.java @@ -1,6 +1,7 @@ package jadx.core.dex.instructions.args; import jadx.core.Consts; +import jadx.core.clsp.ClspGraph; import jadx.core.utils.Utils; import java.util.ArrayList; @@ -43,6 +44,16 @@ public abstract class ArgType { protected int hash; + private static ClspGraph clsp; + + public static ClspGraph getClsp() { + return clsp; + } + + public static void setClsp(ClspGraph clsp) { + ArgType.clsp = clsp; + } + private static ArgType primitive(PrimitiveType stype) { return new PrimitiveArg(stype); } @@ -344,35 +355,30 @@ public abstract class ArgType { } } } else { - if(a.isGenericType()) - return a; - if(b.isGenericType()) - return b; + if (a.isGenericType()) return a; + if (b.isGenericType()) return b; 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())) + String aObj = a.getObject(); + String bObj = b.getObject(); + if (aObj.equals(bObj)) { + return (a.getGenericTypes() != null ? a : b); + } else if (aObj.equals(Consts.CLASS_OBJECT)) { return b; - else if (b.getObject().equals(OBJECT.getObject())) + } else if (bObj.equals(Consts.CLASS_OBJECT)) { return a; - else + } else { // different objects - return null; + String obj = clsp.getCommonAncestor(aObj, bObj); + return (obj == null ? null : object(obj)); + } } - 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())); + if (a.isPrimitive() && b.isPrimitive() && a.getRegCount() == b.getRegCount()) { + return primitive(PrimitiveType.getSmaller(a.getPrimitiveType(), b.getPrimitiveType())); } } return null; diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 84efa9144..469c9db51 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -240,7 +240,7 @@ public class ClassNode extends LineAttrNode implements ILoadable { public FieldNode getConstField(Object o) { FieldNode field = constFields.get(o); - if(field == null) + if (field == null) field = dex.getConstFields().get(o); return field; } @@ -329,6 +329,10 @@ public class ClassNode extends LineAttrNode implements ILoadable { return clsInfo.getPackage(); } + public String getRawName() { + return clsInfo.getRawName(); + } + public void setCode(CodeWriter code) { this.code = code; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java index c7670957d..7eebd5c73 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java @@ -1,34 +1,23 @@ package jadx.core.dex.nodes; -import jadx.api.IJadxArgs; +import jadx.core.clsp.ClspGraph; import jadx.core.dex.info.ClassInfo; +import jadx.core.dex.instructions.args.ArgType; import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.files.InputFile; +import java.io.IOException; 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 dexFiles; - - private List dexNodes; - private final List classes = new ArrayList(); private final Map names = new HashMap(); + private List dexNodes; + private ClspGraph clsp; - public RootNode(IJadxArgs args, List dexFiles) { - this.args = args; - this.dexFiles =dexFiles; - } - - public void load() throws DecodeException { + public void load(List dexFiles) throws DecodeException { dexNodes = new ArrayList(dexFiles.size()); for (InputFile dex : dexFiles) { DexNode dexNode; @@ -39,37 +28,63 @@ public class RootNode { } dexNodes.add(dexNode); } - - for (DexNode dexNode : dexNodes) - dexNode.loadClasses(); - for (DexNode dexNode : dexNodes) { - for (ClassNode cls : dexNode.getClasses()) + dexNode.loadClasses(); + } + + List classes = new ArrayList(); + for (DexNode dexNode : dexNodes) { + for (ClassNode cls : dexNode.getClasses()) { names.put(cls.getFullName(), cls); + } classes.addAll(dexNode.getClasses()); } + + try { + initClassPath(classes); + } catch (IOException e) { + throw new DecodeException("Error loading classpath", e); + } + initInnerClasses(classes); } - public void init() { + private void initClassPath(List classes) throws IOException, DecodeException { + clsp = new ClspGraph(); + clsp.load(); + clsp.addApp(classes); + + ArgType.setClsp(clsp); + } + + private void initInnerClasses(List classes) { // move inner classes List inner = new ArrayList(); - for (ClassNode cls : getClasses()) { + for (ClassNode cls : classes) { 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 getClasses() { + public List getClasses(boolean includeInner) { + List classes = new ArrayList(); + for (DexNode dexNode : dexNodes) { + for (ClassNode cls : dexNode.getClasses()) { + if (includeInner) { + classes.add(cls); + } else { + if (!cls.getClassInfo().isInner()) + classes.add(cls); + } + } + } return classes; } @@ -82,12 +97,4 @@ public class RootNode { ClassNode rCls = searchClassByName(fullName); return rCls; } - - public List getDexNodes() { - return dexNodes; - } - - public IJadxArgs getJadxArgs() { - return args; - } } diff --git a/jadx-core/src/main/java/jadx/core/utils/Utils.java b/jadx-core/src/main/java/jadx/core/utils/Utils.java index 3e0cfd90f..5288429b7 100644 --- a/jadx-core/src/main/java/jadx/core/utils/Utils.java +++ b/jadx-core/src/main/java/jadx/core/utils/Utils.java @@ -1,5 +1,8 @@ package jadx.core.utils; +import jadx.core.utils.exceptions.JadxRuntimeException; + +import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; @@ -138,4 +141,14 @@ public class Utils { } return "dev"; } + + public static 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); + } + } } diff --git a/jadx-core/src/test/java/jadx/tests/TypeMergeTest.java b/jadx-core/src/test/java/jadx/tests/TypeMergeTest.java index 041cd4aac..7554e231d 100644 --- a/jadx-core/src/test/java/jadx/tests/TypeMergeTest.java +++ b/jadx-core/src/test/java/jadx/tests/TypeMergeTest.java @@ -1,9 +1,13 @@ package jadx.tests; +import jadx.core.clsp.ClspGraph; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.PrimitiveType; +import jadx.core.utils.exceptions.DecodeException; import junit.framework.TestCase; +import java.io.IOException; + import static jadx.core.dex.instructions.args.ArgType.BOOLEAN; import static jadx.core.dex.instructions.args.ArgType.CHAR; import static jadx.core.dex.instructions.args.ArgType.INT; @@ -12,12 +16,21 @@ import static jadx.core.dex.instructions.args.ArgType.NARROW; import static jadx.core.dex.instructions.args.ArgType.OBJECT; import static jadx.core.dex.instructions.args.ArgType.UNKNOWN; import static jadx.core.dex.instructions.args.ArgType.UNKNOWN_OBJECT; +import static jadx.core.dex.instructions.args.ArgType.genericType; import static jadx.core.dex.instructions.args.ArgType.object; import static jadx.core.dex.instructions.args.ArgType.unknown; public class TypeMergeTest extends TestCase { - public void testMerge() { + private void initClsp() throws IOException, DecodeException { + ClspGraph clsp = new ClspGraph(); + clsp.load(); + ArgType.setClsp(clsp); + } + + public void testMerge() throws IOException, DecodeException { + initClsp(); + first(INT, INT); first(BOOLEAN, INT); reject(INT, LONG); @@ -27,21 +40,33 @@ public class TypeMergeTest extends TestCase { first(INT, NARROW); first(CHAR, INT); - merge(unknown(PrimitiveType.INT, PrimitiveType.BOOLEAN, PrimitiveType.FLOAT), + check(unknown(PrimitiveType.INT, PrimitiveType.BOOLEAN, PrimitiveType.FLOAT), unknown(PrimitiveType.INT, PrimitiveType.BOOLEAN), unknown(PrimitiveType.INT, PrimitiveType.BOOLEAN)); - merge(unknown(PrimitiveType.INT, PrimitiveType.FLOAT), + check(unknown(PrimitiveType.INT, PrimitiveType.FLOAT), unknown(PrimitiveType.INT, PrimitiveType.BOOLEAN), INT); - merge(unknown(PrimitiveType.INT, PrimitiveType.OBJECT), + check(unknown(PrimitiveType.INT, PrimitiveType.OBJECT), unknown(PrimitiveType.OBJECT, PrimitiveType.ARRAY), unknown(PrimitiveType.OBJECT)); - first(object("Lsomeobj;"), object("Lsomeobj;")); - merge(object("Lsomeobj;"), object("Lotherobj;"), null); - first(object("Lsomeobj;"), OBJECT); + ArgType objExc = object("java.lang.Exception"); + ArgType objThr = object("java.lang.Throwable"); + ArgType objIO = object("java.io.IOException"); + ArgType objArr = object("java.lang.ArrayIndexOutOfBoundsException"); + ArgType objList = object("java.util.List"); + + first(objExc, objExc); + check(objExc, objList, OBJECT); + first(objExc, OBJECT); + + check(objExc, objThr, objThr); + check(objIO, objArr, objExc); + + ArgType generic = genericType("T"); + first(generic, objExc); } private void first(ArgType t1, ArgType t2) { diff --git a/jadx-gui/build.gradle b/jadx-gui/build.gradle index 922723e43..a41e8d166 100644 --- a/jadx-gui/build.gradle +++ b/jadx-gui/build.gradle @@ -6,6 +6,7 @@ dependencies { compile(project(":jadx-core")) compile(project(":jadx-cli")) compile 'com.fifesoft:rsyntaxtextarea:2.0.7' + compile 'ch.qos.logback:logback-classic:1.0.13' } startScripts { diff --git a/jadx-gui/src/main/java/jadx/gui/utils/Utils.java b/jadx-gui/src/main/java/jadx/gui/utils/Utils.java index f31cd7399..7ea06b921 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/Utils.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/Utils.java @@ -37,6 +37,9 @@ public class Utils { } public static String typeStr(ArgType type) { + if (type == null) { + return "null"; + } if (type.isObject()) { String cls = type.getObject(); int dot = cls.lastIndexOf('.'); diff --git a/jadx-samples/src/main/java/jadx/samples/TestTryCatch.java b/jadx-samples/src/main/java/jadx/samples/TestTryCatch.java index a556181d4..e3ef44758 100644 --- a/jadx-samples/src/main/java/jadx/samples/TestTryCatch.java +++ b/jadx-samples/src/main/java/jadx/samples/TestTryCatch.java @@ -168,6 +168,20 @@ public class TestTryCatch extends AbstractTest { return b; } + public int catchInLoop(int i, int j) { + while (true) { + try { + while (i < j) + i = j++ / i; + } catch (RuntimeException e) { + i = 10; + continue; + } + break; + } + return j; + } + @Override public boolean testRun() throws Exception { Object obj = new Object(); @@ -180,8 +194,8 @@ public class TestTryCatch extends AbstractTest { assertTrue(test6(obj)); assertTrue(test7()); - assertTrue(testSynchronize(obj) == true); - assertTrue(testSynchronize("str") == false); + assertTrue(testSynchronize(obj)); + assertFalse(testSynchronize("str")); assertTrue(testSynchronize2("str")); assertTrue(testSynchronize3()); @@ -191,6 +205,10 @@ public class TestTryCatch extends AbstractTest { assertTrue(test8a("a")); assertTrue(test8a(null)); + + assertEquals(catchInLoop(1, 0), 0); + assertEquals(catchInLoop(0, 1), 2); + assertEquals(catchInLoop(788, 100), 100); return true; }