forked from skylot/jadx
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
core: add classpath for precise class types resolving
- Loading branch information
Showing
20 changed files
with
618 additions
and
82 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,8 +17,9 @@ idea/ | |
.gradle/ | ||
gradle.properties | ||
|
||
*-tmp/ | ||
|
||
*.dex | ||
*.jar | ||
*.class | ||
*.dump | ||
*.log | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
Oops, something went wrong.