fix(gui): improve JNode caching (#2400)

This commit is contained in:
Skylot
2025-02-01 17:36:53 +00:00
parent 3d36c93beb
commit 801890f0a8
9 changed files with 82 additions and 27 deletions
@@ -587,6 +587,8 @@ public final class JadxDecompiler implements Closeable {
return convertMethodNode((MethodNode) ann);
case FIELD:
return convertFieldNode((FieldNode) ann);
case PKG:
return convertPackageNode((PackageNode) ann);
case DECLARATION:
return getJavaNodeByCodeAnnotation(codeInfo, ((NodeDeclareRef) ann).getNode());
case VAR:
@@ -9,6 +9,7 @@ import javax.swing.JPopupMenu;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.ICodeInfo;
import jadx.api.JavaClass;
@@ -30,6 +31,7 @@ import jadx.gui.ui.popupmenu.JClassPopupMenu;
import jadx.gui.ui.tab.TabbedPane;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.Icons;
import jadx.gui.utils.JNodeCache;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
@@ -46,12 +48,18 @@ public class JClass extends JLoadableNode implements JRenameNode {
private final transient JavaClass cls;
private final transient JClass jParent;
private final transient JNodeCache nodeCache;
private transient boolean loaded;
public JClass(JavaClass cls, JClass parent) {
/**
* Should be called only from JNodeCache!
*/
public JClass(JavaClass cls, JClass parent, JNodeCache nodeCache) {
this.cls = cls;
this.jParent = parent;
this.loaded = parent != null;
this.nodeCache = nodeCache;
}
public JavaClass getCls() {
@@ -69,7 +77,10 @@ public class JClass extends JLoadableNode implements JRenameNode {
}
@Override
public SimpleTask getLoadTask() {
public synchronized @Nullable SimpleTask getLoadTask() {
if (loaded) {
return null;
}
JClass rootClass = getRootClass();
return new SimpleTask(NLS.str("progress.decompile"),
() -> rootClass.getCls().getClassNode().decompile(), // run decompilation in background
@@ -106,15 +117,15 @@ public class JClass extends JLoadableNode implements JRenameNode {
add(new TextNode(NLS.str("tree.loading")));
} else {
for (JavaClass javaClass : cls.getInnerClasses()) {
JClass innerCls = new JClass(javaClass, this);
JClass innerCls = nodeCache.makeFrom(javaClass);
add(innerCls);
innerCls.update();
}
for (JavaField f : cls.getFields()) {
add(new JField(f, this));
add(nodeCache.makeFrom(f));
}
for (JavaMethod m : cls.getMethods()) {
add(new JMethod(m, this));
add(nodeCache.makeFrom(m));
}
}
}
@@ -34,6 +34,9 @@ public class JField extends JNode implements JRenameNode {
private final transient JavaField field;
private final transient JClass jParent;
/**
* Should be called only from JNodeCache!
*/
public JField(JavaField javaField, JClass jClass) {
this.field = javaField;
this.jParent = jClass;
@@ -40,6 +40,9 @@ public class JMethod extends JNode implements JRenameNode {
private final transient JavaMethod mth;
private final transient JClass jParent;
/**
* Should be called only from JNodeCache!
*/
public JMethod(JavaMethod javaMethod, JClass jClass) {
this.mth = javaMethod;
this.jParent = jClass;
@@ -1,5 +1,7 @@
package jadx.gui.treemodel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.Icon;
@@ -33,6 +35,9 @@ public class JPackage extends JNode {
private String name;
/**
* Should be called only from JNodeCache!
*/
public JPackage(JavaPackage pkg, boolean enabled, List<JClass> classes, List<JPackage> subPackages, boolean synthetic) {
this.pkg = pkg;
this.enabled = enabled;
@@ -41,6 +46,10 @@ public class JPackage extends JNode {
this.synthetic = synthetic;
}
public static JPackage makeTmpRoot() {
return new JPackage(null, true, Collections.emptyList(), new ArrayList<>(), true);
}
public void update() {
removeAllChildren();
if (isEnabled()) {
@@ -27,10 +27,6 @@ public class JSources extends JNode {
public final void update() {
removeAllChildren();
PackageHelper packageHelper = wrapper.getCache().getPackageHelper();
if (packageHelper == null) {
packageHelper = new PackageHelper(wrapper);
wrapper.getCache().setPackageHelper(packageHelper);
}
List<JPackage> roots = packageHelper.getRoots(flatPackages);
for (JPackage rootPkg : roots) {
rootPkg.update();
@@ -14,29 +14,30 @@ import jadx.gui.utils.pkgs.PackageHelper;
public class CacheObject {
private final JadxWrapper wrapper;
private final JNodeCache jNodeCache;
private final PackageHelper packageHelper;
private String lastSearch;
private JNodeCache jNodeCache;
private Map<SearchDialog.SearchPreset, Set<SearchDialog.SearchOptions>> lastSearchOptions;
private String lastSearchPackage;
private List<List<JavaClass>> decompileBatches;
private PackageHelper packageHelper;
private volatile boolean fullDecompilationFinished;
public CacheObject(JadxWrapper wrapper) {
this.wrapper = wrapper;
this.jNodeCache = new JNodeCache(wrapper);
this.packageHelper = new PackageHelper(wrapper, jNodeCache);
reset();
}
public void reset() {
lastSearch = null;
jNodeCache = new JNodeCache(wrapper);
jNodeCache.reset();
lastSearchOptions = new HashMap<>();
lastSearchPackage = null;
decompileBatches = null;
packageHelper = null;
fullDecompilationFinished = false;
}
@@ -78,10 +79,6 @@ public class CacheObject {
return packageHelper;
}
public void setPackageHelper(PackageHelper packageHelper) {
this.packageHelper = packageHelper;
}
public boolean isFullDecompilationFinished() {
return fullDecompilationFinished;
}
@@ -1,5 +1,7 @@
package jadx.gui.utils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -7,6 +9,7 @@ import jadx.api.JavaClass;
import jadx.api.JavaField;
import jadx.api.JavaMethod;
import jadx.api.JavaNode;
import jadx.api.JavaPackage;
import jadx.api.JavaVariable;
import jadx.api.metadata.ICodeNodeRef;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -15,6 +18,7 @@ import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JField;
import jadx.gui.treemodel.JMethod;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JPackage;
import jadx.gui.treemodel.JVariable;
public class JNodeCache {
@@ -38,6 +42,14 @@ public class JNodeCache {
return jNode;
}
public void put(ICodeNodeRef nodeRef, JNode jNode) {
cache.put(nodeRef, jNode);
}
public void put(JavaNode javaNode, JNode jNode) {
cache.put(javaNode.getCodeNodeRef(), jNode);
}
public JNode makeFrom(JavaNode javaNode) {
if (javaNode == null) {
return null;
@@ -58,6 +70,12 @@ public class JNodeCache {
return jCls;
}
public JPackage newJPackage(JavaPackage javaPkg, boolean synthetic, boolean pkgEnabled, List<JClass> classes) {
JPackage jPackage = new JPackage(javaPkg, pkgEnabled, classes, new ArrayList<>(), synthetic);
put(javaPkg, jPackage);
return jPackage;
}
public void remove(JavaNode javaNode) {
cache.remove(javaNode.getCodeNodeRef());
}
@@ -70,12 +88,16 @@ public class JNodeCache {
javaCls.getInlinedClasses().forEach(this::remove);
}
public void reset() {
cache.clear();
}
private JClass convert(JavaClass cls) {
JavaClass parentCls = cls.getDeclaringClass();
if (parentCls == cls) {
return new JClass(cls, null);
return new JClass(cls, null, this);
}
return new JClass(cls, makeFrom(parentCls));
return new JClass(cls, makeFrom(parentCls), this);
}
private JNode convert(ICodeNodeRef nodeRef) {
@@ -101,6 +123,9 @@ public class JNodeCache {
JMethod jMth = (JMethod) makeFrom(javaVar.getMth());
return new JVariable(jMth, javaVar);
}
if (node instanceof JavaPackage) {
throw new JadxRuntimeException("Unexpected JPackage (missing from cache): " + node);
}
throw new JadxRuntimeException("Unknown type for JavaNode: " + node.getClass());
}
}
@@ -28,18 +28,18 @@ public class PackageHelper {
private static final Comparator<JPackage> PKG_COMPARATOR = Comparator.comparing(JPackage::getName, String.CASE_INSENSITIVE_ORDER);
private final JadxWrapper wrapper;
private final JNodeCache nodeCache;
private List<String> excludedPackages;
private JNodeCache nodeCache;
private final Map<PackageInfo, JPackage> pkgInfoMap = new HashMap<>();
public PackageHelper(JadxWrapper wrapper) {
public PackageHelper(JadxWrapper wrapper, JNodeCache jNodeCache) {
this.wrapper = wrapper;
this.nodeCache = jNodeCache;
}
public List<JPackage> getRoots(boolean flatPackages) {
excludedPackages = wrapper.getExcludedPackages();
nodeCache = wrapper.getCache().getNodeCache();
pkgInfoMap.clear();
if (flatPackages) {
return prepareFlatPackages();
@@ -87,7 +87,7 @@ public class PackageHelper {
}
private List<JPackage> prepareHierarchyPackages() {
JPackage root = new JPackage(null, true, Collections.emptyList(), new ArrayList<>(), true);
JPackage root = JPackage.makeTmpRoot();
List<JavaPackage> packages = wrapper.getPackages();
List<JPackage> jPackages = new ArrayList<>(packages.size());
// create nodes for exists packages
@@ -176,12 +176,21 @@ public class PackageHelper {
classes = Utils.collectionMap(javaPkg.getClasses(), nodeCache::makeFrom);
classes.sort(CLASS_COMPARATOR);
}
return new JPackage(javaPkg, pkgEnabled, classes, new ArrayList<>(), synthetic);
return nodeCache.newJPackage(javaPkg, synthetic, pkgEnabled, classes);
}
private static boolean isPkgEnabled(String fullPkgName, List<String> excludedPackages) {
return excludedPackages.isEmpty()
|| excludedPackages.stream()
.noneMatch(p -> fullPkgName.equals(p) || fullPkgName.startsWith(p + '.'));
return excludedPackages.isEmpty() || excludedPackages.stream().noneMatch(p -> isPkgMatch(fullPkgName, p));
}
private static boolean isPkgMatch(String fullPkgName, String filterPkg) {
if (fullPkgName.equals(filterPkg)) {
return true;
}
// optimized check, same as `fullPkgName.startsWith(filterPkg + '.')`
int filterPkgLen = filterPkg.length();
return fullPkgName.length() > filterPkgLen
&& fullPkgName.charAt(filterPkgLen) == '.'
&& fullPkgName.startsWith(filterPkg);
}
}