Skip to content

Commit

Permalink
fix: collect class usage and fix class access modifiers (#729)
Browse files Browse the repository at this point in the history
  • Loading branch information
skylot committed May 22, 2020
1 parent 0d69e0a commit d720179
Show file tree
Hide file tree
Showing 8 changed files with 193 additions and 61 deletions.
1 change: 1 addition & 0 deletions jadx-core/src/main/java/jadx/api/JadxDecompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ public void load() {
root.initClassPath();
root.loadResources(getResources());
root.initPasses();
root.runPreDecompileStage();
}

private void loadInputFiles() {
Expand Down
11 changes: 7 additions & 4 deletions jadx-core/src/main/java/jadx/core/Jadx.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ public static List<IDexTreeVisitor> getFallbackPassesList() {
return passes;
}

public static List<IDexTreeVisitor> getPreDecompilePassesList() {
List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new RenameVisitor());
passes.add(new DependencyCollector());
return passes;
}

public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
if (args.isFallbackMode()) {
return getFallbackPassesList();
Expand Down Expand Up @@ -146,10 +153,6 @@ public static List<IDexTreeVisitor> getPassesList(JadxArgs args) {
if (args.isCfgOutput()) {
passes.add(DotGraphVisitor.dumpRegions());
}

passes.add(new DependencyCollector());
passes.add(new RenameVisitor());

return passes;
}

Expand Down
2 changes: 1 addition & 1 deletion jadx-core/src/main/java/jadx/core/ProcessClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ public static ICodeInfo generateCode(ClassNode cls) {
return generateCode(topParentClass);
}
try {
process(cls);
cls.getDependencies().forEach(ProcessClass::process);
process(cls);

ICodeInfo code = CodeGen.generate(cls);
cls.unload();
Expand Down
22 changes: 20 additions & 2 deletions jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.util.Set;
import java.util.stream.Collectors;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -43,7 +44,7 @@
import static jadx.core.dex.nodes.ProcessState.LOADED;
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;

public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode {
public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> {
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);

private final RootNode root;
Expand All @@ -69,6 +70,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN

private volatile ProcessState state = ProcessState.NOT_LOADED;
private List<ClassNode> dependencies = Collections.emptyList();
private List<ClassNode> usedIn = Collections.emptyList();

// cache maps
private Map<MethodInfo, MethodNode> mthInfoMap = Collections.emptyMap();
Expand Down Expand Up @@ -478,6 +480,10 @@ public boolean isAnonymous() {
return contains(AFlag.ANONYMOUS_CLASS);
}

public boolean isInner() {
return parentClass != null;
}

@Nullable
public MethodNode getClassInitMth() {
return searchMethodByShortId("<clinit>()V");
Expand Down Expand Up @@ -579,6 +585,14 @@ public void setDependencies(List<ClassNode> dependencies) {
this.dependencies = dependencies;
}

public List<ClassNode> getUsedIn() {
return usedIn;
}

public void setUsedIn(List<ClassNode> usedIn) {
this.usedIn = usedIn;
}

@Override
public Path getInputPath() {
return inputPath;
Expand All @@ -601,9 +615,13 @@ public boolean equals(Object o) {
return false;
}

@Override
public int compareTo(@NotNull ClassNode o) {
return this.getFullName().compareTo(o.getFullName());
}

@Override
public String toString() {
return clsInfo.getFullName();
}

}
18 changes: 18 additions & 0 deletions jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.utils.MethodUtils;
import jadx.core.dex.nodes.utils.TypeUtils;
import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.dex.visitors.typeinference.TypeUpdate;
import jadx.core.utils.CacheStorage;
Expand Down Expand Up @@ -189,10 +190,27 @@ private void initInnerClasses() {
}
}

public void runPreDecompileStage() {
for (IDexTreeVisitor pass : Jadx.getPreDecompilePassesList()) {
try {
pass.init(this);
} catch (Exception e) {
LOG.error("Visitor init failed: {}", pass.getClass().getSimpleName(), e);
}
for (ClassNode cls : classes) {
DepthTraversal.visit(pass, cls);
}
}
}

public List<ClassNode> getClasses() {
return classes;
}

public List<ClassNode> getClassesWithoutInner() {
return getClasses(false);
}

public List<ClassNode> getClasses(boolean includeInner) {
if (includeInner) {
return classes;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,61 @@
package jadx.core.dex.visitors;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import jadx.api.plugins.input.data.ICodeReader;
import jadx.api.plugins.input.data.IFieldData;
import jadx.api.plugins.input.data.IMethodData;
import jadx.api.plugins.input.insns.InsnData;
import jadx.api.plugins.input.insns.Opcode;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.FieldInitAttr;
import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.instructions.BaseInvokeNode;
import jadx.core.dex.instructions.IndexInsnNode;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.instructions.args.InsnArg;
import jadx.core.dex.instructions.args.InsnWrapArg;
import jadx.core.dex.instructions.args.RegisterArg;
import jadx.core.dex.nodes.BlockNode;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.InsnNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.exceptions.JadxException;

@JadxVisitor(
name = "DependencyCollector",
desc = "Scan class and methods and collect dependant classes",
runAfter = {
RenameVisitor.class // sort by alias name
}
)
// TODO: store usage info for fields, methods and inner classes
public class DependencyCollector extends AbstractVisitor {

@Override
public boolean visit(ClassNode cls) throws JadxException {
public void init(RootNode root) {
List<ClassNode> clsList = root.getClassesWithoutInner();
for (ClassNode cls : clsList) {
collectClassDeps(cls);
}
buildUsageList(clsList);
}

private void buildUsageList(List<ClassNode> clsList) {
clsList.forEach(cls -> cls.setUsedIn(new ArrayList<>()));
for (ClassNode cls : clsList) {
for (ClassNode depCls : cls.getDependencies()) {
depCls.getUsedIn().add(cls);
}
}
for (ClassNode cls : clsList) {
List<ClassNode> usedIn = cls.getUsedIn();
if (usedIn.isEmpty()) {
cls.setUsedIn(Collections.emptyList());
} else {
Collections.sort(usedIn);
}
}
}

public void collectClassDeps(ClassNode cls) {
RootNode root = cls.root();
Set<ClassNode> depSet = new HashSet<>();
processClass(cls, root, depSet);
Expand All @@ -36,10 +64,13 @@ public boolean visit(ClassNode cls) throws JadxException {
}
depSet.remove(cls);

List<ClassNode> depList = new ArrayList<>(depSet);
depList.sort(Comparator.comparing(c -> c.getClassInfo().getFullName()));
cls.setDependencies(depList);
return false;
if (depSet.isEmpty()) {
cls.setDependencies(Collections.emptyList());
} else {
List<ClassNode> depList = new ArrayList<>(depSet);
Collections.sort(depList);
cls.setDependencies(depList);
}
}

private static void processClass(ClassNode cls, RootNode root, Set<ClassNode> depList) {
Expand All @@ -49,12 +80,6 @@ private static void processClass(ClassNode cls, RootNode root, Set<ClassNode> de
}
for (FieldNode fieldNode : cls.getFields()) {
addDep(root, depList, fieldNode.getType());

// process instructions from field init
FieldInitAttr fieldInitAttr = fieldNode.get(AType.FIELD_INIT);
if (fieldInitAttr != null && fieldInitAttr.getValueType() == FieldInitAttr.InitType.INSN) {
processInsn(root, depList, fieldInitAttr.getInsn());
}
}
// TODO: process annotations and generics
for (MethodNode methodNode : cls.getMethods()) {
Expand All @@ -71,43 +96,64 @@ private static void processMethod(RootNode root, Set<ClassNode> depList, MethodN
for (ArgType arg : methodNode.getMethodInfo().getArgumentsTypes()) {
addDep(root, depList, arg);
}
for (BlockNode block : methodNode.getBasicBlocks()) {
for (InsnNode insnNode : block.getInstructions()) {
processInsn(root, depList, insnNode);
}
try {
processInstructions(methodNode, depList);
} catch (Exception e) {
methodNode.getCodeReader().visitInstructions(insnData -> {
insnData.decode();
System.out.println(insnData);
});
methodNode.addError("Dependency scan failed", e);
}
}

// TODO: add custom instructions processing
private static void processInsn(RootNode root, Set<ClassNode> depList, InsnNode insnNode) {
RegisterArg result = insnNode.getResult();
if (result != null) {
addDep(root, depList, result.getType());
private static void processInstructions(MethodNode mth, Set<ClassNode> deps) {
ICodeReader codeReader = mth.getCodeReader();
if (codeReader == null) {
return;
}
for (InsnArg arg : insnNode.getArguments()) {
if (arg.isInsnWrap()) {
processInsn(root, depList, ((InsnWrapArg) arg).getWrapInsn());
} else {
addDep(root, depList, arg.getType());
RootNode root = mth.root();
codeReader.visitInstructions(insnData -> {
try {
processInsn(root, insnData, deps);
} catch (Exception e) {
mth.addError("Dependency scan failed at insn: " + insnData, e);
}
}
processCustomInsn(root, depList, insnNode);
});
}

private static void processCustomInsn(RootNode root, Set<ClassNode> depList, InsnNode insn) {
if (insn instanceof IndexInsnNode) {
Object index = ((IndexInsnNode) insn).getIndex();
if (index instanceof FieldInfo) {
addDep(root, depList, ((FieldInfo) index).getDeclClass());
} else if (index instanceof ArgType) {
addDep(root, depList, (ArgType) index);
}
} else if (insn instanceof BaseInvokeNode) {
ClassInfo declClass = ((BaseInvokeNode) insn).getCallMth().getDeclClass();
addDep(root, depList, declClass);
private static void processInsn(RootNode root, InsnData insnData, Set<ClassNode> deps) {
if (insnData.getOpcode() == Opcode.UNKNOWN) {
return;
}
switch (insnData.getIndexType()) {
case TYPE_REF:
insnData.decode();
resolveType(root, deps, insnData.getIndexAsType());
break;
case FIELD_REF:
insnData.decode();
resolveField(root, deps, insnData.getIndexAsField());
break;
case METHOD_REF:
insnData.decode();
resolveMethod(root, deps, insnData.getIndexAsMethod());
break;
}
}

private static void resolveType(RootNode root, Set<ClassNode> deps, String type) {
addDep(root, deps, ArgType.parse(type));
}

private static void resolveMethod(RootNode root, Set<ClassNode> deps, IMethodData method) {
resolveType(root, deps, method.getParentClassType());
}

private static void resolveField(RootNode root, Set<ClassNode> deps, IFieldData field) {
resolveType(root, deps, field.getParentClassType());
}

private static void addDep(RootNode root, Set<ClassNode> depList, ArgType type) {
if (type != null) {
if (type.isObject() && !type.isGenericType()) {
Expand Down
Loading

0 comments on commit d720179

Please sign in to comment.