package com.googlecode.d2j.ai;
import com.googlecode.d2j.Field;
import com.googlecode.d2j.Method;
import com.googlecode.d2j.node.*;
import com.googlecode.d2j.node.insn.DexStmtNode;
import com.googlecode.d2j.node.insn.FieldStmtNode;
import com.googlecode.d2j.node.insn.MethodStmtNode;
import com.googlecode.d2j.reader.BaseDexFileReader;
import com.googlecode.d2j.reader.MultiDexFileReader;
import com.googlecode.dex2jar.tools.BaseCmd;
import java.nio.file.*;
import java.util.*;
/**
* Analyze class dependencies in a DEX/APK file.
*
*
For each class, reports the other types it depends on: superclass,
* implemented interfaces, field types, and (with --deep) types referenced by
* method bodies (invoked methods' owners, accessed fields' owners).
*/
public class DexClassDepsCmd extends BaseCmd {
@Opt(opt = "c", longOpt = "class", description = "Only analyze classes matching this pattern (substring)")
String classFilter;
@Opt(opt = "d", longOpt = "deep", hasArg = false, description = "Include dependencies from method bodies (invokes, field access)")
boolean deep;
@Opt(opt = "i", longOpt = "internal-only", hasArg = false, description = "Only show dependencies on classes defined in this DEX")
boolean internalOnly;
@Opt(opt = "o", longOpt = "output", description = "Output file path (default: stdout)")
String output;
@Override
protected void doCommandLine() throws Exception {
if (remainingArgs == null || remainingArgs.length == 0) {
throw new HelpException("No input file specified");
}
Path input = Paths.get(remainingArgs[0]);
if (!Files.exists(input)) {
System.err.println("File not found: " + input);
return;
}
byte[] data = Files.readAllBytes(input);
BaseDexFileReader reader = MultiDexFileReader.open(data);
DexFileNode fileNode = new DexFileNode();
reader.accept(fileNode, deep ? 0 : com.googlecode.d2j.reader.DexFileReader.SKIP_CODE);
// Collect set of internal class names first
Set internal = new HashSet<>();
if (fileNode.clzs != null) {
for (DexClassNode cn : fileNode.clzs) {
internal.add(cn.className);
}
}
// className -> sorted set of dependency types
Map> deps = new LinkedHashMap<>();
if (fileNode.clzs != null) {
for (DexClassNode cn : fileNode.clzs) {
if (classFilter != null && !classFilter.isEmpty() && !cn.className.contains(classFilter)) {
continue;
}
TreeSet d = new TreeSet<>();
if (cn.superClass != null) addDep(d, cn.superClass, cn.className, internal);
if (cn.interfaceNames != null) {
for (String iface : cn.interfaceNames) addDep(d, iface, cn.className, internal);
}
if (cn.fields != null) {
for (DexFieldNode fn : cn.fields) {
if (fn.field != null) addDep(d, fn.field.getType(), cn.className, internal);
}
}
if (cn.methods != null) {
for (DexMethodNode mn : cn.methods) {
if (mn.method != null) {
String[] ps = mn.method.getParameterTypes();
if (ps != null) for (String p : ps) addDep(d, p, cn.className, internal);
addDep(d, mn.method.getReturnType(), cn.className, internal);
}
if (deep && mn.codeNode != null && mn.codeNode.stmts != null) {
for (DexStmtNode stmt : mn.codeNode.stmts) {
if (stmt instanceof MethodStmtNode) {
Method m = ((MethodStmtNode) stmt).method;
if (m != null) addDep(d, m.getOwner(), cn.className, internal);
} else if (stmt instanceof FieldStmtNode) {
Field f = ((FieldStmtNode) stmt).field;
if (f != null) addDep(d, f.getOwner(), cn.className, internal);
}
}
}
}
}
deps.put(cn.className, d);
}
}
StringBuilder sb = new StringBuilder();
sb.append("{\n");
sb.append(" \"class_count\": ").append(deps.size()).append(",\n");
sb.append(" \"deep\": ").append(deep).append(",\n");
sb.append(" \"dependencies\": {");
boolean first = true;
for (Map.Entry> e : deps.entrySet()) {
if (!first) sb.append(",");
first = false;
sb.append("\n ").append(jsonStr(e.getKey())).append(": [");
boolean f2 = true;
for (String dep : e.getValue()) {
if (!f2) sb.append(", ");
f2 = false;
sb.append(jsonStr(dep));
}
sb.append("]");
}
if (!deps.isEmpty()) sb.append("\n ");
sb.append("}\n}");
emit(sb.toString());
}
/** Normalize a type descriptor to a class name and add it if it's a class dependency. */
private void addDep(TreeSet d, String type, String self, Set internal) {
if (type == null) return;
// Unwrap array dimensions
String t = type;
while (t.startsWith("[")) t = t.substring(1);
// Only object types (Lxxx;) are class dependencies; skip primitives
if (!t.startsWith("L") || !t.endsWith(";")) return;
if (t.equals(self)) return; // skip self-reference
if (internalOnly && !internal.contains(t)) return;
d.add(t);
}
private void emit(String result) throws Exception {
if (output != null && !output.isEmpty()) {
Files.write(Paths.get(output), result.getBytes("UTF-8"));
System.out.println("Written to " + output);
} else {
System.out.println(result);
}
}
private static String jsonStr(String s) {
return Json.str(s);
}
}