From 59003bdb1f70e441de56640cba348937ed9e52f5 Mon Sep 17 00:00:00 2001 From: Skylot <118523+skylot@users.noreply.github.com> Date: Wed, 17 Jun 2026 15:46:39 +0100 Subject: [PATCH] refactor: use MethodInfo for unresolved methods usage, improve related code --- .../src/main/java/jadx/api/JavaMethod.java | 4 +- .../jadx/api/usage/IUsageInfoVisitor.java | 4 +- .../main/java/jadx/core/codegen/TypeGen.java | 6 ++ .../java/jadx/core/dex/nodes/ClassNode.java | 8 ++ .../java/jadx/core/dex/nodes/MethodNode.java | 7 +- .../core/dex/visitors/usage/UsageInfo.java | 14 +-- .../dex/visitors/usage/UsageInfoVisitor.java | 21 ++--- .../java/jadx/core/utils/DotGraphUtils.java | 30 ++++--- .../main/java/jadx/core/utils/ListUtils.java | 4 +- .../java/jadx/core/utils/StringUtils.java | 21 +++++ .../jadx/gui/cache/usage/CachedMethodRef.java | 13 ++- .../gui/cache/usage/CollectUsageData.java | 9 +- .../java/jadx/gui/cache/usage/UsageData.java | 8 +- .../gui/cache/usage/UsageFileAdapter.java | 86 +++++++++++-------- .../jadx/gui/cache/usage/UsageInfoCache.java | 2 +- .../sync/fallback/AbstractCodeAreaLine.java | 2 +- .../jadx/gui/ui/graphs/CallGraphDialog.java | 27 +++--- .../graphs/ClassInheritanceGraphDialog.java | 53 +++++------- .../gui/ui/graphs/ClassMethodGraphDialog.java | 10 +-- .../src/main/java/jadx/gui/utils/UiUtils.java | 7 -- 20 files changed, 191 insertions(+), 145 deletions(-) diff --git a/jadx-core/src/main/java/jadx/api/JavaMethod.java b/jadx-core/src/main/java/jadx/api/JavaMethod.java index 645867c16..f8bce8f83 100644 --- a/jadx-core/src/main/java/jadx/api/JavaMethod.java +++ b/jadx-core/src/main/java/jadx/api/JavaMethod.java @@ -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 getUnresolvedUsed() { + public List getUnresolvedUsed() { return mth.getUnresolvedUsed(); } diff --git a/jadx-core/src/main/java/jadx/api/usage/IUsageInfoVisitor.java b/jadx-core/src/main/java/jadx/api/usage/IUsageInfoVisitor.java index d632315cf..3af4e44ca 100644 --- a/jadx-core/src/main/java/jadx/api/usage/IUsageInfoVisitor.java +++ b/jadx-core/src/main/java/jadx/api/usage/IUsageInfoVisitor.java @@ -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 methods); - void visitUnresolvedMethodsUsage(MethodNode mth, List methods); + void visitUnresolvedMethodsUsage(MethodNode mth, List methods); void visitIsSelfCall(MethodNode mth, boolean isSelfCall); diff --git a/jadx-core/src/main/java/jadx/core/codegen/TypeGen.java b/jadx-core/src/main/java/jadx/core/codegen/TypeGen.java index 693fbce8e..ba16f4070 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/TypeGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/TypeGen.java @@ -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 signatures(List types) { + return Utils.collectionMap(types, TypeGen::signature); + } + /** * Convert literal arg to string (preferred method) */ diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 1b46d8cfd..42f20d2de 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -746,6 +746,14 @@ public class ClassNode extends NotificationAttrNode } } + public void getInnerClassesRecursive(Set resultClassesSet) { + for (ClassNode innerCls : innerClasses) { + if (resultClassesSet.add(innerCls)) { + innerCls.getInnerAndInlinedClassesRecursive(resultClassesSet); + } + } + } + public void addInnerClass(ClassNode cls) { if (innerClasses.isEmpty()) { innerClasses = new ArrayList<>(5); diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index 48de30684..05d231b5a 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -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 useIn = Collections.emptyList(); // Unresolved methods that use this method - private List unresolvedUsed = Collections.emptyList(); + private List unresolvedUsed = Collections.emptyList(); // Methods that this method uses private Set 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 getUnresolvedUsed() { + public List getUnresolvedUsed() { return unresolvedUsed; } - public void setUnresolvedUsed(List unresolvedUsed) { + public void setUnresolvedUsed(List unresolvedUsed) { this.unresolvedUsed = unresolvedUsed; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfo.java b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfo.java index 41f8c0c96..1b0c24685 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfo.java @@ -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 mthUsage = new UseSet<>(); // MethodNodeA -> Set of MethodNodes that MethodNodeA calls private final UseSet mthUses = new UseSet<>(); - // MethodNodeA -> Set of IMethodRefs for methods that MethodNodeA calls that cannot be resolved - private final UseSet unresolvedMthUsage = new UseSet<>(); + // MethodNodeA -> Set of MethodInfos for methods that MethodNodeA calls that cannot be resolved + private final UseSet unresolvedMthUsage = new UseSet<>(); private final Map 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 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); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java index 0015e2fb0..b9c8cc304 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/usage/UsageInfoVisitor.java @@ -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; diff --git a/jadx-core/src/main/java/jadx/core/utils/DotGraphUtils.java b/jadx-core/src/main/java/jadx/core/utils/DotGraphUtils.java index 23a1fd43c..24bf95ca5 100644 --- a/jadx-core/src/main/java/jadx/core/utils/DotGraphUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/DotGraphUtils.java @@ -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 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(">", "\\>"); + } } diff --git a/jadx-core/src/main/java/jadx/core/utils/ListUtils.java b/jadx-core/src/main/java/jadx/core/utils/ListUtils.java index fa0333f2d..8f97a4b72 100644 --- a/jadx-core/src/main/java/jadx/core/utils/ListUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/ListUtils.java @@ -33,9 +33,7 @@ public class ListUtils { } public static List mutableListOf(T... objs) { - List list = new ArrayList<>(); - Collections.addAll(list, objs); - return list; + return new ArrayList<>(Arrays.asList(objs)); } public static boolean isSingleElement(@Nullable List list, T obj) { diff --git a/jadx-core/src/main/java/jadx/core/utils/StringUtils.java b/jadx-core/src/main/java/jadx/core/utils/StringUtils.java index 09fa334b4..bffe0ec62 100644 --- a/jadx-core/src/main/java/jadx/core/utils/StringUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/StringUtils.java @@ -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 splitByFixedString(String content, String splitStr) { + if (isEmpty(content)) { + return Collections.emptyList(); + } + List 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()); diff --git a/jadx-gui/src/main/java/jadx/gui/cache/usage/CachedMethodRef.java b/jadx-gui/src/main/java/jadx/gui/cache/usage/CachedMethodRef.java index 13ca8ddcb..6159f7492 100644 --- a/jadx-gui/src/main/java/jadx/gui/cache/usage/CachedMethodRef.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/usage/CachedMethodRef.java @@ -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'"); } - } diff --git a/jadx-gui/src/main/java/jadx/gui/cache/usage/CollectUsageData.java b/jadx-gui/src/main/java/jadx/gui/cache/usage/CollectUsageData.java index 49ba47f6d..e87ac97a7 100644 --- a/jadx-gui/src/main/java/jadx/gui/cache/usage/CollectUsageData.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/usage/CollectUsageData.java @@ -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 methods) { - data.getMethodData(mth).setUnresolvedUsage(methods); + public void visitUnresolvedMethodsUsage(MethodNode mth, List methods) { + data.getMethodData(mth).setUnresolvedUsage(mthInfoRef(methods)); } @Override @@ -68,4 +69,8 @@ final class CollectUsageData implements IUsageInfoVisitor { private List mthNodesRef(List methods) { return Utils.collectionMap(methods, m -> data.getMethodData(m).getMthRef()); } + + private List mthInfoRef(List methods) { + return Utils.collectionMap(methods, CachedMethodRef::new); + } } diff --git a/jadx-gui/src/main/java/jadx/gui/cache/usage/UsageData.java b/jadx-gui/src/main/java/jadx/gui/cache/usage/UsageData.java index 61645b43b..24b80934c 100644 --- a/jadx-gui/src/main/java/jadx/gui/cache/usage/UsageData.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/usage/UsageData.java @@ -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 resolveMthList(List mthRefList) { return Utils.collectionMap(mthRefList, m -> root.resolveDirectMethod(m.getCls(), m.getShortId())); } + + private List resolveMthInfoList(List mthRefList) { + return Utils.collectionMap(mthRefList, m -> MethodInfo.fromRef(root, m)); + } } diff --git a/jadx-gui/src/main/java/jadx/gui/cache/usage/UsageFileAdapter.java b/jadx-gui/src/main/java/jadx/gui/cache/usage/UsageFileAdapter.java index 6ff2879cc..2d35642b4 100644 --- a/jadx-gui/src/main/java/jadx/gui/cache/usage/UsageFileAdapter.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/usage/UsageFileAdapter.java @@ -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 inputs) { + public static synchronized @Nullable RawUsageData load(RootNode root, Path usageFile, List 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 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 mthMap = new HashMap<>(); Map uMthMap = new HashMap<>(); Map clsDataMap = usageData.getClsMap(); + + Map uClsMap = new HashMap<>(); + List unresolvedMethods = clsDataMap.values().stream() + .flatMap(classUsageData -> classUsageData.getMthUsage().values().stream()) + .flatMap(methodUsageData -> { + List unresolvedUsageList = methodUsageData.getUnresolvedUsage(); + return unresolvedUsageList == null ? null : unresolvedUsageList.stream(); + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + List classes = new ArrayList<>(clsDataMap.keySet()); Collections.sort(classes); List classesWithoutData = usageData.getClassesWithoutData(); + // pool for classes from unresolved methods + Set uClsNames = new HashSet<>(); + for (IMethodRef uMthRef : unresolvedMethods) { + uClsNames.add(uMthRef.getParentClassType()); + } + List 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 methods = clsDataMap.values().stream() .flatMap(c -> c.getMthUsage().values().stream()) @@ -212,33 +243,18 @@ public class UsageFileAdapter extends DataAdapterHelper { } // Unresolved method information - Set unresolvedMethods = clsDataMap.values().stream() - .flatMap(classUsageData -> classUsageData.getMthUsage().values().stream()) - .flatMap(methodUsageData -> { - List 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 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 args = uMthRef.getArgTypes(); + writeUVInt(out, args.size()); + for (String arg : args) { + out.writeUTF(arg); } - uMthMap.put(uMth, k++); + uMthMap.put(uMthRef, k++); } // Usage data diff --git a/jadx-gui/src/main/java/jadx/gui/cache/usage/UsageInfoCache.java b/jadx-gui/src/main/java/jadx/gui/cache/usage/UsageInfoCache.java index f2206287f..618725f25 100644 --- a/jadx-gui/src/main/java/jadx/gui/cache/usage/UsageInfoCache.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/usage/UsageInfoCache.java @@ -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); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/fallback/AbstractCodeAreaLine.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/fallback/AbstractCodeAreaLine.java index 507672677..2c71cd21c 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/fallback/AbstractCodeAreaLine.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/fallback/AbstractCodeAreaLine.java @@ -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 { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/graphs/CallGraphDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/graphs/CallGraphDialog.java index aec696454..48710c29f 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/graphs/CallGraphDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/graphs/CallGraphDialog.java @@ -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 methodToNodeID; - private Map unresolvedMethodToNodeID; + private Map unresolvedMethodToNodeID; private Set edges; private boolean longNames = false; @@ -222,10 +224,8 @@ public class CallGraphDialog extends GraphDialog { if (depth >= calleeDepthLimit) { return; } - List 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; } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/graphs/ClassInheritanceGraphDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/graphs/ClassInheritanceGraphDialog.java index d7a19ed0b..2456bfc7b 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/graphs/ClassInheritanceGraphDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/graphs/ClassInheritanceGraphDialog.java @@ -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 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> 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; } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/graphs/ClassMethodGraphDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/graphs/ClassMethodGraphDialog.java index 5cc3e0d8a..c9e9f28be 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/graphs/ClassMethodGraphDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/graphs/ClassMethodGraphDialog.java @@ -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); } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java b/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java index 03a8c6ad2..56bd6d7e2 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/UiUtils.java @@ -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; - } }