Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add generic method information to .jcst #564

Merged
merged 3 commits into from
Apr 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified jadx-core/clsp-data/android-5.1.jar
Binary file not shown.
295 changes: 262 additions & 33 deletions jadx-core/src/main/java/jadx/core/clsp/ClsSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
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.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand All @@ -21,15 +23,15 @@
import org.slf4j.LoggerFactory;

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.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.DecodeException;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
import jadx.core.utils.files.ZipSecurity;

import static jadx.core.utils.files.FileUtils.close;

/**
* Classes list for import into classpath graph
*/
Expand All @@ -41,12 +43,14 @@ public class ClsSet {
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 static final int VERSION = 2;

private static final String STRING_CHARSET = "US-ASCII";

private static final NClass[] EMPTY_NCLASS_ARRAY = new NClass[0];

private enum ARG_TYPE {WILDCARD, GENERIC, GENERIC_TYPE, OBJECT, ARRAY, PRIMITIVE}

private NClass[] classes;

public void load(RootNode root) {
Expand All @@ -56,11 +60,13 @@ public void load(RootNode root) {
for (ClassNode cls : list) {
String clsRawName = cls.getRawName();
if (cls.getAccessFlags().isPublic()) {
cls.load();
NClass nClass = new NClass(clsRawName, k);
if (names.put(clsRawName, nClass) != null) {
throw new JadxRuntimeException("Duplicate class: " + clsRawName);
}
k++;
nClass.setMethods(loadMethods(cls, nClass));
} else {
names.put(clsRawName, null);
}
Expand All @@ -80,6 +86,50 @@ public void load(RootNode root) {
}
}

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

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

boolean genericArg = false;
for (RegisterArg r: m.getArguments(false)) {
ArgType argType = r.getType();
if (argType.isGeneric()) {
args.add(argType);
genericArg = true;
} else if (argType.isGenericType()) {
args.add(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()]);
}

public static NClass[] makeParentsArray(ClassNode cls, Map<String, NClass> names) {
List<NClass> parents = new ArrayList<>(1 + cls.getInterfaces().size());
ArgType superClass = cls.getSuperClass();
Expand Down Expand Up @@ -110,43 +160,129 @@ private static NClass getCls(String fullName, Map<String, NClass> names) {
return cls;
}

void save(File output) throws IOException {
FileUtils.makeDirsForFile(output);
try (BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(output))) {
String outputName = output.getName();
if (outputName.endsWith(CLST_EXTENSION)) {
void save(Path path) throws IOException {
Files.createDirectories(path.getParent());
String outputName = path.getFileName().toString();
if (outputName.endsWith(CLST_EXTENSION)) {
try (BufferedOutputStream outputStream = new BufferedOutputStream(Files.newOutputStream(path))) {
save(outputStream);
} else if (outputName.endsWith(".jar")) {
ZipOutputStream out = new ZipOutputStream(outputStream);
try {
out.putNextEntry(new ZipEntry(CLST_PKG_PATH + '/' + CLST_FILENAME));
save(out);
} finally {
close(out);
}
} else if (outputName.endsWith(".jar")) {
Path temp = Files.createTempFile("jadx", ".zip");
Files.copy(path, temp, StandardCopyOption.REPLACE_EXISTING);

try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(path));
ZipInputStream in = new ZipInputStream(Files.newInputStream(temp))) {
String clst = CLST_PKG_PATH + '/' + CLST_FILENAME;
out.putNextEntry(new ZipEntry(clst));
save(out);
ZipEntry entry = in.getNextEntry();
while (entry != null) {
if (!entry.getName().equals(clst)) {
out.putNextEntry(new ZipEntry(entry.getName()));
FileUtils.copyStream(in, out);
}
entry = in.getNextEntry();
}
} else {
throw new JadxRuntimeException("Unknown file format: " + outputName);
}
Files.delete(temp);

} else {
throw new JadxRuntimeException("Unknown file format: " + outputName);
}
}

public void save(OutputStream output) throws IOException {
try (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());
DataOutputStream out = new DataOutputStream(output);
out.writeBytes(JADX_CLS_SET_HEADER);
out.writeByte(VERSION);

LOG.info("Classes count: {}", classes.length);
Map<String, NClass> names = new HashMap<>(classes.length);
out.writeInt(classes.length);
for (NClass cls : classes) {
writeString(out, cls.getName());
names.put(cls.getName(), cls);
}
for (NClass cls : classes) {
NClass[] parents = cls.getParents();
out.writeByte(parents.length);
for (NClass parent : parents) {
out.writeInt(parent.getId());
}
NMethod[] methods = cls.getMethods();
out.writeByte(methods.length);
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++;
}
}

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);
}
}

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

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

private static void writeArgType(DataOutputStream out, ArgType argType, Map<String, NClass> names) throws IOException {
if (argType.getWildcardType() != null) {
out.writeByte(ARG_TYPE.WILDCARD.ordinal());
int bounds = argType.getWildcardBounds();
out.writeByte(bounds);
if (bounds != 0) {
writeArgType(out, argType.getWildcardType(), names);
}
} else if (argType.isGeneric()) {
out.writeByte(ARG_TYPE.GENERIC.ordinal());
out.writeInt(names.get(argType.getObject()).getId());
ArgType[] types = argType.getGenericTypes();
if (types == null) {
out.writeByte(0);
} else {
out.writeByte(types.length);
for (ArgType type : types) {
writeArgType(out, type, names);
}
}
} else if (argType.isGenericType()) {
out.writeByte(ARG_TYPE.GENERIC_TYPE.ordinal());
writeString(out, argType.getObject());
} else if (argType.isObject()) {
out.writeByte(ARG_TYPE.OBJECT.ordinal());
out.writeInt(names.get(argType.getObject()).getId());
} else if (argType.isArray()) {
out.writeByte(ARG_TYPE.ARRAY.ordinal());
writeArgType(out, argType.getArrayElement(), names);
} else if (argType.isPrimitive()) {
out.writeByte(ARG_TYPE.PRIMITIVE.ordinal());
out.writeByte(argType.getPrimitiveType().getShortName().charAt(0));
} else {
throw new JadxRuntimeException("Cannot save type: " + argType);
}
}

Expand Down Expand Up @@ -203,18 +339,111 @@ public void load(InputStream input) throws IOException, DecodeException {
parents[j] = classes[in.readInt()];
}
classes[i].setParents(parents);

int mCount = in.readByte();
NMethod[] methods = new NMethod[mCount];
for (int j = 0; j < mCount; j++) {
methods[j] = readMethod(in);
}
classes[i].setMethods(methods);
}
}
}

