fix: make class public if some method going to be inlined (#729)
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
+31
@@ -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;
|
||||
Reference in New Issue
Block a user