Skip to content

Commit

Permalink
fix: add generic types propagation (#695)
Browse files Browse the repository at this point in the history
  • Loading branch information
skylot committed Jul 6, 2019
1 parent 850df18 commit ed8c662
Show file tree
Hide file tree
Showing 34 changed files with 815 additions and 177 deletions.
5 changes: 2 additions & 3 deletions jadx-core/build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
ext.jadxClasspath = 'clsp-data/android-5.1.jar'

dependencies {
runtime files(jadxClasspath)
runtime files('clsp-data/android-29-clst.jar')
runtime files('clsp-data/android-29-res.jar')

compile files('lib/dx-1.16.jar') // TODO: dx don't support java version > 9 (53)

Expand Down
Binary file added jadx-core/clsp-data/android-29-clst.jar
Binary file not shown.
Binary file not shown.
215 changes: 124 additions & 91 deletions jadx-core/src/main/java/jadx/core/clsp/ClsSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -22,9 +23,11 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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.ClassNode;
import jadx.core.dex.nodes.GenericInfo;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
Expand Down Expand Up @@ -55,7 +58,16 @@ private enum TypeEnum {

private NClass[] classes;

public void load(RootNode root) {
public void loadFromClstFile() throws IOException, DecodeException {
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) {
if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
}
load(input);
}
}

public void loadFrom(RootNode root) {
List<ClassNode> list = root.getClasses(true);
Map<String, NClass> names = new HashMap<>(list.size());
int k = 0;
Expand All @@ -68,7 +80,8 @@ public void load(RootNode root) {
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
}
k++;
nClass.setMethods(loadMethods(cls, nClass));
nClass.setGenerics(cls.getGenerics());
nClass.setMethods(getMethodsDetails(cls));
} else {
names.put(clsRawName, null);
}
Expand All @@ -88,45 +101,43 @@ public void load(RootNode root) {
}
}

private NMethod[] loadMethods(ClassNode cls, NClass nClass) {
private List<NMethod> getMethodsDetails(ClassNode cls) {
List<NMethod> methods = new ArrayList<>();
for (MethodNode m : cls.getMethods()) {
if (!m.getAccessFlags().isPublic()
&& !m.getAccessFlags().isProtected()) {
continue;
AccessInfo accessFlags = m.getAccessFlags();
if (accessFlags.isPublic() || accessFlags.isProtected()) {
processMethodDetails(methods, m, accessFlags);
}
}
return methods;
}

List<ArgType> args = new ArrayList<>();

boolean genericArg = false;
for (RegisterArg r : m.getArguments(false)) {
ArgType argType = r.getType();
private void processMethodDetails(List<NMethod> methods, MethodNode mth, AccessInfo accessFlags) {
List<RegisterArg> args = mth.getArguments(false);
boolean genericArg = false;
ArgType[] genericArgs;
if (args.isEmpty()) {
genericArgs = null;
} else {
int argsCount = args.size();
genericArgs = new ArgType[argsCount];
for (int i = 0; i < argsCount; i++) {
RegisterArg arg = args.get(i);
ArgType argType = arg.getType();
if (argType.isGeneric() || argType.isGenericType()) {
args.add(argType);
genericArgs[i] = argType;
genericArg = true;
} else {
args.add(null);
}
}

ArgType retType = m.getReturnType();
if (!retType.isGeneric() && !retType.isGenericType()) {
retType = null;
}

boolean varArgs = m.getAccessFlags().isVarArgs();

if (genericArg || retType != null || varArgs) {
methods.add(new NMethod(
m.getMethodInfo().getShortId(),
args.isEmpty()
? new ArgType[0]
: args.toArray(new ArgType[args.size()]),
retType,
varArgs));
}
}
return methods.toArray(new NMethod[methods.size()]);
ArgType retType = mth.getReturnType();
if (!retType.isGeneric() && !retType.isGenericType()) {
retType = null;
}
boolean varArgs = accessFlags.isVarArgs();
if (genericArg || retType != null || varArgs) {
methods.add(new NMethod(mth.getMethodInfo().getShortId(), genericArgs, retType, varArgs));
}
}

public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
Expand Down Expand Up @@ -207,42 +218,58 @@ public void save(OutputStream output) throws IOException {
for (NClass parent : parents) {
out.writeInt(parent.getId());
}
NMethod[] methods = cls.getMethods();
out.writeByte(methods.length);
writeGenerics(out, cls, names);
List<NMethod> methods = cls.getMethodsList();
out.writeByte(methods.size());
for (NMethod method : methods) {
writeMethod(out, method, names);
}
}
}

private static void writeMethod(DataOutputStream out, NMethod method, Map<String, NClass> names) throws IOException {
int argCount = 0;
ArgType[] argTypes = method.getArgType();
for (ArgType arg : argTypes) {
if (arg != null) {
argCount++;
private static void writeGenerics(DataOutputStream out, NClass cls, Map<String, NClass> names) throws IOException {
List<GenericInfo> genericsList = cls.getGenerics();
out.writeByte(genericsList.size());
for (GenericInfo genericInfo : genericsList) {
writeArgType(out, genericInfo.getGenericType(), names);
List<ArgType> extendsList = genericInfo.getExtendsList();
out.writeByte(extendsList.size());
for (ArgType type : extendsList) {
writeArgType(out, type, names);
}

}
}

private static void writeMethod(DataOutputStream out, NMethod method, Map<String, NClass> names) throws IOException {
writeLongString(out, method.getShortId());
out.writeByte(argCount);

// last argument first
for (int i = argTypes.length - 1; i >= 0; i--) {
ArgType argType = argTypes[i];
if (argType != null) {
out.writeByte(i);
writeArgType(out, argType, names);

ArgType[] argTypes = method.getGenericArgs();
if (argTypes == null) {
out.writeByte(0);
} else {
int argCount = 0;
for (ArgType arg : argTypes) {
if (arg != null) {
argCount++;
}
}
out.writeByte(argCount);
// last argument first
for (int i = argTypes.length - 1; i >= 0; i--) {
ArgType argType = argTypes[i];
if (argType != null) {
out.writeByte(i);
writeArgType(out, argType, names);
}
}
}

if (method.getReturnType() != null) {
if (method.getReturnType() == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
writeArgType(out, method.getReturnType(), names);
} else {
out.writeBoolean(false);
}

out.writeBoolean(method.isVarArgs());
}

Expand Down Expand Up @@ -283,16 +310,7 @@ private static void writeArgType(DataOutputStream out, ArgType argType, Map<Stri
}
}

public void load() throws IOException, DecodeException {
try (InputStream input = getClass().getResourceAsStream(CLST_FILENAME)) {
if (input == null) {
throw new JadxRuntimeException("Can't load classpath file: " + CLST_FILENAME);
}
load(input);
}
}

public void load(File input) throws IOException, DecodeException {
private void load(File input) throws IOException, DecodeException {
String name = input.getName();
try (InputStream inputStream = new FileInputStream(input)) {
if (name.endsWith(CLST_EXTENSION)) {
Expand All @@ -313,7 +331,7 @@ public void load(File input) throws IOException, DecodeException {
}
}

public void load(InputStream input) throws IOException, DecodeException {
private void load(InputStream input) throws IOException, DecodeException {
try (DataInputStream in = new DataInputStream(input)) {
byte[] header = new byte[JADX_CLS_SET_HEADER.length()];
int readHeaderLength = in.read(header);
Expand All @@ -335,16 +353,44 @@ public void load(InputStream input) throws IOException, DecodeException {
for (int j = 0; j < pCount; j++) {
parents[j] = classes[in.readInt()];
}
classes[i].setParents(parents);
NClass nClass = classes[i];
nClass.setParents(parents);
nClass.setGenerics(readGenerics(in));
nClass.setMethods(readClsMethods(in));
}
}
}

int mCount = in.readByte();
NMethod[] methods = new NMethod[mCount];
for (int j = 0; j < mCount; j++) {
methods[j] = readMethod(in);
private List<GenericInfo> readGenerics(DataInputStream in) throws IOException {
int count = in.readByte();
if (count == 0) {
return Collections.emptyList();
}
List<GenericInfo> list = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
ArgType genericType = readArgType(in);
List<ArgType> extendsList;
byte extCount = in.readByte();
if (extCount == 0) {
extendsList = Collections.emptyList();
} else {
extendsList = new ArrayList<>(extCount);
for (int j = 0; j < extCount; j++) {
extendsList.add(readArgType(in));
}
classes[i].setMethods(methods);
}
list.add(new GenericInfo(genericType, extendsList));
}
return list;
}

private List<NMethod> readClsMethods(DataInputStream in) throws IOException {
int mCount = in.readByte();
List<NMethod> methods = new ArrayList<>(mCount);
for (int j = 0; j < mCount; j++) {
methods.add(readMethod(in));
}
return methods;
}

private NMethod readMethod(DataInputStream in) throws IOException {
Expand Down Expand Up @@ -372,6 +418,7 @@ private ArgType readArgType(DataInputStream in) throws IOException {
return bounds == 0
? ArgType.wildcard()
: ArgType.wildcard(readArgType(in), bounds);

case GENERIC:
String obj = classes[in.readInt()].getName();
int typeLength = in.readByte();
Expand All @@ -385,34 +432,20 @@ private ArgType readArgType(DataInputStream in) throws IOException {
}
}
return ArgType.generic(obj, generics);

case GENERIC_TYPE:
return ArgType.genericType(readString(in));

case OBJECT:
return ArgType.object(classes[in.readInt()].getName());

case ARRAY:
return ArgType.array(readArgType(in));

case PRIMITIVE:
int shortName = in.readByte();
switch (shortName) {
case 'Z':
return ArgType.BOOLEAN;
case 'C':
return ArgType.CHAR;
case 'B':
return ArgType.BYTE;
case 'S':
return ArgType.SHORT;
case 'I':
return ArgType.INT;
case 'F':
return ArgType.FLOAT;
case 'J':
return ArgType.LONG;
case 'D':
return ArgType.DOUBLE;
default:
return ArgType.VOID;
}
char shortName = (char) in.readByte();
return ArgType.parse(shortName);

default:
throw new JadxRuntimeException("Unsupported Arg Type: " + ordinal);
}
Expand Down
20 changes: 18 additions & 2 deletions jadx-core/src/main/java/jadx/core/clsp/ClspGraph.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@
import java.util.Set;
import java.util.WeakHashMap;

import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;

/**
* Classes hierarchy graph
* Classes hierarchy graph with methods additional info
*/
public class ClspGraph {
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
Expand All @@ -30,7 +33,7 @@ public class ClspGraph {

public void load() throws IOException, DecodeException {
ClsSet set = new ClsSet();
set.load();
set.loadFromClstFile();
addClasspath(set);
}

Expand Down Expand Up @@ -62,6 +65,19 @@ public boolean isClsKnown(String fullName) {
return nameMap.containsKey(fullName);
}

public NClass getClsDetails(ArgType type) {
return nameMap.get(type.getObject());
}

@Nullable
public NMethod getMethodDetails(MethodInfo methodInfo) {
NClass cls = nameMap.get(methodInfo.getDeclClass().getRawName());
if (cls == null) {
return null;
}
return cls.getMethodsMap().get(methodInfo.getShortId());
}

private NClass addClass(ClassNode cls) {
String rawName = cls.getRawName();
NClass nClass = new NClass(rawName, -1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public static void main(String[] args) throws IOException, DecodeException {
root.load(inputFiles);

ClsSet set = new ClsSet();
set.load(root);
set.loadFrom(root);
set.save(output);
LOG.info("Output: {}", output);
LOG.info("done");
Expand Down
Loading

0 comments on commit ed8c662

Please sign in to comment.