fix: make class public if some method going to be inlined (#729)

This commit is contained in:
Skylot
2020-05-24 20:30:39 +01:00
parent d720179deb
commit 15776c4ce3
25 changed files with 509 additions and 216 deletions
@@ -2,6 +2,7 @@ package jadx.core;
public class Consts {
public static final boolean DEBUG = false;
public static final boolean DEBUG_USAGE = false;
public static final String CLASS_OBJECT = "java.lang.Object";
public static final String CLASS_STRING = "java.lang.String";
+2 -2
View File
@@ -17,7 +17,6 @@ import jadx.core.dex.visitors.ClassModifier;
import jadx.core.dex.visitors.ConstInlineVisitor;
import jadx.core.dex.visitors.ConstructorVisitor;
import jadx.core.dex.visitors.DeboxingVisitor;
import jadx.core.dex.visitors.DependencyCollector;
import jadx.core.dex.visitors.DotGraphVisitor;
import jadx.core.dex.visitors.EnumVisitor;
import jadx.core.dex.visitors.ExtractFieldInit;
@@ -55,6 +54,7 @@ import jadx.core.dex.visitors.regions.variables.ProcessVariables;
import jadx.core.dex.visitors.shrink.CodeShrinkVisitor;
import jadx.core.dex.visitors.ssa.SSATransform;
import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor;
import jadx.core.dex.visitors.usage.UsageInfoVisitor;
public class Jadx {
private static final Logger LOG = LoggerFactory.getLogger(Jadx.class);
@@ -79,7 +79,7 @@ public class Jadx {
public static List<IDexTreeVisitor> getPreDecompilePassesList() {
List<IDexTreeVisitor> passes = new ArrayList<>();
passes.add(new RenameVisitor());
passes.add(new DependencyCollector());
passes.add(new UsageInfoVisitor());
return passes;
}
@@ -9,6 +9,7 @@ import jadx.core.dex.visitors.DepthTraversal;
import jadx.core.dex.visitors.IDexTreeVisitor;
import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.dex.nodes.ProcessState.GENERATED;
import static jadx.core.dex.nodes.ProcessState.LOADED;
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
import static jadx.core.dex.nodes.ProcessState.PROCESS_COMPLETE;
@@ -53,11 +54,16 @@ public final class ProcessClass {
if (topParentClass != cls) {
return generateCode(topParentClass);
}
if (cls.getState() == GENERATED) {
// allow to run code generation again
cls.setState(NOT_LOADED);
}
try {
cls.getDependencies().forEach(ProcessClass::process);
process(cls);
ICodeInfo code = CodeGen.generate(cls);
cls.setState(GENERATED);
cls.unload();
return code;
} catch (Throwable e) {
@@ -18,6 +18,7 @@ import jadx.api.JadxArgs;
import jadx.api.plugins.input.data.AccessFlags;
import jadx.api.plugins.input.data.annotations.EncodedType;
import jadx.api.plugins.input.data.annotations.EncodedValue;
import jadx.core.Consts;
import jadx.core.dex.attributes.AFlag;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.AttrNode;
@@ -114,6 +115,9 @@ public class ClassGen {
if (cls.contains(AFlag.DONT_GENERATE)) {
return;
}
if (Consts.DEBUG_USAGE) {
addClassUsageInfo(code, cls);
}
CodeGenUtils.addComments(code, cls);
insertDecompilationProblems(code, cls);
addClassDeclaration(code);
@@ -378,6 +382,9 @@ public class ClassGen {
if (f.contains(AFlag.DONT_GENERATE)) {
return;
}
if (Consts.DEBUG_USAGE) {
addFieldUsageInfo(code, f);
}
CodeGenUtils.addComments(code, f);
annotationGen.addForField(code, f);
@@ -574,11 +581,6 @@ public class ClassGen {
if (extClsInfo.getPackage().equals(useCls.getPackage()) && !extClsInfo.isInner()) {
return shortName;
}
// don't add import if class not public (must be accessed using inheritance)
ClassNode classNode = cls.root().resolveClass(extClsInfo);
if (classNode != null && !classNode.getAccessFlags().isPublic()) {
return shortName;
}
if (searchCollision(cls.root(), useCls, extClsInfo)) {
return fullName;
}
@@ -685,6 +687,40 @@ public class ClassGen {
}
}
private static void addClassUsageInfo(CodeWriter code, ClassNode cls) {
List<ClassNode> deps = cls.getDependencies();
code.startLine("// deps - ").add(Integer.toString(deps.size()));
for (ClassNode depCls : deps) {
code.startLine("// ").add(depCls.getFullName());
}
List<ClassNode> useIn = cls.getUseIn();
code.startLine("// use in - ").add(Integer.toString(useIn.size()));
for (ClassNode useCls : useIn) {
code.startLine("// ").add(useCls.getFullName());
}
List<MethodNode> useInMths = cls.getUseInMth();
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
for (MethodNode useMth : useInMths) {
code.startLine("// ").add(useMth.toString());
}
}
static void addMthUsageInfo(CodeWriter code, MethodNode mth) {
List<MethodNode> useInMths = mth.getUseIn();
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
for (MethodNode useMth : useInMths) {
code.startLine("// ").add(useMth.toString());
}
}
private static void addFieldUsageInfo(CodeWriter code, FieldNode fieldNode) {
List<MethodNode> useInMths = fieldNode.getUseIn();
code.startLine("// use in methods - ").add(Integer.toString(useInMths.size()));
for (MethodNode useMth : useInMths) {
code.startLine("// ").add(useMth.toString());
}
}
public ClassGen getParentGen() {
return parentGen == null ? this : parentGen;
}
@@ -80,6 +80,9 @@ public class MethodGen {
code.attachDefinition(mth);
return false;
}
if (Consts.DEBUG_USAGE) {
ClassGen.addMthUsageInfo(code, mth);
}
addOverrideAnnotation(code, mth);
annotationGen.addForMethod(code, mth);
@@ -42,7 +42,6 @@ import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
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, Comparable<ClassNode> {
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
@@ -69,8 +68,13 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
private ClassNode parentClass;
private volatile ProcessState state = ProcessState.NOT_LOADED;
/** Top level classes used in this class (only for top level classes, empty for inners) */
private List<ClassNode> dependencies = Collections.emptyList();
private List<ClassNode> usedIn = Collections.emptyList();
/** Classes which uses this class */
private List<ClassNode> useIn = Collections.emptyList();
/** Methods which uses this class (by instructions only, definition is excluded) */
private List<MethodNode> useInMth = Collections.emptyList();
// cache maps
private Map<MethodInfo, MethodNode> mthInfoMap = Collections.emptyMap();
@@ -305,7 +309,6 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
innerClasses.forEach(ClassNode::unload);
fields.forEach(FieldNode::unloadAttributes);
unloadAttributes();
setState(NOT_LOADED);
}
private void buildCache() {
@@ -585,12 +588,20 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
this.dependencies = dependencies;
}
public List<ClassNode> getUsedIn() {
return usedIn;
public List<ClassNode> getUseIn() {
return useIn;
}
public void setUsedIn(List<ClassNode> usedIn) {
this.usedIn = usedIn;
public void setUseIn(List<ClassNode> useIn) {
this.useIn = useIn;
}
public List<MethodNode> getUseInMth() {
return useInMth;
}
public void setUseInMth(List<MethodNode> useInMth) {
this.useInMth = useInMth;
}
@Override
@@ -1,6 +1,8 @@
package jadx.core.dex.nodes;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import jadx.api.plugins.input.data.IFieldData;
import jadx.core.dex.attributes.annotations.AnnotationsList;
@@ -18,6 +20,8 @@ public class FieldNode extends LineAttrNode implements ICodeNode {
private ArgType type;
private List<MethodNode> useIn = Collections.emptyList();
public static FieldNode build(ClassNode cls, IFieldData fieldData) {
FieldInfo fieldInfo = FieldInfo.fromData(cls.root(), fieldData);
FieldNode fieldNode = new FieldNode(cls, fieldInfo, fieldData.getAccessFlags());
@@ -70,6 +74,14 @@ public class FieldNode extends LineAttrNode implements ICodeNode {
return parentClass;
}
public List<MethodNode> getUseIn() {
return useIn;
}
public void setUseIn(List<MethodNode> useIn) {
this.useIn = useIn;
}
@Override
public String typeName() {
return "field";
@@ -40,7 +40,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
import static jadx.core.utils.Utils.lockList;
public class MethodNode extends NotificationAttrNode implements IMethodDetails, ILoadable, ICodeNode {
public class MethodNode extends NotificationAttrNode implements IMethodDetails, ILoadable, ICodeNode, Comparable<MethodNode> {
private static final Logger LOG = LoggerFactory.getLogger(MethodNode.class);
private final MethodInfo mthInfo;
@@ -72,6 +72,8 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
private List<LoopInfo> loops;
private Region region;
private List<MethodNode> useIn = Collections.emptyList();
public static MethodNode build(ClassNode classNode, IMethodData methodData) {
MethodNode methodNode = new MethodNode(classNode, methodData);
AnnotationsList.attach(methodNode, methodData.getAnnotations());
@@ -620,6 +622,14 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return codeReader;
}
public List<MethodNode> getUseIn() {
return useIn;
}
public void setUseIn(List<MethodNode> useIn) {
this.useIn = useIn;
}
@Override
public int hashCode() {
return mthInfo.hashCode();
@@ -637,6 +647,11 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
return mthInfo.equals(other.mthInfo);
}
@Override
public int compareTo(@NotNull MethodNode o) {
return mthInfo.compareTo(o.mthInfo);
}
@Override
public String toString() {
return parentClass + "." + mthInfo.getName()
@@ -4,13 +4,10 @@ public enum ProcessState {
NOT_LOADED,
LOADED,
PROCESS_STARTED,
PROCESS_COMPLETE;
public boolean isLoaded() {
return this != NOT_LOADED;
}
PROCESS_COMPLETE,
GENERATED;
public boolean isProcessed() {
return this == PROCESS_COMPLETE;
return this == PROCESS_COMPLETE || this == GENERATED;
}
}
@@ -1,186 +0,0 @@
package jadx.core.dex.visitors;
import java.util.ArrayList;
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.info.ClassInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
@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 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);
for (ClassNode inner : cls.getInnerClasses()) {
processClass(inner, root, depSet);
}
depSet.remove(cls);
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) {
addDep(root, depList, cls.getSuperClass());
for (ArgType iType : cls.getInterfaces()) {
addDep(root, depList, iType);
}
for (FieldNode fieldNode : cls.getFields()) {
addDep(root, depList, fieldNode.getType());
}
// TODO: process annotations and generics
for (MethodNode methodNode : cls.getMethods()) {
if (methodNode.isNoCode() || methodNode.contains(AType.JADX_ERROR)) {
continue;
}
processMethod(root, depList, methodNode);
}
}
private static void processMethod(RootNode root, Set<ClassNode> depList, MethodNode methodNode) {
addDep(root, depList, methodNode.getParentClass());
addDep(root, depList, methodNode.getReturnType());
for (ArgType arg : methodNode.getMethodInfo().getArgumentsTypes()) {
addDep(root, depList, arg);
}
try {
processInstructions(methodNode, depList);
} catch (Exception e) {
methodNode.getCodeReader().visitInstructions(insnData -> {
insnData.decode();
System.out.println(insnData);
});
methodNode.addError("Dependency scan failed", e);
}
}
private static void processInstructions(MethodNode mth, Set<ClassNode> deps) {
ICodeReader codeReader = mth.getCodeReader();
if (codeReader == null) {
return;
}
RootNode root = mth.root();
codeReader.visitInstructions(insnData -> {
try {
processInsn(root, insnData, deps);
} catch (Exception e) {
mth.addError("Dependency scan failed at insn: " + insnData, e);
}
});
}
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()) {
addDep(root, depList, ClassInfo.fromType(root, type));
ArgType[] genericTypes = type.getGenericTypes();
if (type.isGeneric() && genericTypes != null) {
for (ArgType argType : genericTypes) {
addDep(root, depList, argType);
}
}
} else if (type.isArray()) {
addDep(root, depList, type.getArrayRootElement());
}
}
}
private static void addDep(RootNode root, Set<ClassNode> depList, ClassInfo clsInfo) {
if (clsInfo != null) {
ClassNode node = root.resolveClass(clsInfo);
addDep(root, depList, node);
}
}
private static void addDep(RootNode root, Set<ClassNode> depList, ClassNode clsNode) {
if (clsNode != null) {
// add only top classes
depList.add(clsNode.getTopParentClass());
}
}
}
@@ -56,7 +56,7 @@ public class FixAccessModifiers extends AbstractVisitor {
}
private int fixClassVisibility(ClassNode cls) {
if (cls.getUsedIn().isEmpty()) {
if (cls.getUseIn().isEmpty()) {
return -1;
}
AccessInfo accessFlags = cls.getAccessFlags();
@@ -66,7 +66,7 @@ public class FixAccessModifiers extends AbstractVisitor {
}
// check if private inner class is used outside
ClassNode topParentClass = cls.getTopParentClass();
for (ClassNode useCls : cls.getUsedIn()) {
for (ClassNode useCls : cls.getUseIn()) {
if (useCls.getTopParentClass() != topParentClass) {
return AccessFlags.PUBLIC;
}
@@ -74,12 +74,21 @@ public class FixAccessModifiers extends AbstractVisitor {
}
if (accessFlags.isPackagePrivate()) {
String pkg = cls.getPackage();
for (ClassNode useCls : cls.getUsedIn()) {
for (ClassNode useCls : cls.getUseIn()) {
if (!useCls.getPackage().equals(pkg)) {
return AccessFlags.PUBLIC;
}
}
}
if (!accessFlags.isPublic()) {
// if class is used in inlinable method => make it public
for (MethodNode useMth : cls.getUseInMth()) {
boolean canInline = MethodInlineVisitor.canInline(useMth) || useMth.contains(AType.METHOD_INLINE);
if (canInline && !useMth.getUseIn().isEmpty()) {
return AccessFlags.PUBLIC;
}
}
}
return -1;
}
@@ -34,12 +34,7 @@ public class MethodInlineVisitor extends AbstractVisitor {
@Override
public void visit(MethodNode mth) throws JadxException {
if (mth.isNoCode() || mth.contains(AFlag.DONT_GENERATE)) {
return;
}
AccessInfo accessFlags = mth.getAccessFlags();
if (accessFlags.isSynthetic() && accessFlags.isStatic()
&& mth.getBasicBlocks().size() == 2) {
if (canInline(mth) && mth.getBasicBlocks().size() == 2) {
BlockNode returnBlock = mth.getBasicBlocks().get(1);
if (returnBlock.contains(AFlag.RETURN) || returnBlock.getInstructions().isEmpty()) {
BlockNode firstBlock = mth.getBasicBlocks().get(0);
@@ -48,6 +43,14 @@ public class MethodInlineVisitor extends AbstractVisitor {
}
}
public static boolean canInline(MethodNode mth) {
if (mth.isNoCode() || mth.contains(AFlag.DONT_GENERATE)) {
return false;
}
AccessInfo accessFlags = mth.getAccessFlags();
return accessFlags.isSynthetic() && accessFlags.isStatic();
}
private static void inlineMth(MethodNode mth, BlockNode firstBlock, BlockNode returnBlock) {
List<InsnNode> insnList = firstBlock.getInstructions();
if (insnList.isEmpty()) {
@@ -0,0 +1,101 @@
package jadx.core.dex.visitors.usage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
public class UsageInfo {
private final RootNode root;
private final UseSet<ClassNode, ClassNode> clsDeps = new UseSet<>();
private final UseSet<ClassNode, ClassNode> clsUsage = new UseSet<>();
private final UseSet<ClassNode, MethodNode> clsUseInMth = new UseSet<>();
private final UseSet<FieldNode, MethodNode> fieldUsage = new UseSet<>();
private final UseSet<MethodNode, MethodNode> mthUsage = new UseSet<>();
public UsageInfo(RootNode root) {
this.root = root;
}
public void apply() {
clsDeps.visit((cls, deps) -> cls.setDependencies(sortedList(deps)));
clsUsage.visit((cls, deps) -> cls.setUseIn(sortedList(deps)));
clsUseInMth.visit((cls, methods) -> cls.setUseInMth(sortedList(methods)));
fieldUsage.visit((field, methods) -> field.setUseIn(sortedList(methods)));
mthUsage.visit((mth, methods) -> mth.setUseIn(sortedList(methods)));
}
public void clsUse(ClassNode cls, ArgType useType) {
processType(useType, depCls -> clsUse(cls, depCls));
}
public void clsUse(MethodNode mth, ArgType useType) {
processType(useType, depCls -> clsUse(mth, depCls));
}
public void clsUse(MethodNode mth, ClassNode useCls) {
ClassNode parentClass = mth.getParentClass();
clsUse(parentClass, useCls);
if (parentClass != useCls) {
// exclude class usage in self methods
clsUseInMth.add(useCls, mth);
}
}
public void clsUse(ClassNode cls, ClassNode depCls) {
ClassNode topParentClass = cls.getTopParentClass();
clsDeps.add(topParentClass, depCls.getTopParentClass());
clsUsage.add(depCls, cls);
clsUsage.add(depCls, topParentClass);
}
/**
* Add method usage: {@code useMth} occurrence found in {@code mth} code
*/
public void methodUse(MethodNode mth, MethodNode useMth) {
clsUse(mth, useMth.getParentClass());
mthUsage.add(useMth, mth);
}
public void fieldUse(MethodNode mth, FieldNode useFld) {
clsUse(mth, useFld.getParentClass());
fieldUsage.add(useFld, mth);
}
private void processType(ArgType type, Consumer<ClassNode> consumer) {
if (type == null) {
return;
}
if (type.isArray()) {
processType(type.getArrayRootElement(), consumer);
return;
}
if (type.isObject() && !type.isGenericType()) {
ClassNode clsNode = root.resolveClass(type);
if (clsNode != null) {
consumer.accept(clsNode);
}
ArgType[] genericTypes = type.getGenericTypes();
if (type.isGeneric() && genericTypes != null) {
for (ArgType argType : genericTypes) {
processType(argType, consumer);
}
}
}
}
private static <T extends Comparable<T>> List<T> sortedList(Set<T> deps) {
List<T> list = new ArrayList<>(deps);
Collections.sort(list);
return list;
}
}
@@ -0,0 +1,115 @@
package jadx.core.dex.visitors.usage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.input.data.ICodeReader;
import jadx.api.plugins.input.insns.InsnData;
import jadx.api.plugins.input.insns.Opcode;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.info.FieldInfo;
import jadx.core.dex.info.MethodInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.AbstractVisitor;
import jadx.core.dex.visitors.JadxVisitor;
import jadx.core.dex.visitors.RenameVisitor;
@JadxVisitor(
name = "UsageInfoVisitor",
desc = "Scan class and methods to collect usage info and class dependencies",
runAfter = {
RenameVisitor.class // sort by alias name
}
)
public class UsageInfoVisitor extends AbstractVisitor {
private static final Logger LOG = LoggerFactory.getLogger(UsageInfoVisitor.class);
@Override
public void init(RootNode root) {
long startTime = System.currentTimeMillis();
UsageInfo usageInfo = new UsageInfo(root);
for (ClassNode cls : root.getClasses()) {
processClass(cls, usageInfo);
}
usageInfo.apply();
LOG.debug("Dependency collection done in {}ms", System.currentTimeMillis() - startTime);
}
private static void processClass(ClassNode cls, UsageInfo usageInfo) {
usageInfo.clsUse(cls, cls.getSuperClass());
for (ArgType interfaceType : cls.getInterfaces()) {
usageInfo.clsUse(cls, interfaceType);
}
for (FieldNode fieldNode : cls.getFields()) {
usageInfo.clsUse(cls, fieldNode.getType());
}
// TODO: process annotations and generics
for (MethodNode methodNode : cls.getMethods()) {
if (methodNode.isNoCode() || methodNode.contains(AType.JADX_ERROR)) {
continue;
}
processMethod(methodNode, usageInfo);
}
}
private static void processMethod(MethodNode mth, UsageInfo usageInfo) {
ClassNode cls = mth.getParentClass();
usageInfo.clsUse(cls, mth.getReturnType());
for (ArgType argType : mth.getMethodInfo().getArgumentsTypes()) {
usageInfo.clsUse(cls, argType);
}
try {
processInstructions(mth, usageInfo);
} catch (Exception e) {
mth.addError("Dependency scan failed", e);
}
}
private static void processInstructions(MethodNode mth, UsageInfo usageInfo) {
ICodeReader codeReader = mth.getCodeReader();
if (codeReader == null) {
return;
}
RootNode root = mth.root();
codeReader.visitInstructions(insnData -> {
try {
processInsn(root, mth, insnData, usageInfo);
} catch (Exception e) {
mth.addError("Dependency scan failed at insn: " + insnData, e);
}
});
}
private static void processInsn(RootNode root, MethodNode mth, InsnData insnData, UsageInfo usageInfo) {
if (insnData.getOpcode() == Opcode.UNKNOWN) {
return;
}
switch (insnData.getIndexType()) {
case TYPE_REF:
insnData.decode();
ArgType usedType = ArgType.parse(insnData.getIndexAsType());
usageInfo.clsUse(mth, usedType);
break;
case FIELD_REF:
insnData.decode();
FieldNode fieldNode = root.resolveField(FieldInfo.fromData(root, insnData.getIndexAsField()));
if (fieldNode != null) {
usageInfo.fieldUse(mth, fieldNode);
}
break;
case METHOD_REF:
insnData.decode();
MethodNode methodNode = root.resolveMethod(MethodInfo.fromData(root, insnData.getIndexAsMethod()));
if (methodNode != null) {
usageInfo.methodUse(mth, methodNode);
}
break;
}
}
}
@@ -0,0 +1,30 @@
package jadx.core.dex.visitors.usage;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
public class UseSet<K, V> {
private final Map<K, Set<V>> useMap = new HashMap<>();
public void add(K obj, V use) {
if (obj == use) {
// self excluded
return;
}
Set<V> set = useMap.computeIfAbsent(obj, k -> new HashSet<>());
set.add(use);
}
public Set<V> get(K obj) {
return useMap.get(obj);
}
public void visit(BiConsumer<K, Set<V>> consumer) {
for (Map.Entry<K, Set<V>> entry : useMap.entrySet()) {
consumer.accept(entry.getKey(), entry.getValue());
}
}
}
@@ -107,6 +107,7 @@ public abstract class SmaliTest extends IntegrationTest {
try {
SmaliOptions options = new SmaliOptions();
options.outputDexFile = output.getAbsolutePath();
options.verboseErrors = true;
List<String> inputFileNames = inputFiles.stream().map(File::getAbsolutePath).collect(Collectors.toList());
Smali.assemble(options, inputFileNames);
} catch (Exception e) {
@@ -8,10 +8,12 @@ import jadx.core.dex.nodes.ClassNode;
public class JadxAssertions extends Assertions {
public static JadxClassNodeAssertions assertThat(ClassNode actual) {
Assertions.assertThat(actual).isNotNull();
return new JadxClassNodeAssertions(actual);
}
public static JadxCodeAssertions assertThat(ICodeInfo actual) {
Assertions.assertThat(actual).isNotNull();
return new JadxCodeAssertions(actual.getCodeStr());
}
}
@@ -0,0 +1,55 @@
package jadx.tests.integration.inline;
import java.util.List;
import org.junit.jupiter.api.Test;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.SmaliTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestMethodInline extends SmaliTest {
// @formatter:off
/*
package inline;
public class A {
public static void useMth() {
inline.other.B.bridgeMth(); // after inline 'inline.other.C.test()' is not accessible
}
}
-----------------------------------------------------------
package inline.other;
public class B {
public static bridge synthetic void bridgeMth() {
inline.other.C.test();
}
}
----------------------------------------------------------
package inline.other;
class C {
public static void test() {
}
}
*/
// @formatter:on
@Test
public void test() {
List<ClassNode> classes = loadFromSmaliFiles();
ClassNode aCls = searchCls(classes, "inline.A");
ClassNode bCls = searchCls(classes, "inline.other.B");
ClassNode cCls = searchCls(classes, "inline.other.C");
assertThat(bCls).code().doesNotContain("bridgeMth()");
assertThat(aCls).code().containsOne("C.test()");
assertThat(cCls).code().containsOne("public class C {");
// TODO: update dependencies?
// assertThat(aCls.getDependencies()).contains(cCls);
// assertThat(cCls.getUsedIn()).contains(aCls);
}
}
@@ -0,0 +1,31 @@
package jadx.tests.integration.others;
import java.util.List;
import org.junit.jupiter.api.Test;
import jadx.core.dex.nodes.ClassNode;
import jadx.tests.api.SmaliTest;
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
public class TestFixClassAccessModifiers extends SmaliTest {
// @formatter:off
/*
public Cls.InnerCls field;
public static class Cls {
private static class InnerCls {
}
}
*/
// @formatter:on
@Test
public void test() {
List<ClassNode> classes = loadFromSmaliFiles();
assertThat(searchCls(classes, "others.Cls"))
.code()
.containsOne("public static class InnerCls {");
}
}
@@ -0,0 +1,10 @@
.class public Linline/A;
.super Ljava/lang/Object;
.method public static useMth()V
.locals 1
invoke-static {}, Linline/other/B;->bridgeMth()V
return-void
.end method
@@ -0,0 +1,10 @@
.class public Linline/other/B;
.super Ljava/lang/Object;
.method public static bridge synthetic bridgeMth()V
.locals 1
invoke-static {}, Linline/other/C;->test()V
return-void
.end method
@@ -0,0 +1,8 @@
.class Linline/other/C; # package-private
.super Ljava/lang/Object;
.method public static test()V
.locals 1
return-void
.end method
@@ -0,0 +1,8 @@
.class public Lothers/Cls;
.super Ljava/lang/Object;
.annotation system Ldalvik/annotation/MemberClasses;
value = {
Lothers/Cls$InnerCls;
}
.end annotation
@@ -0,0 +1,11 @@
.class private Lothers/Cls$InnerCls;
.super Ljava/lang/Object;
.annotation system Ldalvik/annotation/EnclosingClass;
value = Lothers/Cls;
.end annotation
.annotation system Ldalvik/annotation/InnerClass;
accessFlags = 0xA
name = "InnerCls"
.end annotation
@@ -0,0 +1,4 @@
.class public Lothers/TestCls;
.super Ljava/lang/Object;
.field public field:Lothers/Cls$InnerCls;