feat(api): add 'unload' method to JadxPlugin (#2463)

This commit is contained in:
Skylot
2025-04-20 21:44:47 +01:00
parent ea6492e5ba
commit 9981949a2b
12 changed files with 183 additions and 20 deletions
@@ -244,13 +244,17 @@ public class JCommanderWrapper {
JadxPluginManager pluginManager = decompiler.getPluginManager();
pluginManager.load(new JadxExternalPluginsLoader());
pluginManager.initAll();
for (PluginContext context : pluginManager.getAllPluginContexts()) {
JadxPluginOptions options = context.getOptions();
if (options != null) {
if (appendPlugin(context.getPluginInfo(), context.getOptions(), sb, maxNamesLen)) {
k++;
try {
for (PluginContext context : pluginManager.getAllPluginContexts()) {
JadxPluginOptions options = context.getOptions();
if (options != null) {
if (appendPlugin(context.getPluginInfo(), context.getOptions(), sb, maxNamesLen)) {
k++;
}
}
}
} finally {
pluginManager.unloadAll();
}
}
if (sb.length() == 0) {
@@ -170,6 +170,7 @@ public final class JadxDecompiler implements Closeable {
}
private void reset() {
unloadPlugins();
root = null;
classes = null;
resources = null;
@@ -215,6 +216,10 @@ public final class JadxDecompiler implements Closeable {
}
}
private void unloadPlugins() {
pluginManager.unloadResolved();
}
private void loadFinished() {
LOG.debug("Load finished");
List<JadxPass> list = customPasses.get(JadxAfterLoadPass.TYPE);
@@ -23,4 +23,12 @@ public interface JadxPlugin {
* For long operation, prefer {@link JadxPreparePass} or {@link JadxAfterLoadPass} instead.
*/
void init(JadxPluginContext context);
/**
* Plugin unload handler.
* Can be used to clean up resources on plugin unloading.
*/
default void unload() {
// optional method
}
}
@@ -26,4 +26,13 @@ public interface ISettingsGroup {
default List<ISettingsGroup> getSubGroups() {
return Collections.emptyList();
}
/**
* Settings close handler.
* Apply settings if 'save' param set to true.
* It can be used to clean up resources.
*/
default void close(boolean save) {
// optional method
}
}
@@ -22,7 +22,6 @@ import jadx.api.plugins.loader.JadxPluginLoader;
import jadx.api.plugins.options.JadxPluginOptions;
import jadx.api.plugins.options.OptionDescription;
import jadx.core.plugins.versions.VerifyRequiredVersion;
import jadx.core.utils.exceptions.JadxRuntimeException;
public class JadxPluginManager {
private static final Logger LOG = LoggerFactory.getLogger(JadxPluginManager.class);
@@ -149,7 +148,7 @@ public class JadxPluginManager {
}
context.init();
} catch (Exception e) {
throw new JadxRuntimeException("Failed to init plugin: " + context.getPluginId(), e);
LOG.warn("Failed to init plugin: {}", context.getPluginId(), e);
}
}
for (PluginContext context : pluginContexts) {
@@ -160,6 +159,24 @@ public class JadxPluginManager {
}
}
public void unloadAll() {
unload(allPlugins);
}
public void unloadResolved() {
unload(resolvedPlugins);
}
public void unload(SortedSet<PluginContext> pluginContexts) {
for (PluginContext context : pluginContexts) {
try {
context.unload();
} catch (Exception e) {
LOG.warn("Failed to unload plugin: {}", context.getPluginId(), e);
}
}
}
private AppContext buildDefaultAppContext() {
AppContext appContext = new AppContext();
appContext.setGuiContext(null);
@@ -55,11 +55,17 @@ public class PluginContext implements JadxPluginContext, JadxPluginRuntimeData,
this.pluginInfo = plugin.getPluginInfo();
}
void init() {
public void init() {
plugin.init(this);
initialized = true;
}
public void unload() {
if (initialized) {
plugin.unload();
}
}
@Override
public boolean isInitialized() {
return initialized;
@@ -91,6 +91,7 @@ public class JadxSettingsWindow extends JDialog {
private transient boolean needReload = false;
private transient SettingsTree tree;
private List<ISettingsGroup> groups;
public JadxSettingsWindow(MainWindow mainWindow, JadxSettings settings) {
this.mainWindow = mainWindow;
@@ -116,6 +117,7 @@ public class JadxSettingsWindow extends JDialog {
private void reloadUI() {
int[] selection = tree.getSelectionRows();
closeGroups(false);
getContentPane().removeAll();
initUI();
// wait for other events to process
@@ -128,7 +130,7 @@ public class JadxSettingsWindow extends JDialog {
private void initUI() {
JPanel wrapGroupPanel = new JPanel(new BorderLayout(10, 10));
List<ISettingsGroup> groups = new ArrayList<>();
groups = new ArrayList<>();
groups.add(makeDecompilationGroup());
groups.add(makeDeobfuscationGroup());
groups.add(makeRenameGroup());
@@ -690,7 +692,7 @@ public class JadxSettingsWindow extends JDialog {
sizeLimit.addChangeListener(ev -> settings.setSrhResourceSkipSize((Integer) sizeLimit.getValue()));
JTextField fileExtField = new JTextField();
fileExtField.getDocument().addDocumentListener(new DocumentUpdateListener((ev) -> {
fileExtField.getDocument().addDocumentListener(new DocumentUpdateListener(ev -> {
String ext = fileExtField.getText();
settings.setSrhResourceFileExt(ext);
}));
@@ -703,7 +705,14 @@ public class JadxSettingsWindow extends JDialog {
return searchGroup;
}
private void closeGroups(boolean save) {
for (ISettingsGroup group : groups) {
group.close(save);
}
}
private void save() {
closeGroups(true);
settings.sync();
enableComponents(this, false);
SwingUtilities.invokeLater(() -> {
@@ -723,6 +732,7 @@ public class JadxSettingsWindow extends JDialog {
}
private void cancel() {
closeGroups(false);
JadxSettingsAdapter.fill(settings, startSettings);
mainWindow.loadSettings();
dispose();
@@ -35,7 +35,9 @@ import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.ui.SettingsGroup;
import jadx.gui.ui.MainWindow;
import jadx.gui.utils.NLS;
import jadx.gui.utils.plugins.CloseablePlugins;
import jadx.gui.utils.plugins.CollectPlugins;
import jadx.gui.utils.plugins.SettingsGroupPluginWrap;
import jadx.gui.utils.ui.DocumentUpdateListener;
import jadx.plugins.tools.JadxPluginsTools;
import jadx.plugins.tools.data.JadxPluginMetadata;
@@ -53,12 +55,12 @@ public class PluginSettings {
}
public ISettingsGroup build() {
List<PluginContext> collectedPlugins = new CollectPlugins(mainWindow).build();
CloseablePlugins collectedPlugins = new CollectPlugins(mainWindow).build();
ISettingsGroup pluginsGroup = new PluginSettingsGroup(this, mainWindow, collectedPlugins);
for (PluginContext context : collectedPlugins) {
for (PluginContext context : collectedPlugins.getList()) {
ISettingsGroup pluginGroup = addPluginGroup(context);
if (pluginGroup != null) {
pluginsGroup.getSubGroups().add(pluginGroup);
pluginsGroup.getSubGroups().add(new SettingsGroupPluginWrap(context.getPluginId(), pluginGroup));
}
}
return pluginsGroup;
@@ -40,6 +40,7 @@ import jadx.gui.ui.MainWindow;
import jadx.gui.utils.Link;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.plugins.CloseablePlugins;
import jadx.plugins.tools.JadxPluginsList;
import jadx.plugins.tools.JadxPluginsTools;
import jadx.plugins.tools.data.JadxPluginMetadata;
@@ -51,11 +52,11 @@ class PluginSettingsGroup implements ISettingsGroup {
private final MainWindow mainWindow;
private final String title;
private final List<ISettingsGroup> subGroups = new ArrayList<>();
private final List<PluginContext> collectedPlugins;
private final CloseablePlugins collectedPlugins;
private JPanel detailsPanel;
public PluginSettingsGroup(PluginSettings pluginSettings, MainWindow mainWindow, List<PluginContext> collectedPlugins) {
public PluginSettingsGroup(PluginSettings pluginSettings, MainWindow mainWindow, CloseablePlugins collectedPlugins) {
this.pluginsSettings = pluginSettings;
this.mainWindow = mainWindow;
this.title = NLS.str("preferences.plugins");
@@ -78,6 +79,12 @@ class PluginSettingsGroup implements ISettingsGroup {
return buildMainSettingsPage();
}
@Override
public void close(boolean save) {
subGroups.forEach(subGroup -> subGroup.close(save));
collectedPlugins.close();
}
private JPanel buildMainSettingsPage() {
JButton installPluginBtn = new JButton(NLS.str("preferences.plugins.install"));
installPluginBtn.addActionListener(ev -> pluginsSettings.addPlugin());
@@ -124,13 +131,13 @@ class PluginSettingsGroup implements ISettingsGroup {
private void applyData(DefaultListModel<BasePluginListNode> listModel) {
List<JadxPluginMetadata> installed = JadxPluginsTools.getInstance().getInstalled();
List<BasePluginListNode> nodes = new ArrayList<>(installed.size() + collectedPlugins.size());
List<BasePluginListNode> nodes = new ArrayList<>(installed.size() + collectedPlugins.getList().size());
Set<String> installedSet = new HashSet<>(installed.size());
for (JadxPluginMetadata pluginMetadata : installed) {
installedSet.add(pluginMetadata.getPluginId());
nodes.add(new InstalledPluginNode(pluginMetadata));
}
for (PluginContext plugin : collectedPlugins) {
for (PluginContext plugin : collectedPlugins.getList()) {
if (!installedSet.contains(plugin.getPluginId())) {
nodes.add(new LoadedPluginNode(plugin));
}
@@ -0,0 +1,31 @@
package jadx.gui.utils.plugins;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import jadx.core.plugins.PluginContext;
public class CloseablePlugins {
private final List<PluginContext> list;
private final @Nullable Runnable closeable;
public CloseablePlugins(List<PluginContext> list, @Nullable Runnable closeable) {
this.list = list;
this.closeable = closeable;
}
public void close() {
if (closeable != null) {
closeable.run();
}
}
public @Nullable Runnable getCloseable() {
return closeable;
}
public List<PluginContext> getList() {
return list;
}
}
@@ -1,7 +1,6 @@
package jadx.gui.utils.plugins;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -27,12 +26,13 @@ public class CollectPlugins {
this.mainWindow = mainWindow;
}
public List<PluginContext> build() {
public CloseablePlugins build() {
SortedSet<PluginContext> allPlugins = new TreeSet<>();
mainWindow.getWrapper().getCurrentDecompiler()
.ifPresent(decompiler -> allPlugins.addAll(decompiler.getPluginManager().getResolvedPluginContexts()));
// collect and init not loaded plugins in new temp context
Runnable closeable = null;
JadxArgs jadxArgs = mainWindow.getSettings().toJadxArgs();
try (JadxDecompiler decompiler = new JadxDecompiler(jadxArgs)) {
JadxPluginManager pluginManager = decompiler.getPluginManager();
@@ -52,8 +52,9 @@ public class CollectPlugins {
if (!missingPlugins.isEmpty()) {
pluginManager.init(missingPlugins);
allPlugins.addAll(missingPlugins);
closeable = () -> pluginManager.unload(missingPlugins);
}
}
return new ArrayList<>(allPlugins);
return new CloseablePlugins(new ArrayList<>(allPlugins), closeable);
}
}
@@ -0,0 +1,63 @@
package jadx.gui.utils.plugins;
import java.util.Collections;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.JLabel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.gui.ISettingsGroup;
public class SettingsGroupPluginWrap implements ISettingsGroup {
private static final Logger LOG = LoggerFactory.getLogger(SettingsGroupPluginWrap.class);
private final String pluginId;
private final ISettingsGroup pluginSettingGroup;
public SettingsGroupPluginWrap(String pluginId, ISettingsGroup pluginSettingGroup) {
this.pluginId = pluginId;
this.pluginSettingGroup = pluginSettingGroup;
}
@Override
public String getTitle() {
try {
return pluginSettingGroup.getTitle();
} catch (Throwable t) {
LOG.warn("Failed to get settings group title for plugin: {}", pluginId, t);
return "<error>";
}
}
@Override
public JComponent buildComponent() {
try {
return pluginSettingGroup.buildComponent();
} catch (Throwable t) {
LOG.warn("Failed to build settings group component for plugin: {}", pluginId, t);
return new JLabel("<error>");
}
}
@Override
public List<ISettingsGroup> getSubGroups() {
try {
return pluginSettingGroup.getSubGroups();
} catch (Throwable t) {
LOG.warn("Failed to get settings group sub-groups for plugin: {}", pluginId, t);
return Collections.emptyList();
}
}
@Override
public void close(boolean save) {
try {
pluginSettingGroup.close(save);
} catch (Throwable t) {
LOG.warn("Failed to close settings group for plugin: {}", pluginId, t);
}
}
}