From 918585968d5f613636c3b2253c465a7cbc8adca0 Mon Sep 17 00:00:00 2001 From: Skylot Date: Sat, 13 Nov 2021 14:53:51 +0000 Subject: [PATCH] perf(gui): on rename unload dependent classes instead recompile --- .../main/java/jadx/api/JadxDecompiler.java | 14 +++++++- .../src/main/java/jadx/api/JavaClass.java | 8 ++++- .../src/main/java/jadx/core/ProcessClass.java | 4 +++ .../java/jadx/core/dex/attributes/AFlag.java | 1 + .../java/jadx/core/dex/nodes/ClassNode.java | 34 +++++++++++++++--- .../main/java/jadx/gui/jobs/IndexService.java | 9 +++++ .../main/java/jadx/gui/treemodel/JClass.java | 5 +++ .../java/jadx/gui/ui/dialog/RenameDialog.java | 35 +++++++++++++++---- 8 files changed, 96 insertions(+), 14 deletions(-) diff --git a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java index 7ee80dfe..3e9678ab 100644 --- a/jadx-core/src/main/java/jadx/api/JadxDecompiler.java +++ b/jadx-core/src/main/java/jadx/api/JadxDecompiler.java @@ -415,6 +415,18 @@ public final class JadxDecompiler implements Closeable { } } + /** + * Get JavaClass by ClassNode without loading and decompilation + */ + JavaClass convertClassNode(ClassNode cls) { + return classesMap.computeIfAbsent(cls, node -> { + if (cls.isInner()) { + return new JavaClass(cls, convertClassNode(cls.getParentClass())); + } + return new JavaClass(cls, this); + }); + } + @Nullable("For not generated classes") @ApiStatus.Internal public JavaClass getJavaClassByNode(ClassNode cls) { @@ -555,7 +567,7 @@ public final class JadxDecompiler implements Closeable { return null; } if (obj instanceof ClassNode) { - return getJavaClassByNode((ClassNode) obj); + return convertClassNode((ClassNode) obj); } if (obj instanceof MethodNode) { return getJavaMethodByNode(((MethodNode) obj)); diff --git a/jadx-core/src/main/java/jadx/api/JavaClass.java b/jadx-core/src/main/java/jadx/api/JavaClass.java index b73faa13..80ca5665 100644 --- a/jadx-core/src/main/java/jadx/api/JavaClass.java +++ b/jadx-core/src/main/java/jadx/api/JavaClass.java @@ -63,6 +63,11 @@ public final class JavaClass implements JavaNode { cls.reloadCode(); } + public void unload() { + listsLoaded = false; + cls.unloadCode(); + } + public synchronized String getSmali() { return cls.getDisassembledCode(); } @@ -81,13 +86,14 @@ public final class JavaClass implements JavaNode { } listsLoaded = true; decompile(); + JadxDecompiler rootDecompiler = getRootDecompiler(); int inClsCount = cls.getInnerClasses().size(); if (inClsCount != 0) { List list = new ArrayList<>(inClsCount); for (ClassNode inner : cls.getInnerClasses()) { if (!inner.contains(AFlag.DONT_GENERATE)) { - JavaClass javaClass = new JavaClass(inner, this); + JavaClass javaClass = rootDecompiler.convertClassNode(inner); javaClass.loadLists(); list.add(javaClass); } diff --git a/jadx-core/src/main/java/jadx/core/ProcessClass.java b/jadx-core/src/main/java/jadx/core/ProcessClass.java index 1e347e8a..be40fe24 100644 --- a/jadx-core/src/main/java/jadx/core/ProcessClass.java +++ b/jadx-core/src/main/java/jadx/core/ProcessClass.java @@ -41,6 +41,10 @@ public final class ProcessClass { cls.deepUnload(); cls.root().runPreDecompileStageForClass(cls); } + if (cls.contains(AFlag.CLASS_UNLOADED)) { + cls.remove(AFlag.CLASS_UNLOADED); + cls.root().runPreDecompileStageForClass(cls); + } if (codegen) { if (cls.getState() == GENERATED_AND_UNLOADED) { // allow to run code generation again diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java index d0deb2cb..2a5d0986 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java @@ -82,6 +82,7 @@ public enum AFlag { RESTART_CODEGEN, // codegen must be executed again RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage CLASS_DEEP_RELOAD, // perform deep class unload (reload) before process + CLASS_UNLOADED, // class was completely unloaded DONT_UNLOAD_CLASS, // don't unload class after code generation (only for tests and debug!) } 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 f8c9014b..337e4978 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 @@ -274,6 +274,15 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN return decompile(false); } + public void unloadCode() { + if (state == NOT_LOADED) { + return; + } + add(AFlag.CLASS_UNLOADED); + unloadFromCache(); + deepUnload(); + } + public void deepUnload() { if (clsData == null) { // manually added class @@ -287,17 +296,27 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN innerClasses.forEach(ClassNode::deepUnload); } - private synchronized ICodeInfo decompile(boolean searchInCache) { + private void unloadFromCache() { + if (isInner()) { + return; + } ICodeCache codeCache = root().getCodeCache(); - ClassNode topParentClass = getTopParentClass(); - String clsRawName = topParentClass.getRawName(); + codeCache.remove(getRawName()); + } + + private synchronized ICodeInfo decompile(boolean searchInCache) { + if (isInner()) { + return ICodeInfo.EMPTY; + } + ICodeCache codeCache = root().getCodeCache(); + String clsRawName = getRawName(); if (searchInCache) { ICodeInfo code = codeCache.get(clsRawName); if (code != null && code != ICodeInfo.EMPTY) { return code; } } - ICodeInfo codeInfo = ProcessClass.generateCode(topParentClass); + ICodeInfo codeInfo = ProcessClass.generateCode(this); codeCache.add(clsRawName, codeInfo); return codeInfo; } @@ -510,7 +529,8 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN /** * Get all inner and inlined classes recursively * - * @param resultClassesSet all identified inner and inlined classes are added to this set + * @param resultClassesSet + * all identified inner and inlined classes are added to this set */ public void getInnerAndInlinedClassesRecursive(Set resultClassesSet) { for (ClassNode innerCls : innerClasses) { @@ -554,6 +574,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN return parentClass != this; } + public boolean isTopClass() { + return parentClass == this; + } + @Nullable public MethodNode getClassInitMth() { return searchMethodByShortId("()V"); diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/IndexService.java b/jadx-gui/src/main/java/jadx/gui/jobs/IndexService.java index 2975b3e6..252fd006 100644 --- a/jadx-gui/src/main/java/jadx/gui/jobs/IndexService.java +++ b/jadx-gui/src/main/java/jadx/gui/jobs/IndexService.java @@ -61,6 +61,15 @@ public class IndexService { indexCls(cls); } + public synchronized void remove(JavaClass cls) { + TextSearchIndex index = cache.getTextIndex(); + if (index == null) { + return; + } + indexSet.remove(cls); + index.remove(cls); + } + public boolean isIndexNeeded(JavaClass cls) { return !indexSet.contains(cls); } diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java index 0d1e3c0f..09cd8742 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java @@ -77,6 +77,11 @@ public class JClass extends JLoadableNode implements Comparable { update(); } + public synchronized void unload() { + cls.unload(); + loaded = false; + } + public synchronized void update() { removeAllChildren(); if (!loaded) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/RenameDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/RenameDialog.java index 3b6fb681..c87bed36 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/RenameDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/RenameDialog.java @@ -42,6 +42,7 @@ import jadx.core.dex.nodes.RootNode; import jadx.core.dex.visitors.rename.RenameVisitor; import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; +import jadx.gui.jobs.IndexService; import jadx.gui.jobs.TaskStatus; import jadx.gui.settings.JadxProject; import jadx.gui.treemodel.JClass; @@ -97,6 +98,7 @@ public class RenameDialog extends JDialog { refreshState(); } catch (Exception e) { LOG.error("Rename failed", e); + UiUtils.errorMessage(this, "Rename failed:\n" + Utils.getStackTrace(e)); } dispose(); } @@ -191,7 +193,7 @@ public class RenameDialog extends JDialog { if (!updatedTopClasses.isEmpty()) { mainWindow.getBackgroundExecutor().execute("Refreshing", - Utils.collectionMap(updatedTopClasses, cls -> () -> refreshJClass(cls)), + () -> refreshClasses(updatedTopClasses), (status) -> { if (status == TaskStatus.CANCEL_BY_MEMORY) { mainWindow.showHeapUsageBar(); @@ -219,12 +221,31 @@ public class RenameDialog extends JDialog { } } - private void refreshJClass(JClass cls) { - try { - cls.reload(); - cache.getIndexService().refreshIndex(cls.getCls()); - } catch (Exception e) { - LOG.error("Failed to reload class: {}", cls.getFullName(), e); + private void refreshClasses(Set updatedTopClasses) { + IndexService indexService = cache.getIndexService(); + if (updatedTopClasses.size() < 10) { + // small batch => reload + LOG.debug("Classes to reload: {}", updatedTopClasses.size()); + for (JClass cls : updatedTopClasses) { + try { + cls.reload(); + indexService.refreshIndex(cls.getCls()); + } catch (Exception e) { + LOG.error("Failed to reload class: {}", cls.getFullName(), e); + } + } + } else { + // big batch => unload + LOG.debug("Classes to unload: {}", updatedTopClasses.size()); + indexService.setComplete(false); + for (JClass cls : updatedTopClasses) { + try { + cls.unload(); + indexService.remove(cls.getCls()); + } catch (Exception e) { + LOG.error("Failed to unload class: {}", cls.getFullName(), e); + } + } } }