refactor: use MethodInfo for unresolved methods usage, improve related code
This commit is contained in:
@@ -8,10 +8,10 @@ import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.api.plugins.input.data.IMethodRef;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.Utils;
|
||||
@@ -73,7 +73,7 @@ public final class JavaMethod implements JavaNode {
|
||||
return getDeclaringClass().getRootDecompiler().convertNodes(mth.getUsed());
|
||||
}
|
||||
|
||||
public List<IMethodRef> getUnresolvedUsed() {
|
||||
public List<MethodInfo> getUnresolvedUsed() {
|
||||
return mth.getUnresolvedUsed();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package jadx.api.usage;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.plugins.input.data.IMethodRef;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
@@ -21,7 +21,7 @@ public interface IUsageInfoVisitor {
|
||||
|
||||
void visitMethodsUses(MethodNode mth, List<MethodNode> methods);
|
||||
|
||||
void visitUnresolvedMethodsUsage(MethodNode mth, List<IMethodRef> methods);
|
||||
void visitUnresolvedMethodsUsage(MethodNode mth, List<MethodInfo> methods);
|
||||
|
||||
void visitIsSelfCall(MethodNode mth, boolean isSelfCall);
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package jadx.core.codegen;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -30,6 +32,10 @@ public class TypeGen {
|
||||
return stype.getShortName();
|
||||
}
|
||||
|
||||
public static List<String> signatures(List<ArgType> types) {
|
||||
return Utils.collectionMap(types, TypeGen::signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert literal arg to string (preferred method)
|
||||
*/
|
||||
|
||||
@@ -746,6 +746,14 @@ public class ClassNode extends NotificationAttrNode
|
||||
}
|
||||
}
|
||||
|
||||
public void getInnerClassesRecursive(Set<ClassNode> resultClassesSet) {
|
||||
for (ClassNode innerCls : innerClasses) {
|
||||
if (resultClassesSet.add(innerCls)) {
|
||||
innerCls.getInnerAndInlinedClassesRecursive(resultClassesSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addInnerClass(ClassNode cls) {
|
||||
if (innerClasses.isEmpty()) {
|
||||
innerClasses = new ArrayList<>(5);
|
||||
|
||||
@@ -21,7 +21,6 @@ import jadx.api.metadata.annotations.VarNode;
|
||||
import jadx.api.plugins.input.data.ICodeReader;
|
||||
import jadx.api.plugins.input.data.IDebugInfo;
|
||||
import jadx.api.plugins.input.data.IMethodData;
|
||||
import jadx.api.plugins.input.data.IMethodRef;
|
||||
import jadx.api.plugins.input.data.attributes.JadxAttrType;
|
||||
import jadx.api.plugins.input.data.attributes.types.ExceptionsAttr;
|
||||
import jadx.api.utils.CodeUtils;
|
||||
@@ -87,7 +86,7 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
||||
// Methods that use this method
|
||||
private List<MethodNode> useIn = Collections.emptyList();
|
||||
// Unresolved methods that use this method
|
||||
private List<IMethodRef> unresolvedUsed = Collections.emptyList();
|
||||
private List<MethodInfo> unresolvedUsed = Collections.emptyList();
|
||||
// Methods that this method uses
|
||||
private Set<MethodNode> methodsUsed = new HashSet<>();
|
||||
// True if this method contains a self call
|
||||
@@ -744,11 +743,11 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
||||
return methodsUsed;
|
||||
}
|
||||
|
||||
public List<IMethodRef> getUnresolvedUsed() {
|
||||
public List<MethodInfo> getUnresolvedUsed() {
|
||||
return unresolvedUsed;
|
||||
}
|
||||
|
||||
public void setUnresolvedUsed(List<IMethodRef> unresolvedUsed) {
|
||||
public void setUnresolvedUsed(List<MethodInfo> unresolvedUsed) {
|
||||
this.unresolvedUsed = unresolvedUsed;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,12 +9,12 @@ import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jadx.api.plugins.input.data.IMethodRef;
|
||||
import jadx.api.usage.IUsageInfoData;
|
||||
import jadx.api.usage.IUsageInfoVisitor;
|
||||
import jadx.core.clsp.ClspClass;
|
||||
import jadx.core.clsp.ClspClassSource;
|
||||
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;
|
||||
@@ -37,8 +37,8 @@ public class UsageInfo implements IUsageInfoData {
|
||||
private final UseSet<MethodNode, MethodNode> mthUsage = new UseSet<>();
|
||||
// MethodNodeA -> Set of MethodNodes that MethodNodeA calls
|
||||
private final UseSet<MethodNode, MethodNode> mthUses = new UseSet<>();
|
||||
// MethodNodeA -> Set of IMethodRefs for methods that MethodNodeA calls that cannot be resolved
|
||||
private final UseSet<MethodNode, IMethodRef> unresolvedMthUsage = new UseSet<>();
|
||||
// MethodNodeA -> Set of MethodInfos for methods that MethodNodeA calls that cannot be resolved
|
||||
private final UseSet<MethodNode, MethodInfo> unresolvedMthUsage = new UseSet<>();
|
||||
private final Map<MethodNode, Boolean> selfCalls = new HashMap<>();
|
||||
|
||||
public UsageInfo(RootNode root) {
|
||||
@@ -53,7 +53,7 @@ public class UsageInfo implements IUsageInfoData {
|
||||
fieldUsage.visit((field, methods) -> field.setUseIn(resolveMthList(sortedList(methods))));
|
||||
mthUsage.visit((mth, methods) -> mth.setUseIn(resolveMthList(sortedList(methods))));
|
||||
mthUses.visit((mth, methods) -> mth.setUsed(resolveMthList(sortedList(methods))));
|
||||
unresolvedMthUsage.visit((mth, unresolvedMethods) -> mth.setUnresolvedUsed(new ArrayList<>(unresolvedMethods)));
|
||||
unresolvedMthUsage.visit((mth, unresolvedMethods) -> mth.setUnresolvedUsed(sortedList(unresolvedMethods)));
|
||||
selfCalls.forEach(MethodNode::setCallsSelf);
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public class UsageInfo implements IUsageInfoData {
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
mth.setUseIn(resolveMthList(sortedList(mthUsage.getOrDefault(mth, Collections.emptySet()))));
|
||||
mth.setUsed(resolveMthList(sortedList(mthUses.getOrDefault(mth, Collections.emptySet()))));
|
||||
mth.setUnresolvedUsed(new ArrayList<>(unresolvedMthUsage.getOrDefault(mth, Collections.emptySet())));
|
||||
mth.setUnresolvedUsed(sortedList(unresolvedMthUsage.getOrDefault(mth, Collections.emptySet())));
|
||||
mth.setCallsSelf(selfCalls.getOrDefault(mth, false));
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,7 @@ public class UsageInfo implements IUsageInfoData {
|
||||
fieldUsage.visit((field, methods) -> visitor.visitFieldsUsage(field, resolveMthList(sortedList(methods))));
|
||||
mthUsage.visit((mth, methods) -> visitor.visitMethodsUsage(mth, resolveMthList(sortedList(methods))));
|
||||
mthUses.visit((mth, methods) -> visitor.visitMethodsUses(mth, resolveMthList(sortedList(methods))));
|
||||
unresolvedMthUsage.visit((mth, unresolvedMethods) -> visitor.visitUnresolvedMethodsUsage(mth, new ArrayList<>(unresolvedMethods)));
|
||||
unresolvedMthUsage.visit((mth, unresolvedMethods) -> visitor.visitUnresolvedMethodsUsage(mth, sortedList(unresolvedMethods)));
|
||||
for (Entry<MethodNode, Boolean> entry : selfCalls.entrySet()) {
|
||||
MethodNode mth = entry.getKey();
|
||||
Boolean selfCall = entry.getValue();
|
||||
@@ -155,7 +155,7 @@ public class UsageInfo implements IUsageInfoData {
|
||||
/**
|
||||
* Add method usage: {@code useMth} occurrence found in {@code mth} code
|
||||
*/
|
||||
public void unresolvedMethodUse(MethodNode mth, IMethodRef useMth) {
|
||||
public void unresolvedMethodUse(MethodNode mth, MethodInfo useMth) {
|
||||
unresolvedMthUsage.add(mth, useMth);
|
||||
}
|
||||
|
||||
|
||||
@@ -163,14 +163,12 @@ public class UsageInfoVisitor extends AbstractVisitor {
|
||||
} else {
|
||||
mthRef = insnData.getIndexAsMethod();
|
||||
}
|
||||
MethodNode methodNode = root.resolveMethod(MethodInfo.fromRef(root, mthRef));
|
||||
MethodInfo mthInfo = MethodInfo.fromRef(root, mthRef);
|
||||
MethodNode methodNode = root.resolveMethod(mthInfo);
|
||||
if (methodNode != null) {
|
||||
usageInfo.methodUse(mth, methodNode);
|
||||
} else {
|
||||
mthRef.load();
|
||||
if (mthRef.getName() != null || mthRef.getParentClassType() != null) {
|
||||
usageInfo.unresolvedMethodUse(mth, mthRef);
|
||||
}
|
||||
usageInfo.unresolvedMethodUse(mth, mthInfo);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -181,11 +179,14 @@ public class UsageInfoVisitor extends AbstractVisitor {
|
||||
IMethodHandle methodHandle = InsnDataUtils.getMethodHandleAt(callSite, 4);
|
||||
if (methodHandle != null) {
|
||||
IMethodRef mthRef = methodHandle.getMethodRef();
|
||||
MethodNode mthNode = root.resolveMethod(MethodInfo.fromRef(root, mthRef));
|
||||
if (mthNode != null) {
|
||||
usageInfo.methodUse(mth, mthNode);
|
||||
} else if (mthRef.getName() != null || mthRef.getParentClassType() != null) {
|
||||
usageInfo.unresolvedMethodUse(mth, mthRef);
|
||||
if (mthRef != null) {
|
||||
MethodInfo mthInfo = MethodInfo.fromRef(root, mthRef);
|
||||
MethodNode mthNode = root.resolveMethod(mthInfo);
|
||||
if (mthNode != null) {
|
||||
usageInfo.methodUse(mth, mthNode);
|
||||
} else {
|
||||
usageInfo.unresolvedMethodUse(mth, mthInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
@@ -7,19 +8,18 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.impl.SimpleCodeWriter;
|
||||
import jadx.api.plugins.input.data.IMethodRef;
|
||||
import jadx.core.codegen.MethodGen;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.IAttributeNode;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.IfNode;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
@@ -477,19 +477,13 @@ public class DotGraphUtils {
|
||||
return methodNode.getAlias();
|
||||
}
|
||||
|
||||
public static String unresolvedMethodFormatName(IMethodRef methodRef, boolean longName) {
|
||||
String name = methodRef.getName();
|
||||
public static String unresolvedMethodFormatName(MethodInfo mthInfo, boolean longName) {
|
||||
String name = mthInfo.getName();
|
||||
if (longName) {
|
||||
String className = methodRef.getParentClassType();
|
||||
className = Utils.cleanObjectName(className);
|
||||
|
||||
String returnName = methodRef.getReturnType();
|
||||
returnName = Utils.smaliNameToJavaName(returnName);
|
||||
|
||||
List<String> argTypes = methodRef.getArgTypes();
|
||||
argTypes = argTypes.stream().map(c -> Utils.smaliNameToJavaName(c)).collect(Collectors.toList());
|
||||
|
||||
return String.format("%s.%s(%s):%s", className, name, Utils.listToString(argTypes), returnName);
|
||||
String className = mthInfo.getDeclClass().getFullName();
|
||||
String returnName = mthInfo.getReturnType().toString();
|
||||
String argStr = Utils.listToString(mthInfo.getArgumentsTypes(), ArgType::toString);
|
||||
return String.format("%s.%s(%s):%s", className, name, argStr, returnName);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
@@ -508,4 +502,12 @@ public class DotGraphUtils {
|
||||
}
|
||||
return arg.toString();
|
||||
}
|
||||
|
||||
public static String formatColor(Color color) {
|
||||
return String.format("\"#%02x%02x%02x\"", color.getRed(), color.getGreen(), color.getBlue());
|
||||
}
|
||||
|
||||
public static String toDotNodeName(String fullName) {
|
||||
return fullName.replace("<", "\\<").replace(">", "\\>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,9 +33,7 @@ public class ListUtils {
|
||||
}
|
||||
|
||||
public static <T> List<T> mutableListOf(T... objs) {
|
||||
List<T> list = new ArrayList<>();
|
||||
Collections.addAll(list, objs);
|
||||
return list;
|
||||
return new ArrayList<>(Arrays.asList(objs));
|
||||
}
|
||||
|
||||
public static <T> boolean isSingleElement(@Nullable List<T> list, T obj) {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -368,6 +371,24 @@ public class StringUtils {
|
||||
return WORD_SEPARATORS.indexOf(chr) != -1;
|
||||
}
|
||||
|
||||
public static List<String> splitByFixedString(String content, String splitStr) {
|
||||
if (isEmpty(content)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<String> parts = new ArrayList<>();
|
||||
int splitLen = splitStr.length();
|
||||
int pos = 0;
|
||||
while (true) {
|
||||
int split = content.indexOf(splitStr, pos);
|
||||
if (split == -1) {
|
||||
parts.add(content.substring(pos));
|
||||
return parts;
|
||||
}
|
||||
parts.add(content.substring(pos, split));
|
||||
pos = split + splitLen;
|
||||
}
|
||||
}
|
||||
|
||||
public static String removeSuffix(String str, String suffix) {
|
||||
if (str.endsWith(suffix)) {
|
||||
return str.substring(0, str.length() - suffix.length());
|
||||
|
||||
@@ -3,6 +3,8 @@ package jadx.gui.cache.usage;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.api.plugins.input.data.IMethodRef;
|
||||
import jadx.core.codegen.TypeGen;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
|
||||
public class CachedMethodRef implements IMethodRef {
|
||||
|
||||
@@ -18,6 +20,13 @@ public class CachedMethodRef implements IMethodRef {
|
||||
this.argTypes = argTypes;
|
||||
}
|
||||
|
||||
public CachedMethodRef(MethodInfo mthInfo) {
|
||||
this(TypeGen.signature(mthInfo.getDeclClass().getType()),
|
||||
mthInfo.getName(),
|
||||
TypeGen.signature(mthInfo.getReturnType()),
|
||||
TypeGen.signatures(mthInfo.getArgumentsTypes()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParentClassType() {
|
||||
return parentClassType;
|
||||
@@ -56,12 +65,10 @@ public class CachedMethodRef implements IMethodRef {
|
||||
|
||||
@Override
|
||||
public int getUniqId() {
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getUniqId'");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
throw new UnsupportedOperationException("Unimplemented method 'load'");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.List;
|
||||
|
||||
import jadx.api.plugins.input.data.IMethodRef;
|
||||
import jadx.api.usage.IUsageInfoVisitor;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
@@ -47,8 +48,8 @@ final class CollectUsageData implements IUsageInfoVisitor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitUnresolvedMethodsUsage(MethodNode mth, List<IMethodRef> methods) {
|
||||
data.getMethodData(mth).setUnresolvedUsage(methods);
|
||||
public void visitUnresolvedMethodsUsage(MethodNode mth, List<MethodInfo> methods) {
|
||||
data.getMethodData(mth).setUnresolvedUsage(mthInfoRef(methods));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -68,4 +69,8 @@ final class CollectUsageData implements IUsageInfoVisitor {
|
||||
private List<MthRef> mthNodesRef(List<MethodNode> methods) {
|
||||
return Utils.collectionMap(methods, m -> data.getMethodData(m).getMthRef());
|
||||
}
|
||||
|
||||
private List<IMethodRef> mthInfoRef(List<MethodInfo> methods) {
|
||||
return Utils.collectionMap(methods, CachedMethodRef::new);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.plugins.input.data.IMethodRef;
|
||||
import jadx.api.usage.IUsageInfoData;
|
||||
import jadx.api.usage.IUsageInfoVisitor;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
@@ -60,7 +62,7 @@ class UsageData implements IUsageInfoData {
|
||||
if (mthUsageData != null) {
|
||||
mth.setUseIn(resolveMthList(mthUsageData.getUsage()));
|
||||
mth.setUsed(resolveMthList(mthUsageData.getUses()));
|
||||
mth.setUnresolvedUsed(mthUsageData.getUnresolvedUsage());
|
||||
mth.setUnresolvedUsed(resolveMthInfoList(mthUsageData.getUnresolvedUsage()));
|
||||
mth.setCallsSelf(mthUsageData.callsSelf());
|
||||
}
|
||||
}
|
||||
@@ -85,4 +87,8 @@ class UsageData implements IUsageInfoData {
|
||||
private List<MethodNode> resolveMthList(List<MthRef> mthRefList) {
|
||||
return Utils.collectionMap(mthRefList, m -> root.resolveDirectMethod(m.getCls(), m.getShortId()));
|
||||
}
|
||||
|
||||
private List<MethodInfo> resolveMthInfoList(List<IMethodRef> mthRefList) {
|
||||
return Utils.collectionMap(mthRefList, m -> MethodInfo.fromRef(root, m));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.plugins.input.data.IMethodRef;
|
||||
import jadx.api.usage.IUsageInfoData;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
@@ -38,10 +39,10 @@ import static java.nio.file.StandardOpenOption.WRITE;
|
||||
public class UsageFileAdapter extends DataAdapterHelper {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UsageFileAdapter.class);
|
||||
|
||||
private static final int USAGE_DATA_VERSION = 2;
|
||||
private static final int USAGE_DATA_VERSION = 3;
|
||||
private static final byte[] JADX_USAGE_HEADER = "jadx.usage".getBytes(StandardCharsets.US_ASCII);
|
||||
|
||||
public static synchronized @Nullable RawUsageData load(Path usageFile, List<File> inputs) {
|
||||
public static synchronized @Nullable RawUsageData load(RootNode root, Path usageFile, List<File> inputs) {
|
||||
if (!Files.isRegularFile(usageFile)) {
|
||||
return null;
|
||||
}
|
||||
@@ -61,7 +62,7 @@ public class UsageFileAdapter extends DataAdapterHelper {
|
||||
FileUtils.deleteFileIfExists(usageFile);
|
||||
return null;
|
||||
}
|
||||
RawUsageData data = readData(in);
|
||||
RawUsageData data = readData(root, in);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Loaded usage data from disk cache, classes count: {}, time: {}ms, file: {}",
|
||||
data.getClsMap().size(), System.currentTimeMillis() - start, usageFile);
|
||||
@@ -103,7 +104,7 @@ public class UsageFileAdapter extends DataAdapterHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private static RawUsageData readData(DataInputStream in) throws IOException {
|
||||
private static RawUsageData readData(RootNode root, DataInputStream in) throws IOException {
|
||||
RawUsageData data = new RawUsageData();
|
||||
int clsCount = readUVInt(in);
|
||||
int clsWithoutDataCount = readUVInt(in);
|
||||
@@ -120,6 +121,11 @@ public class UsageFileAdapter extends DataAdapterHelper {
|
||||
for (int i = 0; i < clsWithoutDataCount; i++) {
|
||||
clsNames[c++] = in.readUTF();
|
||||
}
|
||||
int uClsCount = readUVInt(in);
|
||||
String[] uClsNames = new String[uClsCount];
|
||||
for (int i = 0; i < uClsCount; i++) {
|
||||
uClsNames[i] = in.readUTF();
|
||||
}
|
||||
|
||||
// Method information
|
||||
int mthCount = readUVInt(in);
|
||||
@@ -137,16 +143,15 @@ public class UsageFileAdapter extends DataAdapterHelper {
|
||||
int uMthCount = readUVInt(in);
|
||||
IMethodRef[] unresolvedMethods = new IMethodRef[uMthCount];
|
||||
for (int i = 0; i < uMthCount; i++) {
|
||||
int clsId = readUVInt(in);
|
||||
String name = in.readUTF();
|
||||
String parentClassType = in.readUTF();
|
||||
String returnType = in.readUTF();
|
||||
int argCount = in.readInt();
|
||||
String[] args = new String[argCount];
|
||||
int argCount = readUVInt(in);
|
||||
List<String> args = new ArrayList<>(argCount);
|
||||
for (int j = 0; j < argCount; j++) {
|
||||
args[j] = in.readUTF();
|
||||
args.add(in.readUTF());
|
||||
}
|
||||
IMethodRef iMethodRef = new CachedMethodRef(parentClassType, name, returnType, Arrays.asList(args));
|
||||
unresolvedMethods[i] = iMethodRef;
|
||||
unresolvedMethods[i] = new CachedMethodRef(uClsNames[clsId], name, returnType, args);
|
||||
}
|
||||
|
||||
// Usage data
|
||||
@@ -181,10 +186,29 @@ public class UsageFileAdapter extends DataAdapterHelper {
|
||||
Map<MthRef, Integer> mthMap = new HashMap<>();
|
||||
Map<IMethodRef, Integer> uMthMap = new HashMap<>();
|
||||
Map<String, ClsUsageData> clsDataMap = usageData.getClsMap();
|
||||
|
||||
Map<String, Integer> uClsMap = new HashMap<>();
|
||||
List<IMethodRef> unresolvedMethods = clsDataMap.values().stream()
|
||||
.flatMap(classUsageData -> classUsageData.getMthUsage().values().stream())
|
||||
.flatMap(methodUsageData -> {
|
||||
List<IMethodRef> unresolvedUsageList = methodUsageData.getUnresolvedUsage();
|
||||
return unresolvedUsageList == null ? null : unresolvedUsageList.stream();
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<String> classes = new ArrayList<>(clsDataMap.keySet());
|
||||
Collections.sort(classes);
|
||||
List<String> classesWithoutData = usageData.getClassesWithoutData();
|
||||
|
||||
// pool for classes from unresolved methods
|
||||
Set<String> uClsNames = new HashSet<>();
|
||||
for (IMethodRef uMthRef : unresolvedMethods) {
|
||||
uClsNames.add(uMthRef.getParentClassType());
|
||||
}
|
||||
List<String> uClsList = new ArrayList<>(uClsNames);
|
||||
Collections.sort(uClsList);
|
||||
|
||||
// Class information
|
||||
writeUVInt(out, classes.size());
|
||||
writeUVInt(out, classesWithoutData.size());
|
||||
@@ -198,6 +222,13 @@ public class UsageFileAdapter extends DataAdapterHelper {
|
||||
clsMap.put(cls, i++);
|
||||
}
|
||||
|
||||
writeUVInt(out, uClsList.size());
|
||||
int u = 0;
|
||||
for (String cls : uClsList) {
|
||||
out.writeUTF(cls);
|
||||
uClsMap.put(cls, u++);
|
||||
}
|
||||
|
||||
// Method information
|
||||
List<MthRef> methods = clsDataMap.values().stream()
|
||||
.flatMap(c -> c.getMthUsage().values().stream())
|
||||
@@ -212,33 +243,18 @@ public class UsageFileAdapter extends DataAdapterHelper {
|
||||
}
|
||||
|
||||
// Unresolved method information
|
||||
Set<IMethodRef> unresolvedMethods = clsDataMap.values().stream()
|
||||
.flatMap(classUsageData -> classUsageData.getMthUsage().values().stream())
|
||||
.flatMap(methodUsageData -> {
|
||||
List<IMethodRef> unresolvedUsageList = methodUsageData.getUnresolvedUsage();
|
||||
return (unresolvedUsageList == null) ? null : unresolvedUsageList.stream();
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
writeUVInt(out, unresolvedMethods.size());
|
||||
int k = 0;
|
||||
for (IMethodRef uMth : unresolvedMethods) {
|
||||
String name = uMth.getName();
|
||||
out.writeUTF((name == null) ? "" : name);
|
||||
String parentClassType = uMth.getParentClassType();
|
||||
out.writeUTF((parentClassType == null) ? "" : parentClassType);
|
||||
String returnType = uMth.getReturnType();
|
||||
out.writeUTF((returnType == null) ? "" : returnType);
|
||||
List<String> argTypes = uMth.getArgTypes();
|
||||
if (argTypes == null) {
|
||||
out.writeInt(0);
|
||||
} else {
|
||||
out.writeInt(argTypes.size());
|
||||
for (String arg : argTypes) {
|
||||
out.writeUTF(arg);
|
||||
}
|
||||
for (IMethodRef uMthRef : unresolvedMethods) {
|
||||
writeUVInt(out, uClsMap.get(uMthRef.getParentClassType()));
|
||||
out.writeUTF(uMthRef.getName());
|
||||
out.writeUTF(uMthRef.getReturnType());
|
||||
List<String> args = uMthRef.getArgTypes();
|
||||
writeUVInt(out, args.size());
|
||||
for (String arg : args) {
|
||||
out.writeUTF(arg);
|
||||
}
|
||||
uMthMap.put(uMth, k++);
|
||||
uMthMap.put(uMthRef, k++);
|
||||
}
|
||||
|
||||
// Usage data
|
||||
|
||||
@@ -33,7 +33,7 @@ public class UsageInfoCache implements IUsageInfoCache {
|
||||
}
|
||||
synchronized (LOAD_DATA_SYNC) {
|
||||
if (rawUsageData == null) {
|
||||
rawUsageData = UsageFileAdapter.load(usageFile, inputs);
|
||||
rawUsageData = UsageFileAdapter.load(root, usageFile, inputs);
|
||||
}
|
||||
if (rawUsageData != null) {
|
||||
UsageData data = new UsageData(root, rawUsageData);
|
||||
|
||||
@@ -51,7 +51,7 @@ abstract class AbstractCodeAreaLine {
|
||||
|
||||
/**
|
||||
* This could be itself or:
|
||||
* - the enclosing method delcaration if line is in a method
|
||||
* - the enclosing method declaration if line is in a method
|
||||
* - the enclosing class declaration if line is a field declaration
|
||||
*/
|
||||
public IDeclaration getEnclosingScopeDeclaration() throws BadLocationException, FallbackSyncException {
|
||||
|
||||
@@ -25,14 +25,16 @@ import javax.swing.UIManager;
|
||||
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.plugins.input.data.IMethodRef;
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.utils.DotGraphUtils;
|
||||
import jadx.gui.treemodel.JMethod;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.layout.WrapLayout;
|
||||
|
||||
import static jadx.core.utils.DotGraphUtils.formatColor;
|
||||
import static jadx.core.utils.DotGraphUtils.toDotNodeName;
|
||||
|
||||
public class CallGraphDialog extends GraphDialog {
|
||||
private static final long serialVersionUID = -850803763322590708L;
|
||||
|
||||
@@ -43,7 +45,7 @@ public class CallGraphDialog extends GraphDialog {
|
||||
private int calleeDepthLimit = 3;
|
||||
private int nextNodeID;
|
||||
private Map<JavaMethod, Integer> methodToNodeID;
|
||||
private Map<IMethodRef, Integer> unresolvedMethodToNodeID;
|
||||
private Map<MethodInfo, Integer> unresolvedMethodToNodeID;
|
||||
private Set<Edge> edges;
|
||||
private boolean longNames = false;
|
||||
|
||||
@@ -222,10 +224,8 @@ public class CallGraphDialog extends GraphDialog {
|
||||
if (depth >= calleeDepthLimit) {
|
||||
return;
|
||||
}
|
||||
List<IMethodRef> used = javaMethod.getUnresolvedUsed();
|
||||
|
||||
// add "calls" relationships
|
||||
for (IMethodRef callee : used) {
|
||||
for (MethodInfo callee : javaMethod.getUnresolvedUsed()) {
|
||||
String name = callee.getName();
|
||||
if (name == null) {
|
||||
continue;
|
||||
@@ -252,21 +252,21 @@ public class CallGraphDialog extends GraphDialog {
|
||||
methodToNodeID.put(method, nodeID);
|
||||
}
|
||||
String name = DotGraphUtils.methodFormatName(method, longNames);
|
||||
f.format("Node_%d [ label=\"{%s}\" %s]\n", nodeID, UiUtils.toDotNodeName(name), extra);
|
||||
f.format("Node_%d [ label=\"{%s}\" %s]\n", nodeID, toDotNodeName(name), extra);
|
||||
if (javaMethod.callsSelf()) {
|
||||
addEdge(f, nodeID, nodeID);
|
||||
}
|
||||
return nodeID;
|
||||
}
|
||||
|
||||
private int addNode(Formatter f, IMethodRef method) {
|
||||
private int addNode(Formatter f, MethodInfo method) {
|
||||
return addNode(f, method, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a node representing an unresolved method to the graph in f. Returns the ID of the new node
|
||||
*/
|
||||
private int addNode(Formatter f, IMethodRef method, String extra) {
|
||||
private int addNode(Formatter f, MethodInfo method, String extra) {
|
||||
int nodeID;
|
||||
if (unresolvedMethodToNodeID.containsKey(method)) {
|
||||
nodeID = unresolvedMethodToNodeID.get(method);
|
||||
@@ -275,15 +275,10 @@ public class CallGraphDialog extends GraphDialog {
|
||||
nextNodeID++;
|
||||
unresolvedMethodToNodeID.put(method, nodeID);
|
||||
}
|
||||
|
||||
String name = DotGraphUtils.unresolvedMethodFormatName(method, longNames);
|
||||
|
||||
Color themeOutOfFocus = UIManager.getColor("Component.disabledBorderColor");
|
||||
String outOfFocus =
|
||||
String.format("color=\"#%02x%02x%02x\"", themeOutOfFocus.getRed(), themeOutOfFocus.getGreen(),
|
||||
themeOutOfFocus.getBlue());
|
||||
|
||||
f.format("Node_%d [ label=\"{%s}\" style=dashed %s %s]\n", nodeID, UiUtils.toDotNodeName(name), outOfFocus, extra);
|
||||
String outOfFocus = "color=" + formatColor(themeOutOfFocus);
|
||||
f.format("Node_%d [ label=\"{%s}\" style=dashed %s %s]\n", nodeID, toDotNodeName(name), outOfFocus, extra);
|
||||
return nodeID;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,12 +24,16 @@ import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.IMethodDetails;
|
||||
import jadx.core.dex.nodes.MethodNode;
|
||||
import jadx.core.utils.DotGraphUtils;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.layout.WrapLayout;
|
||||
|
||||
import static jadx.core.utils.DotGraphUtils.classFormatName;
|
||||
import static jadx.core.utils.DotGraphUtils.formatColor;
|
||||
import static jadx.core.utils.DotGraphUtils.toDotNodeName;
|
||||
|
||||
public class ClassInheritanceGraphDialog extends GraphDialog {
|
||||
private static final long serialVersionUID = 938883901412562913L;
|
||||
|
||||
@@ -43,8 +47,9 @@ public class ClassInheritanceGraphDialog extends GraphDialog {
|
||||
private int nextNodeID = 0;
|
||||
|
||||
public ClassInheritanceGraphDialog(MainWindow mainWindow, ClassNode cls) {
|
||||
super(mainWindow,
|
||||
String.format("%s: %s", NLS.str("graph_viewer.inheritance_graph.title"), DotGraphUtils.classFormatName(cls, false)));
|
||||
super(mainWindow, String.format("%s: %s",
|
||||
NLS.str("graph_viewer.inheritance_graph.title"),
|
||||
classFormatName(cls, false)));
|
||||
this.cls = cls;
|
||||
}
|
||||
|
||||
@@ -96,7 +101,6 @@ public class ClassInheritanceGraphDialog extends GraphDialog {
|
||||
}
|
||||
|
||||
private String generateGraph(ClassNode rootClass) {
|
||||
|
||||
objectToNodeID = new HashMap<>();
|
||||
|
||||
Color themeBackground = UIManager.getColor("Panel.background");
|
||||
@@ -104,17 +108,11 @@ public class ClassInheritanceGraphDialog extends GraphDialog {
|
||||
Color themeHighlight = UIManager.getColor("Component.focusedBorderColor");
|
||||
Color themeShade = UIManager.getColor("TextArea.background");
|
||||
|
||||
String bgColor =
|
||||
String.format("bgcolor=\"#%02x%02x%02x\"", themeBackground.getRed(), themeBackground.getGreen(), themeBackground.getBlue());
|
||||
String lineColor =
|
||||
String.format("color=\"#%02x%02x%02x\"", themeForeground.getRed(), themeForeground.getGreen(), themeForeground.getBlue());
|
||||
String fontColor =
|
||||
String.format("fontcolor=\"#%02x%02x%02x\"", themeForeground.getRed(), themeForeground.getGreen(),
|
||||
themeForeground.getBlue());
|
||||
String highlightColor =
|
||||
String.format("color=\"#%02x%02x%02x\"", themeHighlight.getRed(), themeHighlight.getGreen(),
|
||||
themeHighlight.getBlue());
|
||||
String shadeColor = String.format("fillcolor=\"#%02x%02x%02x\"", themeShade.getRed(), themeShade.getGreen(), themeShade.getBlue());
|
||||
String bgColor = "bgcolor=" + formatColor(themeBackground);
|
||||
String lineColor = "color=" + formatColor(themeForeground);
|
||||
String fontColor = "fontcolor=" + formatColor(themeForeground);
|
||||
String highlightColor = "color=" + formatColor(themeHighlight);
|
||||
String shadeColor = "fillcolor=" + formatColor(themeShade);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try (Formatter f = new Formatter(sb)) {
|
||||
@@ -144,10 +142,7 @@ public class ClassInheritanceGraphDialog extends GraphDialog {
|
||||
int classID = addNode(f, cls, extra);
|
||||
|
||||
// add interface relationships
|
||||
List<ArgType> ifaces = cls.getInterfaces();
|
||||
for (int i = 0; i < ifaces.size(); i++) {
|
||||
ArgType iface = ifaces.get(i);
|
||||
|
||||
for (ArgType iface : cls.getInterfaces()) {
|
||||
int ifaceID;
|
||||
ClassNode ifaceNode = cls.root().resolveClass(iface);
|
||||
if (ifaceNode != null) {
|
||||
@@ -160,10 +155,9 @@ public class ClassInheritanceGraphDialog extends GraphDialog {
|
||||
String edgeLabel = cls.getAccessFlags().isInterface() ? "extends" : "implements";
|
||||
f.format("Node_%d -> Node_%d [label=\"%s\" style=\"dashed\" ]\n", classID, ifaceID, edgeLabel);
|
||||
}
|
||||
|
||||
// add superclass relationship
|
||||
ArgType superClass = cls.getSuperClass();
|
||||
if (superClass != ArgType.OBJECT) {
|
||||
if (superClass != ArgType.OBJECT && superClass != null) {
|
||||
int superClsID;
|
||||
cls = cls.root().resolveClass(superClass);
|
||||
if (cls != null) {
|
||||
@@ -194,8 +188,8 @@ public class ClassInheritanceGraphDialog extends GraphDialog {
|
||||
if (cls.getAccessFlags().isInterface()) {
|
||||
extra += " style=\"dashed, filled\"";
|
||||
}
|
||||
String name = DotGraphUtils.classFormatName(cls, longNames);
|
||||
f.format("Node_%d [ label=\"{%s\\ ", nodeID, UiUtils.toDotNodeName(name));
|
||||
String name = classFormatName(cls, longNames);
|
||||
f.format("Node_%d [ label=\"{%s\\ ", nodeID, toDotNodeName(name));
|
||||
if (overrides) {
|
||||
f.format("|");
|
||||
List<Pair<String, String>> table = new ArrayList<>();
|
||||
@@ -207,12 +201,10 @@ public class ClassInheritanceGraphDialog extends GraphDialog {
|
||||
Formatter details = new Formatter();
|
||||
details.format(" overrides ");
|
||||
for (IMethodDetails baseMthDetails : ovrdAttr.getOverrideList()) {
|
||||
String baseClassName = DotGraphUtils.classFormatName(baseMthDetails.getMethodInfo().getDeclClass(), longNames);
|
||||
String baseClassName = classFormatName(baseMthDetails.getMethodInfo().getDeclClass(), longNames);
|
||||
details.format("%s, ", baseClassName);
|
||||
}
|
||||
String detailsString = details.toString();
|
||||
// Remove trailing ', '
|
||||
detailsString = detailsString.substring(0, detailsString.length() - 2);
|
||||
String detailsString = StringUtils.removeSuffix(details.toString(), ", ");
|
||||
table.add(Pair.of(methodName, detailsString));
|
||||
details.close();
|
||||
}
|
||||
@@ -231,7 +223,7 @@ public class ClassInheritanceGraphDialog extends GraphDialog {
|
||||
return nodeID;
|
||||
}
|
||||
|
||||
// Add a node for an unresolved argtype
|
||||
// Add a node for an unresolved arg type
|
||||
private int addNode(Formatter f, ArgType argType) {
|
||||
return addNode(f, argType, "");
|
||||
}
|
||||
@@ -246,10 +238,9 @@ public class ClassInheritanceGraphDialog extends GraphDialog {
|
||||
objectToNodeID.put(argType, nodeID);
|
||||
}
|
||||
Color themeOutOfFocus = UIManager.getColor("Component.disabledBorderColor");
|
||||
String outOfFocus =
|
||||
String.format("color=\"#%02x%02x%02x\"", themeOutOfFocus.getRed(), themeOutOfFocus.getGreen(), themeOutOfFocus.getBlue());
|
||||
String outOfFocus = "color=" + formatColor(themeOutOfFocus);
|
||||
String name = DotGraphUtils.interfaceFormatName(argType, cls, longNames);
|
||||
f.format("Node_%d [ label=\"{%s}\" %s %s]\n", nodeID, UiUtils.toDotNodeName(name), outOfFocus, extra);
|
||||
f.format("Node_%d [ label=\"{%s}\" %s %s]\n", nodeID, toDotNodeName(name), outOfFocus, extra);
|
||||
return nodeID;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +26,11 @@ import jadx.core.utils.DotGraphUtils;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.layout.WrapLayout;
|
||||
|
||||
import static jadx.core.utils.DotGraphUtils.formatColor;
|
||||
import static jadx.core.utils.DotGraphUtils.toDotNodeName;
|
||||
|
||||
public class ClassMethodGraphDialog extends GraphDialog {
|
||||
private static final long serialVersionUID = -850803763322590708L;
|
||||
|
||||
@@ -116,10 +118,6 @@ public class ClassMethodGraphDialog extends GraphDialog {
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatColor(Color color) {
|
||||
return String.format("\"#%02x%02x%02x\"", color.getRed(), color.getGreen(), color.getBlue());
|
||||
}
|
||||
|
||||
private void addCallers(int depth, Formatter f, JavaMethod javaMethod) {
|
||||
if (depth >= CALLER_DEPTH_LIMIT) {
|
||||
return;
|
||||
@@ -152,7 +150,7 @@ public class ClassMethodGraphDialog extends GraphDialog {
|
||||
methodToNodeID.put(method, nodeID);
|
||||
}
|
||||
String name = DotGraphUtils.methodFormatName(method, longNames);
|
||||
f.format("Node_%d [ label=\"{%s}\"]\n", nodeID, UiUtils.toDotNodeName(name));
|
||||
f.format("Node_%d [ label=\"{%s}\"]\n", nodeID, toDotNodeName(name));
|
||||
if (method.callsSelf()) {
|
||||
addEdge(f, nodeID, nodeID);
|
||||
}
|
||||
|
||||
@@ -565,11 +565,4 @@ public class UiUtils {
|
||||
public static boolean nearlyEqual(float a, float b) {
|
||||
return Math.abs(a - b) < 1E-6f;
|
||||
}
|
||||
|
||||
// Formats a string to be in a .DOT node
|
||||
public static String toDotNodeName(String fullName) {
|
||||
String newName = fullName.replace("<", "\\<");
|
||||
newName = newName.replace(">", "\\>");
|
||||
return newName;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user