Skip to content

Commit

Permalink
core: add classpath for precise class types resolving
Browse files Browse the repository at this point in the history
  • Loading branch information
skylot committed Aug 1, 2013
1 parent d0f120c commit 6ddb71e
Show file tree
Hide file tree
Showing 20 changed files with 618 additions and 82 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ idea/
.gradle/
gradle.properties

*-tmp/

*.dex
*.jar
*.class
*.dump
*.log
Expand Down
1 change: 0 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}

Expand Down
5 changes: 5 additions & 0 deletions jadx-core/build.gradle
Original file line number Diff line number Diff line change
@@ -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)
}

Binary file added jadx-core/clsp-data/android-4.3.jar
Binary file not shown.
12 changes: 6 additions & 6 deletions jadx-core/src/main/java/jadx/api/Decompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@ public void loadFile(File file) throws IOException, DecodeException {
}

public List<JavaClass> getClasses() {
List<JavaClass> classes = new ArrayList<JavaClass>(root.getClasses().size());
for (ClassNode classNode : root.getClasses()) {
List<ClassNode> classNodeList = root.getClasses(false);
List<JavaClass> classes = new ArrayList<JavaClass>(classNodeList.size());
for (ClassNode classNode : classNodeList) {
classes.add(new JavaClass(this, classNode));
}
return Collections.unmodifiableList(classes);
Expand Down Expand Up @@ -101,7 +102,7 @@ public ThreadPoolExecutor saveAll(File dir) {

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);
Expand Down Expand Up @@ -133,10 +134,9 @@ private void parseDex() throws DecodeException {
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) {
Expand Down
218 changes: 218 additions & 0 deletions jadx-core/src/main/java/jadx/core/clsp/ClsSet.java
Original file line number Diff line number Diff line change
@@ -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<ClassNode> list = root.getClasses(true);
Map<String, NClass> names = new HashMap<String, NClass>(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<String, NClass> names) {
List<NClass> parents = new ArrayList<NClass>(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<String, NClass> 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;
}
}
115 changes: 115 additions & 0 deletions jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java
Original file line number Diff line number Diff line change
@@ -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<String, NClass> nameMap;
private Map<String, Set<String>> ancestorCache = new WeakHashMap<String, Set<String>>();

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<String, NClass>(arr.length);
for (NClass cls : arr) {
nameMap.put(cls.getName(), cls);
}
} else {
throw new RuntimeException("Classpath already loaded");
}
}

public void addApp(List<ClassNode> 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<String> anc = getAncestors(clsName);
return anc.contains(implClsName);
}

public String getCommonAncestor(String clsName, String implClsName) {
if (isImplements(clsName, implClsName)) {
return implClsName;
}
Set<String> 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<String> 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<String> getAncestors(String clsName) {
Set<String> result = ancestorCache.get(clsName);
if (result == null) {
result = new HashSet<String>();
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<String> result) {
result.add(cls.getName());
for (NClass p : cls.getParents()) {
addAncestorsNames(p, result);
}
}
}
Loading

0 comments on commit 6ddb71e

Please sign in to comment.