private void writeString(DataOutputStream out, String name) throws IOException {
private NMethod readMethod(DataInputStream in) throws IOException {
String shortId = readLongString(in);
int argCount = in.readByte();
ArgType[] argTypes = null;
for (int i = 0; i < argCount; i++) {
int index = in.readByte();
ArgType argType = readArgType(in);
if (argTypes == null) {
argTypes = new ArgType[index + 1];
}
argTypes[index] = argType;
}
ArgType retType = in.readBoolean() ? readArgType(in) : null;
boolean varArgs = in.readBoolean();
return new NMethod(shortId, argTypes, retType, varArgs);
}

private ArgType readArgType(DataInputStream in) throws IOException {
int ordinal = in.readByte();
switch(ARG_TYPE.values()[ordinal]) {
case WILDCARD:
int bounds = in.readByte();
return bounds == 0
? ArgType.wildcard()
: ArgType.wildcard(readArgType(in), bounds);
case GENERIC:
String obj = classes[in.readInt()].getName();
int typeLength = in.readByte();
ArgType[] generics;
if (typeLength == 0) {
generics = null;
} else {
generics = new ArgType[typeLength];
for (int i = 0; i < typeLength; i++) {
generics[i] = readArgType(in);
}
}
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;
}
default:
throw new JadxRuntimeException("Unsupported Arg Type: " + ordinal);
}
}

private static void writeString(DataOutputStream out, String name) throws IOException {
byte[] bytes = name.getBytes(STRING_CHARSET);
out.writeByte(bytes.length);
out.write(bytes);
}

private static void writeLongString(DataOutputStream out, String name) throws IOException {
byte[] bytes = name.getBytes(STRING_CHARSET);
out.writeShort(bytes.length);
out.write(bytes);
}

private static String readString(DataInputStream in) throws IOException {
int len = in.readByte();
return readString(in, len);
}

private static String readLongString(DataInputStream in) throws IOException {
int len = in.readShort();
return readString(in, len);
}

private static String readString(DataInputStream in, int len) throws IOException {
byte[] bytes = new byte[len];
int count = in.read(bytes);
while (count != len) {
Expand Down
Loading