From 6912ed40b4e90a2a585e8b61760e4e5a938306b5 Mon Sep 17 00:00:00 2001 From: Skylot Date: Mon, 24 Oct 2022 20:47:24 +0100 Subject: [PATCH] feat(gui): save usage data into disk cache --- .../src/main/java/jadx/api/JadxArgs.java | 25 +- .../java/jadx/api/usage/IUsageInfoCache.java | 15 + .../java/jadx/api/usage/IUsageInfoData.java | 12 + .../jadx/api/usage/IUsageInfoVisitor.java | 22 ++ .../api/usage/impl/EmptyUsageInfoCache.java | 24 ++ .../usage/impl/InMemoryUsageInfoCache.java | 34 +++ .../java/jadx/core/dex/nodes/ClassNode.java | 34 +-- .../java/jadx/core/dex/nodes/RootNode.java | 12 + .../core/dex/visitors/usage/UsageInfo.java | 31 ++- .../dex/visitors/usage/UsageInfoVisitor.java | 32 ++- .../java/jadx/core/utils/files/FileUtils.java | 22 ++ .../src/main/java/jadx/gui/JadxWrapper.java | 39 ++- .../code}/CodeCacheMode.java | 2 +- .../code}/CodeStringCache.java | 2 +- .../code}/FixedCodeCache.java | 2 +- .../code}/disk/BufferCodeCache.java | 2 +- .../code}/disk/CodeMetadataAdapter.java | 19 +- .../code}/disk/DiskCodeCache.java | 28 +- .../code}/disk/adapters/ArgTypeAdapter.java | 2 +- .../code}/disk/adapters/ClassNodeAdapter.java | 2 +- .../disk/adapters/CodeAnnotationAdapter.java | 2 +- .../code}/disk/adapters/DataAdapter.java | 2 +- .../disk/adapters/DataAdapterHelper.java | 2 +- .../code}/disk/adapters/FieldNodeAdapter.java | 2 +- .../disk/adapters/InsnCodeOffsetAdapter.java | 2 +- .../disk/adapters/MethodNodeAdapter.java | 13 +- .../disk/adapters/NodeDeclareRefAdapter.java | 2 +- .../code}/disk/adapters/NodeEndAdapter.java | 2 +- .../code}/disk/adapters/VarNodeAdapter.java | 10 +- .../code}/disk/adapters/VarRefAdapter.java | 2 +- .../jadx/gui/cache/usage/ClsUsageData.java | 56 ++++ .../gui/cache/usage/CollectUsageData.java | 55 ++++ .../java/jadx/gui/cache/usage/FldRef.java | 19 ++ .../jadx/gui/cache/usage/FldUsageData.java | 24 ++ .../java/jadx/gui/cache/usage/MthRef.java | 37 +++ .../jadx/gui/cache/usage/MthUsageData.java | 24 ++ .../jadx/gui/cache/usage/RawUsageData.java | 66 +++++ .../jadx/gui/cache/usage/UsageCacheMode.java | 7 + .../java/jadx/gui/cache/usage/UsageData.java | 84 ++++++ .../gui/cache/usage/UsageFileAdapter.java | 258 ++++++++++++++++++ .../jadx/gui/cache/usage/UsageInfoCache.java | 58 ++++ .../main/java/jadx/gui/jobs/ExportTask.java | 2 +- .../java/jadx/gui/settings/JadxSettings.java | 12 +- .../jadx/gui/settings/JadxSettingsWindow.java | 11 +- .../resources/i18n/Messages_de_DE.properties | 1 + .../resources/i18n/Messages_en_US.properties | 1 + .../resources/i18n/Messages_es_ES.properties | 1 + .../resources/i18n/Messages_ko_KR.properties | 1 + .../resources/i18n/Messages_pt_BR.properties | 1 + .../resources/i18n/Messages_zh_CN.properties | 1 + .../resources/i18n/Messages_zh_TW.properties | 1 + .../code}/DiskCodeCacheTest.java | 4 +- .../disk/adapters/DataAdapterHelperTest.java | 4 +- 53 files changed, 1023 insertions(+), 105 deletions(-) create mode 100644 jadx-core/src/main/java/jadx/api/usage/IUsageInfoCache.java create mode 100644 jadx-core/src/main/java/jadx/api/usage/IUsageInfoData.java create mode 100644 jadx-core/src/main/java/jadx/api/usage/IUsageInfoVisitor.java create mode 100644 jadx-core/src/main/java/jadx/api/usage/impl/EmptyUsageInfoCache.java create mode 100644 jadx-core/src/main/java/jadx/api/usage/impl/InMemoryUsageInfoCache.java rename jadx-gui/src/main/java/jadx/gui/{utils/codecache => cache/code}/CodeCacheMode.java (95%) rename jadx-gui/src/main/java/jadx/gui/{utils/codecache => cache/code}/CodeStringCache.java (98%) rename jadx-gui/src/main/java/jadx/gui/{utils/codecache => cache/code}/FixedCodeCache.java (93%) rename jadx-gui/src/main/java/jadx/gui/{utils/codecache => cache/code}/disk/BufferCodeCache.java (98%) rename jadx-gui/src/main/java/jadx/gui/{utils/codecache => cache/code}/disk/CodeMetadataAdapter.java (88%) rename jadx-gui/src/main/java/jadx/gui/{utils/codecache => cache/code}/disk/DiskCodeCache.java (90%) rename jadx-gui/src/main/java/jadx/gui/{utils/codecache => cache/code}/disk/adapters/ArgTypeAdapter.java (98%) rename jadx-gui/src/main/java/jadx/gui/{utils/codecache => cache/code}/disk/adapters/ClassNodeAdapter.java (92%) rename jadx-gui/src/main/java/jadx/gui/{utils/codecache => cache/code}/disk/adapters/CodeAnnotationAdapter.java (98%) rename jadx-gui/src/main/java/jadx/gui/{utils/codecache => cache/code}/disk/adapters/DataAdapter.java (82%) rename jadx-gui/src/main/java/jadx/gui/{utils/codecache => cache/code}/disk/adapters/DataAdapterHelper.java (96%) rename jadx-gui/src/main/java/jadx/gui/{utils/codecache => cache/code}/disk/adapters/FieldNodeAdapter.java (95%) rename jadx-gui/src/main/java/jadx/gui/{utils/codecache => cache/code}/disk/adapters/InsnCodeOffsetAdapter.java (92%) rename jadx-gui/src/main/java/jadx/gui/{utils/codecache => cache/code}/disk/adapters/MethodNodeAdapter.java (64%) rename jadx-gui/src/main/java/jadx/gui/{utils/codecache => cache/code}/disk/adapters/NodeDeclareRefAdapter.java (95%) rename jadx-gui/src/main/java/jadx/gui/{utils/codecache => cache/code}/disk/adapters/NodeEndAdapter.java (88%) rename jadx-gui/src/main/java/jadx/gui/{utils/codecache => cache/code}/disk/adapters/VarNodeAdapter.java (72%) rename jadx-gui/src/main/java/jadx/gui/{utils/codecache => cache/code}/disk/adapters/VarRefAdapter.java (92%) create mode 100644 jadx-gui/src/main/java/jadx/gui/cache/usage/ClsUsageData.java create mode 100644 jadx-gui/src/main/java/jadx/gui/cache/usage/CollectUsageData.java create mode 100644 jadx-gui/src/main/java/jadx/gui/cache/usage/FldRef.java create mode 100644 jadx-gui/src/main/java/jadx/gui/cache/usage/FldUsageData.java create mode 100644 jadx-gui/src/main/java/jadx/gui/cache/usage/MthRef.java create mode 100644 jadx-gui/src/main/java/jadx/gui/cache/usage/MthUsageData.java create mode 100644 jadx-gui/src/main/java/jadx/gui/cache/usage/RawUsageData.java create mode 100644 jadx-gui/src/main/java/jadx/gui/cache/usage/UsageCacheMode.java create mode 100644 jadx-gui/src/main/java/jadx/gui/cache/usage/UsageData.java create mode 100644 jadx-gui/src/main/java/jadx/gui/cache/usage/UsageFileAdapter.java create mode 100644 jadx-gui/src/main/java/jadx/gui/cache/usage/UsageInfoCache.java rename jadx-gui/src/test/java/jadx/gui/utils/{codecache => cache/code}/DiskCodeCacheTest.java (94%) rename jadx-gui/src/test/java/jadx/gui/utils/{codecache => cache/code}/disk/adapters/DataAdapterHelperTest.java (90%) diff --git a/jadx-core/src/main/java/jadx/api/JadxArgs.java b/jadx-core/src/main/java/jadx/api/JadxArgs.java index 989df6278..839938888 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgs.java @@ -1,5 +1,6 @@ package jadx.api; +import java.io.Closeable; import java.io.File; import java.nio.charset.StandardCharsets; import java.nio.file.Path; @@ -24,11 +25,13 @@ import jadx.api.deobf.IAliasProvider; import jadx.api.deobf.IRenameCondition; import jadx.api.impl.AnnotatedCodeWriter; import jadx.api.impl.InMemoryCodeCache; +import jadx.api.usage.IUsageInfoCache; +import jadx.api.usage.impl.InMemoryUsageInfoCache; import jadx.core.deobf.DeobfAliasProvider; import jadx.core.deobf.DeobfCondition; import jadx.core.utils.files.FileUtils; -public class JadxArgs { +public class JadxArgs implements Closeable { private static final Logger LOG = LoggerFactory.getLogger(JadxArgs.class); public static final int DEFAULT_THREADS_COUNT = Math.max(1, Runtime.getRuntime().availableProcessors() / 2); @@ -44,6 +47,13 @@ public class JadxArgs { private File outDirRes; private ICodeCache codeCache = new InMemoryCodeCache(); + + /** + * Usage data cache. Saves use places of classes, methods and fields between code reloads. + * Can be set to {@link jadx.api.usage.impl.EmptyUsageInfoCache} if code reload not needed. + */ + private IUsageInfoCache usageInfoCache = new InMemoryUsageInfoCache(); + private Function codeWriterProvider = AnnotatedCodeWriter::new; private int threadsCount = DEFAULT_THREADS_COUNT; @@ -148,16 +158,21 @@ public class JadxArgs { setOutDirRes(new File(rootDir, DEFAULT_RES_DIR)); } + @Override public void close() { try { inputFiles = null; if (codeCache != null) { codeCache.close(); } + if (usageInfoCache != null) { + usageInfoCache.close(); + } } catch (Exception e) { LOG.error("Failed to close JadxArgs", e); } finally { codeCache = null; + usageInfoCache = null; } } @@ -553,6 +568,14 @@ public class JadxArgs { this.codeWriterProvider = codeWriterProvider; } + public IUsageInfoCache getUsageInfoCache() { + return usageInfoCache; + } + + public void setUsageInfoCache(IUsageInfoCache usageInfoCache) { + this.usageInfoCache = usageInfoCache; + } + public ICodeData getCodeData() { return codeData; } diff --git a/jadx-core/src/main/java/jadx/api/usage/IUsageInfoCache.java b/jadx-core/src/main/java/jadx/api/usage/IUsageInfoCache.java new file mode 100644 index 000000000..8c6fef88b --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/usage/IUsageInfoCache.java @@ -0,0 +1,15 @@ +package jadx.api.usage; + +import java.io.Closeable; + +import org.jetbrains.annotations.Nullable; + +import jadx.core.dex.nodes.RootNode; + +public interface IUsageInfoCache extends Closeable { + + @Nullable + IUsageInfoData get(RootNode root); + + void set(RootNode root, IUsageInfoData data); +} diff --git a/jadx-core/src/main/java/jadx/api/usage/IUsageInfoData.java b/jadx-core/src/main/java/jadx/api/usage/IUsageInfoData.java new file mode 100644 index 000000000..6075fbbab --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/usage/IUsageInfoData.java @@ -0,0 +1,12 @@ +package jadx.api.usage; + +import jadx.core.dex.nodes.ClassNode; + +public interface IUsageInfoData { + + void apply(); + + void applyForClass(ClassNode cls); + + void visitUsageData(IUsageInfoVisitor visitor); +} diff --git a/jadx-core/src/main/java/jadx/api/usage/IUsageInfoVisitor.java b/jadx-core/src/main/java/jadx/api/usage/IUsageInfoVisitor.java new file mode 100644 index 000000000..0495a052e --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/usage/IUsageInfoVisitor.java @@ -0,0 +1,22 @@ +package jadx.api.usage; + +import java.util.List; + +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; + +public interface IUsageInfoVisitor { + + void visitClassDeps(ClassNode cls, List deps); + + void visitClassUsage(ClassNode cls, List usage); + + void visitClassUseInMethods(ClassNode cls, List methods); + + void visitFieldsUsage(FieldNode fld, List methods); + + void visitMethodsUsage(MethodNode mth, List methods); + + void visitComplete(); +} diff --git a/jadx-core/src/main/java/jadx/api/usage/impl/EmptyUsageInfoCache.java b/jadx-core/src/main/java/jadx/api/usage/impl/EmptyUsageInfoCache.java new file mode 100644 index 000000000..dff52f616 --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/usage/impl/EmptyUsageInfoCache.java @@ -0,0 +1,24 @@ +package jadx.api.usage.impl; + +import java.io.IOException; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.usage.IUsageInfoCache; +import jadx.api.usage.IUsageInfoData; +import jadx.core.dex.nodes.RootNode; + +public class EmptyUsageInfoCache implements IUsageInfoCache { + @Override + public @Nullable IUsageInfoData get(RootNode root) { + return null; + } + + @Override + public void set(RootNode root, IUsageInfoData data) { + } + + @Override + public void close() throws IOException { + } +} diff --git a/jadx-core/src/main/java/jadx/api/usage/impl/InMemoryUsageInfoCache.java b/jadx-core/src/main/java/jadx/api/usage/impl/InMemoryUsageInfoCache.java new file mode 100644 index 000000000..27e26614f --- /dev/null +++ b/jadx-core/src/main/java/jadx/api/usage/impl/InMemoryUsageInfoCache.java @@ -0,0 +1,34 @@ +package jadx.api.usage.impl; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.usage.IUsageInfoCache; +import jadx.api.usage.IUsageInfoData; +import jadx.core.dex.nodes.RootNode; + +public class InMemoryUsageInfoCache implements IUsageInfoCache { + + private IUsageInfoData data; + + /** + * `data` field tied to root node instance, keep hash to reset cache on change + */ + private int rootNodeHash; + + @Override + public @Nullable IUsageInfoData get(RootNode root) { + return rootNodeHash == root.hashCode() ? data : null; + } + + @Override + public void set(RootNode root, IUsageInfoData data) { + this.rootNodeHash = root.hashCode(); + this.data = data; + } + + @Override + public void close() { + this.rootNodeHash = 0; + this.data = null; + } +} 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 b536fb8e5..a908f6ac9 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 @@ -32,6 +32,7 @@ import jadx.api.plugins.input.data.attributes.types.InnerClassesAttr; import jadx.api.plugins.input.data.attributes.types.InnerClsInfo; import jadx.api.plugins.input.data.attributes.types.SourceFileAttr; import jadx.api.plugins.input.data.impl.ListConsumer; +import jadx.api.usage.IUsageInfoData; import jadx.core.Consts; import jadx.core.ProcessClass; import jadx.core.dex.attributes.AFlag; @@ -106,10 +107,10 @@ public class ClassNode extends NotificationAttrNode this.clsInfo = ClassInfo.fromType(root, ArgType.object(cls.getType())); this.packageNode = PackageNode.getForClass(root, clsInfo.getPackage(), this); this.clsData = cls.copy(); - initialLoad(clsData); + load(clsData, false); } - private void initialLoad(IClassData cls) { + private void load(IClassData cls, boolean reloading) { try { addAttrs(cls.getAttributes()); this.accessFlags = new AccessInfo(getAccessFlags(cls), AFType.CLASS); @@ -119,13 +120,11 @@ public class ClassNode extends NotificationAttrNode ListConsumer fieldsConsumer = new ListConsumer<>(fld -> FieldNode.build(this, fld)); ListConsumer methodsConsumer = new ListConsumer<>(mth -> MethodNode.build(this, mth)); cls.visitFieldsAndMethods(fieldsConsumer, methodsConsumer); - if (this.fields != null && this.methods != null) { - // TODO: temporary solution for restore usage info in reloaded methods and fields - restoreUsageData(this.fields, this.methods, fieldsConsumer.getResult(), methodsConsumer.getResult()); - } this.fields = fieldsConsumer.getResult(); this.methods = methodsConsumer.getResult(); - + if (reloading) { + restoreUsageData(); + } initStaticValues(fields); processAttributes(this); buildCache(); @@ -139,21 +138,10 @@ public class ClassNode extends NotificationAttrNode } } - private void restoreUsageData(List oldFields, List oldMethods, - List newFields, List newMethods) { - Map oldFieldMap = Utils.groupBy(oldFields, FieldNode::getFieldInfo); - for (FieldNode newField : newFields) { - FieldNode oldField = oldFieldMap.get(newField.getFieldInfo()); - if (oldField != null) { - newField.setUseIn(oldField.getUseIn()); - } - } - Map oldMethodsMap = Utils.groupBy(oldMethods, MethodNode::getMethodInfo); - for (MethodNode newMethod : newMethods) { - MethodNode oldMethod = oldMethodsMap.get(newMethod.getMethodInfo()); - if (oldMethod != null) { - newMethod.setUseIn(oldMethod.getUseIn()); - } + private void restoreUsageData() { + IUsageInfoData usageInfoData = root.getArgs().getUsageInfoCache().get(root); + if (usageInfoData != null) { + usageInfoData.applyForClass(this); } } @@ -357,7 +345,7 @@ public class ClassNode extends NotificationAttrNode unload(); clearAttributes(); root().getConstValues().removeForClass(this); - initialLoad(clsData); + load(clsData, true); innerClasses.forEach(ClassNode::deepUnload); } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java index b4bdbd342..893fb7521 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java @@ -472,6 +472,18 @@ public class RootNode { return deepResolveMethod(cls, mth.makeSignature(false)); } + public @NotNull MethodNode resolveDirectMethod(String rawClsName, String mthShortId) { + ClassNode clsNode = resolveClass(rawClsName); + if (clsNode == null) { + throw new RuntimeException("Class not found: " + rawClsName); + } + MethodNode methodNode = clsNode.searchMethodByShortId(mthShortId); + if (methodNode == null) { + throw new RuntimeException("Method not found: " + rawClsName + "." + mthShortId); + } + return methodNode; + } + @Nullable private MethodNode deepResolveMethod(@NotNull ClassNode cls, String signature) { for (MethodNode m : cls.getMethods()) { 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 093da3d98..bfa13e5b7 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 @@ -6,6 +6,8 @@ import java.util.List; import java.util.Set; import java.util.function.Consumer; +import jadx.api.usage.IUsageInfoData; +import jadx.api.usage.IUsageInfoVisitor; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.FieldNode; @@ -14,7 +16,7 @@ import jadx.core.dex.nodes.RootNode; import static jadx.core.utils.Utils.notEmpty; -public class UsageInfo { +public class UsageInfo implements IUsageInfoData { private final RootNode root; private final UseSet clsDeps = new UseSet<>(); @@ -27,6 +29,7 @@ public class UsageInfo { this.root = root; } + @Override public void apply() { clsDeps.visit((cls, deps) -> cls.setDependencies(sortedList(deps))); clsUsage.visit((cls, deps) -> cls.setUseIn(sortedList(deps))); @@ -35,6 +38,29 @@ public class UsageInfo { mthUsage.visit((mth, methods) -> mth.setUseIn(sortedList(methods))); } + @Override + public void applyForClass(ClassNode cls) { + cls.setDependencies(sortedList(clsDeps.get(cls))); + cls.setUseIn(sortedList(clsUsage.get(cls))); + cls.setUseInMth(sortedList(clsUseInMth.get(cls))); + for (FieldNode fld : cls.getFields()) { + fld.setUseIn(sortedList(fieldUsage.get(fld))); + } + for (MethodNode mth : cls.getMethods()) { + mth.setUseIn(sortedList(mthUsage.get(mth))); + } + } + + @Override + public void visitUsageData(IUsageInfoVisitor visitor) { + clsDeps.visit((cls, deps) -> visitor.visitClassDeps(cls, sortedList(deps))); + clsUsage.visit((cls, deps) -> visitor.visitClassUsage(cls, sortedList(deps))); + clsUseInMth.visit((cls, methods) -> visitor.visitClassUseInMethods(cls, sortedList(methods))); + fieldUsage.visit((field, methods) -> visitor.visitFieldsUsage(field, sortedList(methods))); + mthUsage.visit((mth, methods) -> visitor.visitMethodsUsage(mth, sortedList(methods))); + visitor.visitComplete(); + } + public void clsUse(ClassNode cls, ArgType useType) { processType(useType, depCls -> clsUse(cls, depCls)); } @@ -101,6 +127,9 @@ public class UsageInfo { } private static > List sortedList(Set deps) { + if (deps == null || deps.isEmpty()) { + return Collections.emptyList(); + } List list = new ArrayList<>(deps); Collections.sort(list); return list; 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 40de47f64..1afd75563 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 @@ -3,6 +3,9 @@ package jadx.core.dex.visitors.usage; import java.util.Collections; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import jadx.api.plugins.input.data.ICallSite; import jadx.api.plugins.input.data.ICodeReader; import jadx.api.plugins.input.data.IMethodHandle; @@ -10,6 +13,8 @@ import jadx.api.plugins.input.data.IMethodRef; import jadx.api.plugins.input.insns.InsnData; import jadx.api.plugins.input.insns.Opcode; import jadx.api.plugins.input.insns.custom.ICustomPayload; +import jadx.api.usage.IUsageInfoCache; +import jadx.api.usage.IUsageInfoData; import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.args.ArgType; @@ -33,14 +38,39 @@ import jadx.core.utils.input.InsnDataUtils; } ) public class UsageInfoVisitor extends AbstractVisitor { + private static final Logger LOG = LoggerFactory.getLogger(UsageInfoVisitor.class); @Override public void init(RootNode root) { + IUsageInfoCache usageCache = root.getArgs().getUsageInfoCache(); + IUsageInfoData usageInfoData = usageCache.get(root); + if (usageInfoData != null) { + try { + apply(usageInfoData); + return; + } catch (Exception e) { + LOG.error("Failed to apply cached usage data", e); + } + } + IUsageInfoData collectedInfoData = buildUsageData(root); + usageCache.set(root, collectedInfoData); + apply(collectedInfoData); + } + + private static void apply(IUsageInfoData usageInfoData) { + long start = System.currentTimeMillis(); + usageInfoData.apply(); + if (LOG.isDebugEnabled()) { + LOG.debug("Apply usage data in {}ms", System.currentTimeMillis() - start); + } + } + + private static IUsageInfoData buildUsageData(RootNode root) { UsageInfo usageInfo = new UsageInfo(root); for (ClassNode cls : root.getClasses()) { processClass(cls, usageInfo); } - usageInfo.apply(); + return usageInfo; } private static void processClass(ClassNode cls, UsageInfo usageInfo) { diff --git a/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java b/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java index 37917e343..78ef49551 100644 --- a/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java @@ -3,6 +3,7 @@ package jadx.core.utils.files; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.Closeable; +import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -17,6 +18,7 @@ import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; import java.security.MessageDigest; import java.util.ArrayList; import java.util.Collections; @@ -358,4 +360,24 @@ public class FileUtils { throw new JadxRuntimeException("Failed to build hash", e); } } + + /** + * Hash timestamps of input files + */ + public static String buildInputsHash(List inputPaths) { + try (ByteArrayOutputStream bout = new ByteArrayOutputStream(); + DataOutputStream data = new DataOutputStream(bout)) { + List inputFiles = FileUtils.expandDirs(inputPaths); + Collections.sort(inputFiles); + data.write(inputPaths.size()); + data.write(inputFiles.size()); + for (Path inputFile : inputFiles) { + FileTime modifiedTime = Files.getLastModifiedTime(inputFile); + data.writeLong(modifiedTime.toMillis()); + } + return FileUtils.md5Sum(bout.toByteArray()); + } catch (Exception e) { + throw new JadxRuntimeException("Failed to build hash for inputs", e); + } + } } diff --git a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java index e2bca90f5..ab14f9cee 100644 --- a/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java +++ b/jadx-gui/src/main/java/jadx/gui/JadxWrapper.java @@ -21,18 +21,22 @@ import jadx.api.ResourceFile; import jadx.api.impl.InMemoryCodeCache; import jadx.api.impl.plugins.PluginsContext; import jadx.api.metadata.ICodeNodeRef; +import jadx.api.usage.impl.EmptyUsageInfoCache; +import jadx.api.usage.impl.InMemoryUsageInfoCache; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ProcessState; import jadx.core.dex.nodes.RootNode; import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.core.utils.files.FileUtils; +import jadx.gui.cache.code.CodeStringCache; +import jadx.gui.cache.code.disk.BufferCodeCache; +import jadx.gui.cache.code.disk.DiskCodeCache; +import jadx.gui.cache.usage.UsageInfoCache; import jadx.gui.plugins.context.GuiPluginsContext; import jadx.gui.settings.JadxProject; import jadx.gui.settings.JadxSettings; import jadx.gui.ui.MainWindow; import jadx.gui.utils.CacheObject; -import jadx.gui.utils.codecache.CodeStringCache; -import jadx.gui.utils.codecache.disk.BufferCodeCache; -import jadx.gui.utils.codecache.disk.DiskCodeCache; import static jadx.core.dex.nodes.ProcessState.GENERATED_AND_UNLOADED; import static jadx.core.dex.nodes.ProcessState.NOT_LOADED; @@ -60,10 +64,10 @@ public class JadxWrapper { JadxArgs jadxArgs = getSettings().toJadxArgs(); project.fillJadxArgs(jadxArgs); - this.decompiler = new JadxDecompiler(jadxArgs); - this.guiPluginsContext = new GuiPluginsContext(mainWindow); - this.decompiler.getPluginsContext().setGuiContext(guiPluginsContext); - this.decompiler.load(); + decompiler = new JadxDecompiler(jadxArgs); + initGuiPluginsContext(); + initUsageCache(jadxArgs); + decompiler.load(); initCodeCache(); } } catch (Exception e) { @@ -119,6 +123,25 @@ public class JadxWrapper { return new BufferCodeCache(diskCache); } + private void initUsageCache(JadxArgs jadxArgs) { + switch (getSettings().getUsageCacheMode()) { + case NONE: + jadxArgs.setUsageInfoCache(new EmptyUsageInfoCache()); + break; + case MEMORY: + jadxArgs.setUsageInfoCache(new InMemoryUsageInfoCache()); + break; + case DISK: + jadxArgs.setUsageInfoCache(new UsageInfoCache(getProject().getCacheDir(), jadxArgs.getInputFiles())); + break; + } + } + + private void initGuiPluginsContext() { + guiPluginsContext = new GuiPluginsContext(mainWindow); + decompiler.getPluginsContext().setGuiContext(guiPluginsContext); + } + /** * Get the complete list of classes */ @@ -201,6 +224,8 @@ public class JadxWrapper { return decompiler.getPluginsContext(); } try (JadxDecompiler tmpDecompiler = new JadxDecompiler()) { + // TODO: override input file checks + tmpDecompiler.getArgs().setInputFile(FileUtils.createTempFile("tmp.txt").toFile()); tmpDecompiler.load(); return tmpDecompiler.getPluginsContext(); } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/CodeCacheMode.java b/jadx-gui/src/main/java/jadx/gui/cache/code/CodeCacheMode.java similarity index 95% rename from jadx-gui/src/main/java/jadx/gui/utils/codecache/CodeCacheMode.java rename to jadx-gui/src/main/java/jadx/gui/cache/code/CodeCacheMode.java index 80adc2592..ad55290a7 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/codecache/CodeCacheMode.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/code/CodeCacheMode.java @@ -1,4 +1,4 @@ -package jadx.gui.utils.codecache; +package jadx.gui.cache.code; import java.util.stream.Collectors; import java.util.stream.Stream; diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/CodeStringCache.java b/jadx-gui/src/main/java/jadx/gui/cache/code/CodeStringCache.java similarity index 98% rename from jadx-gui/src/main/java/jadx/gui/utils/codecache/CodeStringCache.java rename to jadx-gui/src/main/java/jadx/gui/cache/code/CodeStringCache.java index 550d6b5d5..dc43b835a 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/codecache/CodeStringCache.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/code/CodeStringCache.java @@ -1,4 +1,4 @@ -package jadx.gui.utils.codecache; +package jadx.gui.cache.code; import java.io.IOException; import java.util.Map; diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/FixedCodeCache.java b/jadx-gui/src/main/java/jadx/gui/cache/code/FixedCodeCache.java similarity index 93% rename from jadx-gui/src/main/java/jadx/gui/utils/codecache/FixedCodeCache.java rename to jadx-gui/src/main/java/jadx/gui/cache/code/FixedCodeCache.java index e414bef84..c219f7a5d 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/codecache/FixedCodeCache.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/code/FixedCodeCache.java @@ -1,4 +1,4 @@ -package jadx.gui.utils.codecache; +package jadx.gui.cache.code; import jadx.api.ICodeCache; import jadx.api.ICodeInfo; diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/BufferCodeCache.java b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/BufferCodeCache.java similarity index 98% rename from jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/BufferCodeCache.java rename to jadx-gui/src/main/java/jadx/gui/cache/code/disk/BufferCodeCache.java index 97388cc9e..2e12b8fab 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/BufferCodeCache.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/BufferCodeCache.java @@ -1,4 +1,4 @@ -package jadx.gui.utils.codecache.disk; +package jadx.gui.cache.code.disk; import java.io.IOException; import java.util.Deque; diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/CodeMetadataAdapter.java b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/CodeMetadataAdapter.java similarity index 88% rename from jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/CodeMetadataAdapter.java rename to jadx-gui/src/main/java/jadx/gui/cache/code/disk/CodeMetadataAdapter.java index 41d70df9e..192305c04 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/CodeMetadataAdapter.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/CodeMetadataAdapter.java @@ -1,4 +1,4 @@ -package jadx.gui.utils.codecache.disk; +package jadx.gui.cache.code.disk; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -23,10 +23,9 @@ import jadx.api.metadata.ICodeAnnotation; import jadx.api.metadata.ICodeMetadata; import jadx.core.dex.nodes.RootNode; import jadx.core.utils.files.FileUtils; -import jadx.gui.utils.codecache.disk.adapters.CodeAnnotationAdapter; +import jadx.gui.cache.code.disk.adapters.CodeAnnotationAdapter; +import jadx.gui.cache.code.disk.adapters.DataAdapterHelper; -import static jadx.gui.utils.codecache.disk.adapters.DataAdapterHelper.readUVInt; -import static jadx.gui.utils.codecache.disk.adapters.DataAdapterHelper.writeUVInt; import static java.nio.file.StandardOpenOption.CREATE; import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; import static java.nio.file.StandardOpenOption.WRITE; @@ -70,8 +69,8 @@ public class CodeMetadataAdapter { private void writeLines(DataOutput out, Map lines) throws IOException { out.writeInt(lines.size()); for (Map.Entry entry : lines.entrySet()) { - writeUVInt(out, entry.getKey()); - writeUVInt(out, entry.getValue()); + DataAdapterHelper.writeUVInt(out, entry.getKey()); + DataAdapterHelper.writeUVInt(out, entry.getValue()); } } @@ -82,8 +81,8 @@ public class CodeMetadataAdapter { } Map lines = new HashMap<>(size); for (int i = 0; i < size; i++) { - int key = readUVInt(in); - int value = readUVInt(in); + int key = DataAdapterHelper.readUVInt(in); + int value = DataAdapterHelper.readUVInt(in); lines.put(key, value); } return lines; @@ -92,7 +91,7 @@ public class CodeMetadataAdapter { private void writeAnnotations(DataOutputStream out, Map annotations) throws IOException { out.writeInt(annotations.size()); for (Map.Entry entry : annotations.entrySet()) { - writeUVInt(out, entry.getKey()); + DataAdapterHelper.writeUVInt(out, entry.getKey()); codeAnnotationAdapter.write(out, entry.getValue()); } } @@ -104,7 +103,7 @@ public class CodeMetadataAdapter { } Map map = new HashMap<>(size); for (int i = 0; i < size; i++) { - int pos = readUVInt(in); + int pos = DataAdapterHelper.readUVInt(in); ICodeAnnotation ann = codeAnnotationAdapter.read(in); if (ann != null) { map.put(pos, ann); diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/DiskCodeCache.java b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/DiskCodeCache.java similarity index 90% rename from jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/DiskCodeCache.java rename to jadx-gui/src/main/java/jadx/gui/cache/code/disk/DiskCodeCache.java index e7e8f1440..2d034f819 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/DiskCodeCache.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/DiskCodeCache.java @@ -1,8 +1,7 @@ -package jadx.gui.utils.codecache.disk; +package jadx.gui.cache.code.disk; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; @@ -13,9 +12,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.nio.file.attribute.FileTime; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -213,28 +210,7 @@ public class DiskCodeCache implements ICodeCache { return DATA_FORMAT_VERSION + ":" + Jadx.getVersion() + ":" + args.makeCodeArgsHash() - + ":" + buildInputsHash(inputFiles); - } - - /** - * Hash timestamps of all input files - */ - private String buildInputsHash(List inputs) { - try (ByteArrayOutputStream bout = new ByteArrayOutputStream(); - DataOutputStream data = new DataOutputStream(bout)) { - List inputPaths = Utils.collectionMap(inputs, File::toPath); - List inputFiles = FileUtils.expandDirs(inputPaths); - Collections.sort(inputFiles); - data.write(inputs.size()); - data.write(inputFiles.size()); - for (Path inputFile : inputFiles) { - FileTime modifiedTime = Files.getLastModifiedTime(inputFile); - data.writeLong(modifiedTime.toMillis()); - } - return FileUtils.md5Sum(bout.toByteArray()); - } catch (Exception e) { - throw new JadxRuntimeException("Failed to build hash for inputs", e); - } + + ":" + FileUtils.buildInputsHash(Utils.collectionMap(inputFiles, File::toPath)); } private int getClsId(String clsFullName) { diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/ArgTypeAdapter.java b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/ArgTypeAdapter.java similarity index 98% rename from jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/ArgTypeAdapter.java rename to jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/ArgTypeAdapter.java index 6aec68d1b..8e035ac8d 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/ArgTypeAdapter.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/ArgTypeAdapter.java @@ -1,4 +1,4 @@ -package jadx.gui.utils.codecache.disk.adapters; +package jadx.gui.cache.code.disk.adapters; import java.io.DataInput; import java.io.DataOutput; diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/ClassNodeAdapter.java b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/ClassNodeAdapter.java similarity index 92% rename from jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/ClassNodeAdapter.java rename to jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/ClassNodeAdapter.java index b8cb29efc..51f004482 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/ClassNodeAdapter.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/ClassNodeAdapter.java @@ -1,4 +1,4 @@ -package jadx.gui.utils.codecache.disk.adapters; +package jadx.gui.cache.code.disk.adapters; import java.io.DataInput; import java.io.DataOutput; diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/CodeAnnotationAdapter.java b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/CodeAnnotationAdapter.java similarity index 98% rename from jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/CodeAnnotationAdapter.java rename to jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/CodeAnnotationAdapter.java index 9386baf72..75e20d9de 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/CodeAnnotationAdapter.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/CodeAnnotationAdapter.java @@ -1,4 +1,4 @@ -package jadx.gui.utils.codecache.disk.adapters; +package jadx.gui.cache.code.disk.adapters; import java.io.DataInput; import java.io.DataOutput; diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/DataAdapter.java b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/DataAdapter.java similarity index 82% rename from jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/DataAdapter.java rename to jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/DataAdapter.java index b7ca3b93e..505895600 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/DataAdapter.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/DataAdapter.java @@ -1,4 +1,4 @@ -package jadx.gui.utils.codecache.disk.adapters; +package jadx.gui.cache.code.disk.adapters; import java.io.DataInput; import java.io.DataOutput; diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/DataAdapterHelper.java b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/DataAdapterHelper.java similarity index 96% rename from jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/DataAdapterHelper.java rename to jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/DataAdapterHelper.java index aab5d55e3..33f38b590 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/DataAdapterHelper.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/DataAdapterHelper.java @@ -1,4 +1,4 @@ -package jadx.gui.utils.codecache.disk.adapters; +package jadx.gui.cache.code.disk.adapters; import java.io.DataInput; import java.io.DataOutput; diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/FieldNodeAdapter.java b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/FieldNodeAdapter.java similarity index 95% rename from jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/FieldNodeAdapter.java rename to jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/FieldNodeAdapter.java index b4e1b0a43..f47274cd4 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/FieldNodeAdapter.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/FieldNodeAdapter.java @@ -1,4 +1,4 @@ -package jadx.gui.utils.codecache.disk.adapters; +package jadx.gui.cache.code.disk.adapters; import java.io.DataInput; import java.io.DataOutput; diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/InsnCodeOffsetAdapter.java b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/InsnCodeOffsetAdapter.java similarity index 92% rename from jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/InsnCodeOffsetAdapter.java rename to jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/InsnCodeOffsetAdapter.java index b317038a5..bd44d43d5 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/InsnCodeOffsetAdapter.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/InsnCodeOffsetAdapter.java @@ -1,4 +1,4 @@ -package jadx.gui.utils.codecache.disk.adapters; +package jadx.gui.cache.code.disk.adapters; import java.io.DataInput; import java.io.DataOutput; diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/MethodNodeAdapter.java b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/MethodNodeAdapter.java similarity index 64% rename from jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/MethodNodeAdapter.java rename to jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/MethodNodeAdapter.java index c2f265ef8..a9fa822a8 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/MethodNodeAdapter.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/MethodNodeAdapter.java @@ -1,11 +1,10 @@ -package jadx.gui.utils.codecache.disk.adapters; +package jadx.gui.cache.code.disk.adapters; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import jadx.core.dex.info.MethodInfo; -import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; @@ -27,14 +26,6 @@ public class MethodNodeAdapter implements DataAdapter { public MethodNode read(DataInput in) throws IOException { String cls = in.readUTF(); String sign = in.readUTF(); - ClassNode clsNode = root.resolveClass(cls); - if (clsNode == null) { - throw new RuntimeException("Class not found: " + cls); - } - MethodNode methodNode = clsNode.searchMethodByShortId(sign); - if (methodNode == null) { - throw new RuntimeException("Method not found: " + cls + "." + sign); - } - return methodNode; + return root.resolveDirectMethod(cls, sign); } } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/NodeDeclareRefAdapter.java b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/NodeDeclareRefAdapter.java similarity index 95% rename from jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/NodeDeclareRefAdapter.java rename to jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/NodeDeclareRefAdapter.java index 118315e46..a1e8dccd0 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/NodeDeclareRefAdapter.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/NodeDeclareRefAdapter.java @@ -1,4 +1,4 @@ -package jadx.gui.utils.codecache.disk.adapters; +package jadx.gui.cache.code.disk.adapters; import java.io.DataInput; import java.io.DataOutput; diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/NodeEndAdapter.java b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/NodeEndAdapter.java similarity index 88% rename from jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/NodeEndAdapter.java rename to jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/NodeEndAdapter.java index 9ae129f5d..f47f9eac0 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/NodeEndAdapter.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/NodeEndAdapter.java @@ -1,4 +1,4 @@ -package jadx.gui.utils.codecache.disk.adapters; +package jadx.gui.cache.code.disk.adapters; import java.io.DataInput; import java.io.DataOutput; diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/VarNodeAdapter.java b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/VarNodeAdapter.java similarity index 72% rename from jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/VarNodeAdapter.java rename to jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/VarNodeAdapter.java index ee4dd2dc3..4d950591d 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/VarNodeAdapter.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/VarNodeAdapter.java @@ -1,4 +1,4 @@ -package jadx.gui.utils.codecache.disk.adapters; +package jadx.gui.cache.code.disk.adapters; import java.io.DataInput; import java.io.DataOutput; @@ -8,10 +8,10 @@ import jadx.api.metadata.annotations.VarNode; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.MethodNode; -import static jadx.gui.utils.codecache.disk.adapters.DataAdapterHelper.readNullableUTF; -import static jadx.gui.utils.codecache.disk.adapters.DataAdapterHelper.readUVInt; -import static jadx.gui.utils.codecache.disk.adapters.DataAdapterHelper.writeNullableUTF; -import static jadx.gui.utils.codecache.disk.adapters.DataAdapterHelper.writeUVInt; +import static jadx.gui.cache.code.disk.adapters.DataAdapterHelper.readNullableUTF; +import static jadx.gui.cache.code.disk.adapters.DataAdapterHelper.readUVInt; +import static jadx.gui.cache.code.disk.adapters.DataAdapterHelper.writeNullableUTF; +import static jadx.gui.cache.code.disk.adapters.DataAdapterHelper.writeUVInt; public class VarNodeAdapter implements DataAdapter { private final MethodNodeAdapter mthAdapter; diff --git a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/VarRefAdapter.java b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/VarRefAdapter.java similarity index 92% rename from jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/VarRefAdapter.java rename to jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/VarRefAdapter.java index 507df2574..cfe2e4870 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/codecache/disk/adapters/VarRefAdapter.java +++ b/jadx-gui/src/main/java/jadx/gui/cache/code/disk/adapters/VarRefAdapter.java @@ -1,4 +1,4 @@ -package jadx.gui.utils.codecache.disk.adapters; +package jadx.gui.cache.code.disk.adapters; import java.io.DataInput; import java.io.DataOutput; diff --git a/jadx-gui/src/main/java/jadx/gui/cache/usage/ClsUsageData.java b/jadx-gui/src/main/java/jadx/gui/cache/usage/ClsUsageData.java new file mode 100644 index 000000000..31cc412ac --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/cache/usage/ClsUsageData.java @@ -0,0 +1,56 @@ +package jadx.gui.cache.usage; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +final class ClsUsageData { + private final String rawName; + + private List clsDeps; + private List clsUsage; + private List clsUseInMth; + + private final Map fldUsage = new HashMap<>(); + private final Map mthUsage = new HashMap<>(); + + ClsUsageData(String rawName) { + this.rawName = rawName; + } + + public String getRawName() { + return rawName; + } + + public List getClsDeps() { + return clsDeps; + } + + public void setClsDeps(List clsDeps) { + this.clsDeps = clsDeps; + } + + public List getClsUsage() { + return clsUsage; + } + + public void setClsUsage(List clsUsage) { + this.clsUsage = clsUsage; + } + + public List getClsUseInMth() { + return clsUseInMth; + } + + public void setClsUseInMth(List clsUseInMth) { + this.clsUseInMth = clsUseInMth; + } + + public Map getFldUsage() { + return fldUsage; + } + + public Map getMthUsage() { + return mthUsage; + } +} 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 new file mode 100644 index 000000000..b9a8c7ed2 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/cache/usage/CollectUsageData.java @@ -0,0 +1,55 @@ +package jadx.gui.cache.usage; + +import java.util.List; + +import jadx.api.usage.IUsageInfoVisitor; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.utils.Utils; + +final class CollectUsageData implements IUsageInfoVisitor { + private final RawUsageData data; + + public CollectUsageData(RawUsageData usageData) { + data = usageData; + } + + @Override + public void visitClassDeps(ClassNode cls, List deps) { + data.getClassData(cls).setClsDeps(clsNodesRef(deps)); + } + + @Override + public void visitClassUsage(ClassNode cls, List usage) { + data.getClassData(cls).setClsUsage(clsNodesRef(usage)); + } + + @Override + public void visitClassUseInMethods(ClassNode cls, List methods) { + data.getClassData(cls).setClsUseInMth(mthNodesRef(methods)); + } + + @Override + public void visitFieldsUsage(FieldNode fld, List methods) { + data.getFieldData(fld).setUsage(mthNodesRef(methods)); + } + + @Override + public void visitMethodsUsage(MethodNode mth, List methods) { + data.getMethodData(mth).setUsage(mthNodesRef(methods)); + } + + @Override + public void visitComplete() { + data.collectClassesWithoutData(); + } + + private List clsNodesRef(List usage) { + return Utils.collectionMap(usage, ClassNode::getRawName); + } + + private List mthNodesRef(List methods) { + return Utils.collectionMap(methods, m -> data.getMethodData(m).getMthRef()); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/cache/usage/FldRef.java b/jadx-gui/src/main/java/jadx/gui/cache/usage/FldRef.java new file mode 100644 index 000000000..d91688067 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/cache/usage/FldRef.java @@ -0,0 +1,19 @@ +package jadx.gui.cache.usage; + +final class FldRef { + private final String cls; + private final String shortId; + + FldRef(String cls, String shortId) { + this.cls = cls; + this.shortId = shortId; + } + + public String getCls() { + return cls; + } + + public String getShortId() { + return shortId; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/cache/usage/FldUsageData.java b/jadx-gui/src/main/java/jadx/gui/cache/usage/FldUsageData.java new file mode 100644 index 000000000..916c2013a --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/cache/usage/FldUsageData.java @@ -0,0 +1,24 @@ +package jadx.gui.cache.usage; + +import java.util.List; + +final class FldUsageData { + private final FldRef fldRef; + private List usage; + + public FldUsageData(FldRef fldRef) { + this.fldRef = fldRef; + } + + public FldRef getFldRef() { + return fldRef; + } + + public List getUsage() { + return usage; + } + + public void setUsage(List usage) { + this.usage = usage; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/cache/usage/MthRef.java b/jadx-gui/src/main/java/jadx/gui/cache/usage/MthRef.java new file mode 100644 index 000000000..86fecebdd --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/cache/usage/MthRef.java @@ -0,0 +1,37 @@ +package jadx.gui.cache.usage; + +final class MthRef { + private final String cls; + private final String shortId; + + MthRef(String cls, String shortId) { + this.cls = cls; + this.shortId = shortId; + } + + public String getCls() { + return cls; + } + + public String getShortId() { + return shortId; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof MthRef)) { + return false; + } + MthRef other = (MthRef) o; + return cls.equals(other.cls) + && shortId.equals(other.shortId); + } + + @Override + public int hashCode() { + return 31 * cls.hashCode() + shortId.hashCode(); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/cache/usage/MthUsageData.java b/jadx-gui/src/main/java/jadx/gui/cache/usage/MthUsageData.java new file mode 100644 index 000000000..10dbaf85f --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/cache/usage/MthUsageData.java @@ -0,0 +1,24 @@ +package jadx.gui.cache.usage; + +import java.util.List; + +final class MthUsageData { + private final MthRef mthRef; + private List usage; + + public MthUsageData(MthRef mthRef) { + this.mthRef = mthRef; + } + + public MthRef getMthRef() { + return mthRef; + } + + public List getUsage() { + return usage; + } + + public void setUsage(List usage) { + this.usage = usage; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/cache/usage/RawUsageData.java b/jadx-gui/src/main/java/jadx/gui/cache/usage/RawUsageData.java new file mode 100644 index 000000000..1260d7a7d --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/cache/usage/RawUsageData.java @@ -0,0 +1,66 @@ +package jadx.gui.cache.usage; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; + +class RawUsageData { + + private final Map clsMap = new HashMap<>(); + private List classesWithoutData = Collections.emptyList(); + + public Map getClsMap() { + return clsMap; + } + + public List getClassesWithoutData() { + return classesWithoutData; + } + + public ClsUsageData getClassData(ClassNode cls) { + return getClassData(cls.getRawName()); + } + + public ClsUsageData getClassData(String clsRawName) { + return clsMap.computeIfAbsent(clsRawName, ClsUsageData::new); + } + + public MthUsageData getMethodData(MethodNode mth) { + ClassNode parentClass = mth.getParentClass(); + String shortId = mth.getMethodInfo().getShortId(); + return getClassData(parentClass).getMthUsage().computeIfAbsent(shortId, + m -> new MthUsageData(new MthRef(parentClass.getRawName(), shortId))); + } + + public FldUsageData getFieldData(FieldNode fld) { + ClassNode parentClass = fld.getParentClass(); + String shortId = fld.getFieldInfo().getShortId(); + return getClassData(parentClass).getFldUsage().computeIfAbsent(shortId, + m -> new FldUsageData(new FldRef(parentClass.getRawName(), shortId))); + } + + public void collectClassesWithoutData() { + Set allClasses = new HashSet<>(clsMap.size() * 2); + for (ClsUsageData usageData : clsMap.values()) { + List deps = usageData.getClsDeps(); + if (deps != null) { + allClasses.addAll(deps); + } + List usage = usageData.getClsUsage(); + if (usage != null) { + allClasses.addAll(usage); + } + } + allClasses.removeAll(clsMap.keySet()); + classesWithoutData = new ArrayList<>(allClasses); + Collections.sort(classesWithoutData); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/cache/usage/UsageCacheMode.java b/jadx-gui/src/main/java/jadx/gui/cache/usage/UsageCacheMode.java new file mode 100644 index 000000000..0c13facd0 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/cache/usage/UsageCacheMode.java @@ -0,0 +1,7 @@ +package jadx.gui.cache.usage; + +public enum UsageCacheMode { + NONE, + MEMORY, + DISK +} 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 new file mode 100644 index 000000000..f8f97e50d --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/cache/usage/UsageData.java @@ -0,0 +1,84 @@ +package jadx.gui.cache.usage; + +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.usage.IUsageInfoData; +import jadx.api.usage.IUsageInfoVisitor; +import jadx.core.dex.nodes.ClassNode; +import jadx.core.dex.nodes.FieldNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; +import jadx.core.utils.Utils; +import jadx.core.utils.exceptions.JadxRuntimeException; + +class UsageData implements IUsageInfoData { + private static final Logger LOG = LoggerFactory.getLogger(UsageData.class); + + private final RootNode root; + private final RawUsageData rawUsageData; + + public UsageData(RootNode root, RawUsageData rawUsageData) { + this.root = root; + this.rawUsageData = rawUsageData; + } + + @Override + public void apply() { + for (ClsUsageData clsUsageData : rawUsageData.getClsMap().values()) { + String clsRawName = clsUsageData.getRawName(); + ClassNode cls = root.resolveClass(clsRawName); + if (cls == null) { + throw new JadxRuntimeException("Failed to resolve class: " + clsRawName); + } + applyForClass(clsUsageData, clsRawName, cls); + } + } + + @Override + public void applyForClass(ClassNode cls) { + String clsRawName = cls.getRawName(); + ClsUsageData clsUsageData = rawUsageData.getClsMap().get(clsRawName); + if (clsUsageData == null) { + LOG.debug("No usage data for class: {}", clsRawName); + return; + } + applyForClass(clsUsageData, clsRawName, cls); + } + + private void applyForClass(ClsUsageData clsUsageData, String clsRawName, ClassNode cls) { + cls.setDependencies(resolveClsList(clsUsageData.getClsDeps())); + cls.setUseIn(resolveClsList(clsUsageData.getClsUsage())); + cls.setUseInMth(resolveMthList(clsUsageData.getClsUseInMth())); + for (Map.Entry entry : clsUsageData.getMthUsage().entrySet()) { + MethodNode mth = cls.searchMethodByShortId(entry.getKey()); + if (mth == null) { + throw new JadxRuntimeException("Method not found: " + clsRawName + "." + entry.getKey()); + } + mth.setUseIn(resolveMthList(entry.getValue().getUsage())); + } + for (Map.Entry entry : clsUsageData.getFldUsage().entrySet()) { + FieldNode fld = cls.searchFieldByShortId(entry.getKey()); + if (fld == null) { + throw new JadxRuntimeException("Field not found: " + clsRawName + "." + entry.getKey()); + } + fld.setUseIn(resolveMthList(entry.getValue().getUsage())); + } + } + + @Override + public void visitUsageData(IUsageInfoVisitor visitor) { + throw new JadxRuntimeException("Not implemented"); + } + + private List resolveClsList(List clsList) { + return Utils.collectionMap(clsList, root::resolveClass); + } + + private List resolveMthList(List mthRefList) { + return Utils.collectionMap(mthRefList, m -> root.resolveDirectMethod(m.getCls(), m.getShortId())); + } +} 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 new file mode 100644 index 000000000..c0ead18c3 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/cache/usage/UsageFileAdapter.java @@ -0,0 +1,258 @@ +package jadx.gui.cache.usage; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.api.usage.IUsageInfoData; +import jadx.core.utils.Utils; +import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.core.utils.files.FileUtils; +import jadx.gui.cache.code.disk.adapters.DataAdapterHelper; + +import static java.nio.file.StandardOpenOption.CREATE; +import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; +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 = 1; + private static final byte[] JADX_USAGE_HEADER = "jadx.usage".getBytes(StandardCharsets.US_ASCII); + + public static synchronized @Nullable RawUsageData load(Path usageFile, List inputs) { + if (!Files.isRegularFile(usageFile)) { + return null; + } + long start = System.currentTimeMillis(); + try (DataInputStream in = new DataInputStream(new BufferedInputStream(Files.newInputStream(usageFile)))) { + in.skipBytes(JADX_USAGE_HEADER.length); + int dataVersion = in.readInt(); + if (dataVersion != USAGE_DATA_VERSION) { + LOG.debug("Found old usage data format"); + FileUtils.deleteFileIfExists(usageFile); + return null; + } + String inputsHash = buildInputsHash(inputs); + String fileInputsHash = in.readUTF(); + if (!inputsHash.equals(fileInputsHash)) { + LOG.debug("Found usage data with different inputs hash"); + FileUtils.deleteFileIfExists(usageFile); + return null; + } + RawUsageData data = readData(in); + if (LOG.isDebugEnabled()) { + LOG.debug("Loaded usage data from disk cache, classes count: {}, time: {}ms, file: {}", + data.getClsMap().size(), System.currentTimeMillis() - start, usageFile); + } + return data; + } catch (Exception e) { + try { + FileUtils.deleteFileIfExists(usageFile); + } catch (IOException ex) { + // ignore + } + LOG.error("Failed to load usage data file", e); + return null; + } + } + + public static synchronized void save(IUsageInfoData data, Path usageFile, List inputs) { + long start = System.currentTimeMillis(); + FileUtils.makeDirsForFile(usageFile); + String inputsHash = buildInputsHash(inputs); + RawUsageData usageData = new RawUsageData(); + data.visitUsageData(new CollectUsageData(usageData)); + try (OutputStream fileOutput = Files.newOutputStream(usageFile, WRITE, CREATE, TRUNCATE_EXISTING); + DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fileOutput))) { + out.write(JADX_USAGE_HEADER); + out.writeInt(USAGE_DATA_VERSION); + out.writeUTF(inputsHash); + writeData(out, usageData); + } catch (Exception e) { + LOG.error("Failed to save usage data file", e); + try { + FileUtils.deleteFileIfExists(usageFile); + } catch (IOException ex) { + LOG.error("Failed to delete usage data file: {}", usageFile, ex); + } + } + if (LOG.isDebugEnabled()) { + LOG.debug("Usage data saved, time: {}ms, file: {}", System.currentTimeMillis() - start, usageFile); + } + } + + private static RawUsageData readData(DataInputStream in) throws IOException { + RawUsageData data = new RawUsageData(); + int clsCount = readUVInt(in); + int clsWithoutDataCount = readUVInt(in); + + String[] clsNames = new String[clsCount + clsWithoutDataCount]; + ClsUsageData[] classes = new ClsUsageData[clsCount]; + int c = 0; + for (int i = 0; i < clsCount; i++) { + String clsRawName = in.readUTF(); + classes[i] = data.getClassData(clsRawName); + clsNames[c++] = clsRawName; + } + for (int i = 0; i < clsWithoutDataCount; i++) { + clsNames[c++] = in.readUTF(); + } + int mthCount = readUVInt(in); + MthRef[] methods = new MthRef[mthCount]; + for (int i = 0; i < mthCount; i++) { + int clsId = readUVInt(in); + String mthShortId = in.readUTF(); + ClsUsageData cls = classes[clsId]; + MthRef mthRef = new MthRef(cls.getRawName(), mthShortId); + cls.getMthUsage().put(mthShortId, new MthUsageData(mthRef)); + methods[i] = mthRef; + } + for (int i = 0; i < clsCount; i++) { + ClsUsageData cls = data.getClassData(clsNames[i]); + cls.setClsDeps(readClsList(in, clsNames)); + cls.setClsUsage(readClsList(in, clsNames)); + cls.setClsUseInMth(readMthList(in, methods)); + + int mCount = readUVInt(in); + for (int m = 0; m < mCount; m++) { + MthRef mthRef = methods[readUVInt(in)]; + cls.getMthUsage().get(mthRef.getShortId()) + .setUsage(readMthList(in, methods)); + } + int fCount = readUVInt(in); + for (int f = 0; f < fCount; f++) { + String fldShortId = in.readUTF(); + cls.getFldUsage().computeIfAbsent(fldShortId, + fldId -> new FldUsageData(new FldRef(cls.getRawName(), fldId))) + .setUsage(readMthList(in, methods)); + } + } + return data; + } + + private static void writeData(DataOutputStream out, RawUsageData usageData) throws IOException { + Map clsMap = new HashMap<>(); + Map mthMap = new HashMap<>(); + Map clsDataMap = usageData.getClsMap(); + List classes = new ArrayList<>(clsDataMap.keySet()); + Collections.sort(classes); + List classesWithoutData = usageData.getClassesWithoutData(); + + writeUVInt(out, classes.size()); + writeUVInt(out, classesWithoutData.size()); + int i = 0; + for (String cls : classes) { + out.writeUTF(cls); + clsMap.put(cls, i++); + } + for (String cls : classesWithoutData) { + out.writeUTF(cls); + clsMap.put(cls, i++); + } + List methods = clsDataMap.values().stream() + .flatMap(c -> c.getMthUsage().values().stream()) + .map(MthUsageData::getMthRef) + .collect(Collectors.toList()); + writeUVInt(out, methods.size()); + int j = 0; + for (MthRef mth : methods) { + writeUVInt(out, clsMap.get(mth.getCls())); + out.writeUTF(mth.getShortId()); + mthMap.put(mth, j++); + } + for (String cls : classes) { + ClsUsageData clsData = clsDataMap.get(cls); + writeClsList(out, clsMap, clsData.getClsDeps()); + writeClsList(out, clsMap, clsData.getClsUsage()); + writeMthList(out, mthMap, clsData.getClsUseInMth()); + + writeUVInt(out, clsData.getMthUsage().size()); + for (MthUsageData mthData : clsData.getMthUsage().values()) { + writeUVInt(out, mthMap.get(mthData.getMthRef())); + writeMthList(out, mthMap, mthData.getUsage()); + } + + writeUVInt(out, clsData.getFldUsage().size()); + for (FldUsageData fldData : clsData.getFldUsage().values()) { + out.writeUTF(fldData.getFldRef().getShortId()); + writeMthList(out, mthMap, fldData.getUsage()); + } + } + } + + private static List readClsList(DataInputStream in, String[] classes) throws IOException { + int count = readUVInt(in); + if (count == 0) { + return Collections.emptyList(); + } + List list = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + list.add(classes[readUVInt(in)]); + } + return list; + } + + private static void writeClsList(DataOutputStream out, Map clsMap, List clsList) throws IOException { + if (Utils.isEmpty(clsList)) { + writeUVInt(out, 0); + return; + } + writeUVInt(out, clsList.size()); + for (String cls : clsList) { + Integer clsId = clsMap.get(cls); + if (clsId == null) { + throw new JadxRuntimeException("Unknown class in usage: " + cls); + } + writeUVInt(out, clsId); + } + } + + private static List readMthList(DataInputStream in, MthRef[] methods) throws IOException { + int count = readUVInt(in); + if (count == 0) { + return Collections.emptyList(); + } + List list = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + list.add(methods[readUVInt(in)]); + } + return list; + } + + private static void writeMthList(DataOutputStream out, Map mthMap, List mthList) throws IOException { + if (Utils.isEmpty(mthList)) { + writeUVInt(out, 0); + return; + } + writeUVInt(out, mthList.size()); + for (MthRef mth : mthList) { + writeUVInt(out, mthMap.get(mth)); + } + } + + private static String buildInputsHash(List inputs) { + List paths = inputs.stream() + .filter(f -> !f.getName().endsWith(".jadx.kts")) + .map(File::toPath) + .collect(Collectors.toList()); + return FileUtils.buildInputsHash(paths); + } +} 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 new file mode 100644 index 000000000..f2206287f --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/cache/usage/UsageInfoCache.java @@ -0,0 +1,58 @@ +package jadx.gui.cache.usage; + +import java.io.File; +import java.nio.file.Path; +import java.util.List; + +import org.jetbrains.annotations.Nullable; + +import jadx.api.usage.IUsageInfoCache; +import jadx.api.usage.IUsageInfoData; +import jadx.api.usage.impl.InMemoryUsageInfoCache; +import jadx.core.dex.nodes.RootNode; + +public class UsageInfoCache implements IUsageInfoCache { + + private static final Object LOAD_DATA_SYNC = new Object(); + + private final Path usageFile; + private final List inputs; + private final InMemoryUsageInfoCache memCache = new InMemoryUsageInfoCache(); + private @Nullable RawUsageData rawUsageData; + + public UsageInfoCache(Path cacheDir, List inputFiles) { + usageFile = cacheDir.resolve("usage"); + inputs = inputFiles; + } + + @Override + public @Nullable IUsageInfoData get(RootNode root) { + IUsageInfoData memData = memCache.get(root); + if (memData != null) { + return memData; + } + synchronized (LOAD_DATA_SYNC) { + if (rawUsageData == null) { + rawUsageData = UsageFileAdapter.load(usageFile, inputs); + } + if (rawUsageData != null) { + UsageData data = new UsageData(root, rawUsageData); + memCache.set(root, data); + return data; + } + } + return null; + } + + @Override + public void set(RootNode root, IUsageInfoData data) { + memCache.set(root, data); + UsageFileAdapter.save(data, usageFile, inputs); + } + + @Override + public void close() { + rawUsageData = null; + memCache.close(); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/ExportTask.java b/jadx-gui/src/main/java/jadx/gui/jobs/ExportTask.java index 7248f3eb9..70b579618 100644 --- a/jadx-gui/src/main/java/jadx/gui/jobs/ExportTask.java +++ b/jadx-gui/src/main/java/jadx/gui/jobs/ExportTask.java @@ -7,9 +7,9 @@ import javax.swing.JOptionPane; import jadx.api.ICodeCache; import jadx.gui.JadxWrapper; +import jadx.gui.cache.code.FixedCodeCache; import jadx.gui.ui.MainWindow; import jadx.gui.utils.NLS; -import jadx.gui.utils.codecache.FixedCodeCache; public class ExportTask extends CancelableBackgroundTask { diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index 4827726cc..57f92521c 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -34,13 +34,14 @@ import jadx.api.args.ResourceNameSource; import jadx.api.args.UserRenamesMappingsMode; import jadx.cli.JadxCLIArgs; import jadx.cli.LogHelper; +import jadx.gui.cache.code.CodeCacheMode; +import jadx.gui.cache.usage.UsageCacheMode; import jadx.gui.ui.MainWindow; import jadx.gui.ui.codearea.EditorTheme; import jadx.gui.utils.FontUtils; import jadx.gui.utils.LafManager; import jadx.gui.utils.LangLocale; import jadx.gui.utils.NLS; -import jadx.gui.utils.codecache.CodeCacheMode; public class JadxSettings extends JadxCLIArgs { private static final Logger LOG = LoggerFactory.getLogger(JadxSettings.class); @@ -96,6 +97,7 @@ public class JadxSettings extends JadxCLIArgs { private String adbDialogPort = "5037"; private CodeCacheMode codeCacheMode = CodeCacheMode.DISK_WITH_CACHE; + private UsageCacheMode usageCacheMode = UsageCacheMode.DISK; private boolean jumpOnDoubleClick = true; /** @@ -668,6 +670,14 @@ public class JadxSettings extends JadxCLIArgs { this.codeCacheMode = codeCacheMode; } + public UsageCacheMode getUsageCacheMode() { + return usageCacheMode; + } + + public void setUsageCacheMode(UsageCacheMode usageCacheMode) { + this.usageCacheMode = usageCacheMode; + } + public boolean isJumpOnDoubleClick() { return jumpOnDoubleClick; } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java index 8f5543205..d79ea4967 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java @@ -66,6 +66,8 @@ import jadx.api.impl.plugins.PluginsContext; import jadx.api.plugins.JadxPlugin; import jadx.api.plugins.options.JadxPluginOptions; import jadx.api.plugins.options.OptionDescription; +import jadx.gui.cache.code.CodeCacheMode; +import jadx.gui.cache.usage.UsageCacheMode; import jadx.gui.ui.MainWindow; import jadx.gui.ui.codearea.EditorTheme; import jadx.gui.utils.FontUtils; @@ -73,7 +75,6 @@ import jadx.gui.utils.LafManager; import jadx.gui.utils.LangLocale; import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; -import jadx.gui.utils.codecache.CodeCacheMode; import jadx.gui.utils.ui.DocumentUpdateListener; public class JadxSettingsWindow extends JDialog { @@ -450,6 +451,13 @@ public class JadxSettingsWindow extends JDialog { }); String codeCacheModeToolTip = CodeCacheMode.buildToolTip(); + JComboBox usageCacheModeComboBox = new JComboBox<>(UsageCacheMode.values()); + usageCacheModeComboBox.setSelectedItem(settings.getUsageCacheMode()); + usageCacheModeComboBox.addActionListener(e -> { + settings.setUsageCacheMode((UsageCacheMode) usageCacheModeComboBox.getSelectedItem()); + needReload(); + }); + JCheckBox showInconsistentCode = new JCheckBox(); showInconsistentCode.setSelected(settings.isShowInconsistentCode()); showInconsistentCode.addItemListener(e -> { @@ -583,6 +591,7 @@ public class JadxSettingsWindow extends JDialog { other.addRow(NLS.str("preferences.start_jobs"), autoStartJobs); other.addRow(NLS.str("preferences.decompilationMode"), decompilationModeComboBox); other.addRow(NLS.str("preferences.codeCacheMode"), codeCacheModeToolTip, codeCacheModeComboBox); + other.addRow(NLS.str("preferences.usageCacheMode"), usageCacheModeComboBox); other.addRow(NLS.str("preferences.showInconsistentCode"), showInconsistentCode); other.addRow(NLS.str("preferences.escapeUnicode"), escapeUnicode); other.addRow(NLS.str("preferences.replaceConsts"), replaceConsts); diff --git a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties index 55552833b..7dd143b0e 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -161,6 +161,7 @@ preferences.check_for_updates=Nach Updates beim Start suchen preferences.useDx=dx/d8 zur Konvertierung von Java Bytecode verwenden preferences.decompilationMode=Dekompilierungsmodus preferences.codeCacheMode=Cache-Code-Modus +#preferences.usageCacheMode=Usage data cache mode preferences.showInconsistentCode=Inkonsistenten Code anzeigen preferences.escapeUnicode=Unicodezeichen escapen preferences.replaceConsts=Konstanten ersetzen diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index cdbd555d1..6c85368ad 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -161,6 +161,7 @@ preferences.check_for_updates=Check for updates on startup preferences.useDx=Use dx/d8 to convert java bytecode preferences.decompilationMode=Decompilation mode preferences.codeCacheMode=Code cache mode +preferences.usageCacheMode=Usage data cache mode preferences.showInconsistentCode=Show inconsistent code preferences.escapeUnicode=Escape unicode preferences.replaceConsts=Replace constants diff --git a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties index d010f515d..7b60464b8 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -161,6 +161,7 @@ preferences.check_for_updates=Buscar actualizaciones al iniciar #preferences.useDx=Use dx/d8 to convert java bytecode #preferences.decompilationMode=Decompilation mode #preferences.codeCacheMode=Code cache mode +#preferences.usageCacheMode=Usage data cache mode preferences.showInconsistentCode=Mostrar código inconsistente preferences.escapeUnicode=Escape unicode preferences.replaceConsts=Reemplazar constantes diff --git a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties index 48e2797cb..8b6490b5b 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -161,6 +161,7 @@ preferences.check_for_updates=시작시 업데이트 확인 preferences.useDx=dx/d8을 사용하여 Java 바이트 코드 변환 preferences.decompilationMode=디컴파일 모드 preferences.codeCacheMode=코드 캐시 모드 +#preferences.usageCacheMode=Usage data cache mode preferences.showInconsistentCode=디컴파일 안된 코드 표시 preferences.escapeUnicode=유니코드 이스케이프 preferences.replaceConsts=상수 바꾸기 diff --git a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties index b9117f6c6..e00e56834 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties @@ -161,6 +161,7 @@ preferences.check_for_updates=Verificar por atualizações ao inicializar preferences.useDx=Usar dx/d8 para converter bytecode Java preferences.decompilationMode=Modo de descompilação preferences.codeCacheMode=Modo de cachê do código +#preferences.usageCacheMode=Usage data cache mode preferences.showInconsistentCode=Mostrar código inconsistent preferences.escapeUnicode=Escapar unicode preferences.replaceConsts=Substituir constantes diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties index d7a59eb95..5038107ca 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -161,6 +161,7 @@ preferences.check_for_updates=启动时检查更新 preferences.useDx=使用 dx/d8 来转换java字节码 preferences.decompilationMode=反编译模式 preferences.codeCacheMode=代码缓存模式 +#preferences.usageCacheMode=Usage data cache mode preferences.showInconsistentCode=显示不一致的代码 preferences.escapeUnicode=Unicode 字符转义 preferences.replaceConsts=替换常量 diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties index fb6c40458..c00b2ceae 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -161,6 +161,7 @@ preferences.check_for_updates=啟動時檢查更新 preferences.useDx=使用 dx/d8 來轉換 Java 位元組碼 preferences.decompilationMode=反編譯模式 preferences.codeCacheMode=程式碼快取模式 +#preferences.usageCacheMode=Usage data cache mode preferences.showInconsistentCode=顯示不一致的程式碼 preferences.escapeUnicode=Unicode 逸出 preferences.replaceConsts=替換常數 diff --git a/jadx-gui/src/test/java/jadx/gui/utils/codecache/DiskCodeCacheTest.java b/jadx-gui/src/test/java/jadx/gui/utils/cache/code/DiskCodeCacheTest.java similarity index 94% rename from jadx-gui/src/test/java/jadx/gui/utils/codecache/DiskCodeCacheTest.java rename to jadx-gui/src/test/java/jadx/gui/utils/cache/code/DiskCodeCacheTest.java index a41af1d65..6fb586bf4 100644 --- a/jadx-gui/src/test/java/jadx/gui/utils/codecache/DiskCodeCacheTest.java +++ b/jadx-gui/src/test/java/jadx/gui/utils/cache/code/DiskCodeCacheTest.java @@ -1,4 +1,4 @@ -package jadx.gui.utils.codecache; +package jadx.gui.utils.cache.code; import java.io.IOException; import java.nio.file.Path; @@ -11,7 +11,7 @@ import org.slf4j.LoggerFactory; import jadx.api.ICodeInfo; import jadx.api.impl.NoOpCodeCache; import jadx.core.dex.nodes.ClassNode; -import jadx.gui.utils.codecache.disk.DiskCodeCache; +import jadx.gui.cache.code.disk.DiskCodeCache; import jadx.tests.api.IntegrationTest; import static org.assertj.core.api.Assertions.assertThat; diff --git a/jadx-gui/src/test/java/jadx/gui/utils/codecache/disk/adapters/DataAdapterHelperTest.java b/jadx-gui/src/test/java/jadx/gui/utils/cache/code/disk/adapters/DataAdapterHelperTest.java similarity index 90% rename from jadx-gui/src/test/java/jadx/gui/utils/codecache/disk/adapters/DataAdapterHelperTest.java rename to jadx-gui/src/test/java/jadx/gui/utils/cache/code/disk/adapters/DataAdapterHelperTest.java index 5d8f48ec8..83d28c068 100644 --- a/jadx-gui/src/test/java/jadx/gui/utils/codecache/disk/adapters/DataAdapterHelperTest.java +++ b/jadx-gui/src/test/java/jadx/gui/utils/cache/code/disk/adapters/DataAdapterHelperTest.java @@ -1,4 +1,4 @@ -package jadx.gui.utils.codecache.disk.adapters; +package jadx.gui.utils.cache.code.disk.adapters; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -9,6 +9,8 @@ import java.io.IOException; import org.junit.jupiter.api.Test; +import jadx.gui.cache.code.disk.adapters.DataAdapterHelper; + import static org.assertj.core.api.Assertions.assertThat; class DataAdapterHelperTest {