refactor: use MethodInfo for unresolved methods usage, improve related code

This commit is contained in:
Skylot
2026-06-17 15:46:39 +01:00
parent 9d4babcdce
commit 59003bdb1f
20 changed files with 191 additions and 145 deletions
@@ -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);
}
}
+7 -1
View File
@@ -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;
}
}