factor GhidraScriptInfoManager out of GhidraScriptUtil

- move bundlehost to ghidrascriptmgrplugin
This commit is contained in:
Jason P. Leasure
2020-04-27 15:41:42 -04:00
parent a26ce496ef
commit 2b49816c6c
21 changed files with 375 additions and 344 deletions
@@ -54,9 +54,7 @@ public class BundleHost {
} }
public void dispose() { public void dispose() {
if (felix != null) { disposeFramework();
forceStopFramework();
}
} }
HashMap<ResourceFile, GhidraBundle> bp2gb = new HashMap<>(); HashMap<ResourceFile, GhidraBundle> bp2gb = new HashMap<>();
@@ -503,14 +501,16 @@ public class BundleHost {
} }
} }
void forceStopFramework() { void disposeFramework() {
try { if (felix != null) {
felix.stop(); try {
felix.waitForStop(5000); felix.stop();
felix = null; felix.waitForStop(5000);
} felix = null;
catch (BundleException | InterruptedException e) { }
e.printStackTrace(); catch (BundleException | InterruptedException e) {
e.printStackTrace();
}
} }
} }
@@ -518,7 +518,7 @@ public class BundleHost {
Task t = new Task("killing OSGi framework", false, false, true, true) { Task t = new Task("killing OSGi framework", false, false, true, true) {
@Override @Override
public void run(TaskMonitor monitor) throws CancelledException { public void run(TaskMonitor monitor) throws CancelledException {
forceStopFramework(); disposeFramework();
} }
}; };
@@ -599,10 +599,10 @@ public class BundleHost {
List<BundleHostListener> listeners = new ArrayList<>(); List<BundleHostListener> listeners = new ArrayList<>();
void fireBundleBuilt(GhidraBundle sbi) { void fireBundleBuilt(GhidraBundle gb) {
synchronized (listeners) { synchronized (listeners) {
for (BundleHostListener l : listeners) { for (BundleHostListener l : listeners) {
l.bundleBuilt(sbi); l.bundleBuilt(gb);
} }
} }
} }
@@ -140,6 +140,11 @@ public class GhidraSourceBundle extends GhidraBundle {
Collectors.joining()); Collectors.joining());
} }
private String parseImps(ResourceFile javaSource) {
// XXX don't use @imports, use an annotation
return GhidraScriptUtil.newScriptInfo(javaSource).getImports();
}
/** /**
* update buildReqs based on \@imports tag in java files from the default package * update buildReqs based on \@imports tag in java files from the default package
* *
@@ -155,8 +160,7 @@ public class GhidraSourceBundle extends GhidraBundle {
// this might be the earliest need for ScriptInfo, so allow construction. // this might be the earliest need for ScriptInfo, so allow construction.
// NB: ScriptInfo will update field values if lastModified has changed since last time they were computed // NB: ScriptInfo will update field values if lastModified has changed since last time they were computed
ScriptInfo si = GhidraScriptUtil.getScriptInfo(rf); String imps = parseImps(rf);
String imps = si.getImports();
if (imps != null && !imps.isEmpty()) { if (imps != null && !imps.isEmpty()) {
List<BundleRequirement> reqs; List<BundleRequirement> reqs;
try { try {
@@ -33,8 +33,7 @@ import docking.actions.KeyBindingUtils;
import docking.tool.ToolConstants; import docking.tool.ToolConstants;
import docking.widgets.table.GTable; import docking.widgets.table.GTable;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.*;
import ghidra.app.script.ScriptInfo;
import ghidra.framework.Application; import ghidra.framework.Application;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.util.*; import ghidra.util.*;
@@ -51,6 +50,7 @@ class GhidraScriptActionManager {
private GhidraScriptComponentProvider provider; private GhidraScriptComponentProvider provider;
private GhidraScriptMgrPlugin plugin; private GhidraScriptMgrPlugin plugin;
private GhidraScriptInfoManager infoManager;
private DockingAction refreshAction; private DockingAction refreshAction;
private DockingAction bundleStatusAction; private DockingAction bundleStatusAction;
private DockingAction newAction; private DockingAction newAction;
@@ -65,10 +65,11 @@ class GhidraScriptActionManager {
private DockingAction helpAction; private DockingAction helpAction;
private Map<ResourceFile, ScriptAction> actionMap = new HashMap<>(); private Map<ResourceFile, ScriptAction> actionMap = new HashMap<>();
GhidraScriptActionManager(GhidraScriptComponentProvider provider, GhidraScriptActionManager(GhidraScriptComponentProvider provider, GhidraScriptMgrPlugin plugin,
GhidraScriptMgrPlugin plugin) { GhidraScriptInfoManager infoManager) {
this.provider = provider; this.provider = provider;
this.plugin = plugin; this.plugin = plugin;
this.infoManager = infoManager;
createActions(); createActions();
} }
@@ -107,7 +108,7 @@ class GhidraScriptActionManager {
void restoreScriptsThatAreInTool(SaveState saveState) { void restoreScriptsThatAreInTool(SaveState saveState) {
String[] array = saveState.getStrings(SCRIPT_ACTIONS_KEY, new String[0]); String[] array = saveState.getStrings(SCRIPT_ACTIONS_KEY, new String[0]);
for (String filename : array) { for (String filename : array) {
ScriptInfo info = GhidraScriptUtil.findScriptByName(filename); ScriptInfo info = infoManager.findScriptByName(filename);
if (info != null) { // the file may have been deleted from disk if (info != null) { // the file may have been deleted from disk
provider.getActionManager().createAction(info.getSourceFile()); provider.getActionManager().createAction(info.getSourceFile());
} }
@@ -129,7 +130,7 @@ class GhidraScriptActionManager {
continue; continue;
} }
ResourceFile scriptFile = action.getScript(); ResourceFile scriptFile = action.getScript();
ScriptInfo info = GhidraScriptUtil.getExistingScriptInfo(scriptFile); ScriptInfo info = infoManager.getExistingScriptInfo(scriptFile);
if (info == null) { if (info == null) {
Msg.showError(this, provider.getComponent(), "Bad state?", Msg.showError(this, provider.getComponent(), "Bad state?",
"action associated with a script that has no info"); "action associated with a script that has no info");
@@ -71,6 +71,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
private RootNode scriptRoot; private RootNode scriptRoot;
private GTree scriptCategoryTree; private GTree scriptCategoryTree;
private DraggableScriptTable scriptTable; private DraggableScriptTable scriptTable;
private GhidraScriptInfoManager infoManager;
private GhidraScriptTableModel tableModel; private GhidraScriptTableModel tableModel;
private BundleStatusComponentProvider bundleStatusComponentProvider; private BundleStatusComponentProvider bundleStatusComponentProvider;
private TaskListener taskListener = new ScriptTaskListener(); private TaskListener taskListener = new ScriptTaskListener();
@@ -102,23 +103,12 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
final private RefreshingBundleHostListener refreshingBundleHostListener = final private RefreshingBundleHostListener refreshingBundleHostListener =
new RefreshingBundleHostListener(); new RefreshingBundleHostListener();
static private int loaded = 0; GhidraScriptComponentProvider(GhidraScriptMgrPlugin plugin, BundleHost bundleHost) {
GhidraScriptComponentProvider(GhidraScriptMgrPlugin plugin) {
super(plugin.getTool(), "Script Manager", plugin.getName()); super(plugin.getTool(), "Script Manager", plugin.getName());
this.plugin = plugin; this.plugin = plugin;
this.bundleHost = bundleHost;
if (loaded == 0) { this.infoManager = new GhidraScriptInfoManager();
bundleHost = new BundleHost();
GhidraScriptUtil.initialize(bundleHost);
bundleHost.addGhidraBundle(GhidraScriptUtil.getUserScriptDirectory(), true, false);
bundleHost.addGhidraBundles(GhidraScriptUtil.getSystemScriptPaths(), true, true);
}
else {
bundleHost = GhidraScriptUtil.getBundleHost();
}
loaded += 1;
bundleStatusComponentProvider = bundleStatusComponentProvider =
new BundleStatusComponentProvider(plugin.getTool(), plugin.getName(), bundleHost); new BundleStatusComponentProvider(plugin.getTool(), plugin.getName(), bundleHost);
@@ -133,7 +123,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
build(); build();
plugin.getTool().addComponentProvider(this, false); plugin.getTool().addComponentProvider(this, false);
actionManager = new GhidraScriptActionManager(this, plugin); actionManager = new GhidraScriptActionManager(this, plugin, infoManager);
updateTitle(); updateTitle();
} }
@@ -171,7 +161,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
scriptCategoryTree.getSelectionModel().setSelectionMode( scriptCategoryTree.getSelectionModel().setSelectionMode(
TreeSelectionModel.SINGLE_TREE_SELECTION); TreeSelectionModel.SINGLE_TREE_SELECTION);
tableModel = new GhidraScriptTableModel(this); tableModel = new GhidraScriptTableModel(this, infoManager);
scriptTable = new DraggableScriptTable(this, tableModel); scriptTable = new DraggableScriptTable(this, tableModel);
scriptTable.setName("SCRIPT_TABLE"); scriptTable.setName("SCRIPT_TABLE");
@@ -292,11 +282,6 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
tableFilterPanel.dispose(); tableFilterPanel.dispose();
actionManager.dispose(); actionManager.dispose();
bundleStatusComponentProvider.dispose(); bundleStatusComponentProvider.dispose();
loaded -= 1;
if (loaded == 0) {
GhidraScriptUtil.dispose();
}
} }
public BundleHost getBundleHost() { public BundleHost getBundleHost() {
@@ -307,6 +292,10 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
return actionManager; return actionManager;
} }
GhidraScriptInfoManager getInfoManager() {
return infoManager;
}
Map<ResourceFile, GhidraScriptEditorComponentProvider> getEditorMap() { Map<ResourceFile, GhidraScriptEditorComponentProvider> getEditorMap() {
return editorMap; return editorMap;
} }
@@ -410,6 +399,8 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
renameFile.delete(); renameFile.delete();
return; return;
} }
infoManager.removeMetadata(script);
if (actionManager.hasScriptAction(script)) { if (actionManager.hasScriptAction(script)) {
KeyStroke ks = actionManager.getKeyBinding(script); KeyStroke ks = actionManager.getKeyBinding(script);
actionManager.removeAction(script); actionManager.removeAction(script);
@@ -417,9 +408,9 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
action.setKeyBindingData(new KeyBindingData(ks)); action.setKeyBindingData(new KeyBindingData(ks));
} }
assert !GhidraScriptUtil.containsMetadata( assert !infoManager.containsMetadata(
renameFile) : "renamed script already has metadata"; renameFile) : "renamed script already has metadata";
GhidraScriptUtil.getScriptInfo(renameFile); infoManager.getScriptInfo(renameFile);
tableModel.switchScript(script, renameFile); tableModel.switchScript(script, renameFile);
setSelectedScript(renameFile); setSelectedScript(renameFile);
@@ -496,6 +487,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
if (removeScript(script)) { if (removeScript(script)) {
GhidraScriptProvider provider = GhidraScriptUtil.getProvider(script); GhidraScriptProvider provider = GhidraScriptUtil.getProvider(script);
if (provider.deleteScript(script)) { if (provider.deleteScript(script)) {
infoManager.removeMetadata(script);
restoreSelection(script); restoreSelection(script);
} }
else { else {
@@ -563,8 +555,8 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
// create the ScriptInfo object now, before the TableModelEvent handlers // create the ScriptInfo object now, before the TableModelEvent handlers
// attempt to use it. // attempt to use it.
assert !GhidraScriptUtil.containsMetadata(newFile) : "new source already has metadata?"; assert !infoManager.containsMetadata(newFile) : "new source already has metadata?";
GhidraScriptUtil.getScriptInfo(newFile); infoManager.getScriptInfo(newFile);
tableModel.insertScript(newFile); tableModel.insertScript(newFile);
@@ -710,8 +702,8 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
if (sb instanceof GhidraSourceBundle) { if (sb instanceof GhidraSourceBundle) {
GhidraSourceBundle gsb = (GhidraSourceBundle) sb; GhidraSourceBundle gsb = (GhidraSourceBundle) sb;
for (ResourceFile sf : gsb.getNewSources()) { for (ResourceFile sf : gsb.getNewSources()) {
if (GhidraScriptUtil.containsMetadata(sf)) { if (infoManager.containsMetadata(sf)) {
ScriptInfo info = GhidraScriptUtil.getExistingScriptInfo(sf); ScriptInfo info = infoManager.getExistingScriptInfo(sf);
BuildFailure e = gsb.getErrors(sf); BuildFailure e = gsb.getErrors(sf);
info.setCompileErrors(e != null); info.setCompileErrors(e != null);
} }
@@ -753,7 +745,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
} }
private void performRefresh() { private void performRefresh() {
GhidraScriptUtil.clearMetadata(); infoManager.clearMetadata();
refresh(); refresh();
} }
@@ -787,7 +779,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
removeScript(file); removeScript(file);
} }
GhidraScriptUtil.refreshDuplicates(); infoManager.refreshDuplicates();
refreshScriptData(); refreshScriptData();
} }
@@ -808,7 +800,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
// new ScriptInfo objects are created on performRefresh, e.g. on startup. Other // new ScriptInfo objects are created on performRefresh, e.g. on startup. Other
// refresh operations might have old infos. // refresh operations might have old infos.
// assert !GhidraScriptUtil.containsMetadata(scriptFile): "info already exists for script during refresh"; // assert !GhidraScriptUtil.containsMetadata(scriptFile): "info already exists for script during refresh";
ScriptInfo info = GhidraScriptUtil.getScriptInfo(scriptFile); ScriptInfo info = infoManager.getScriptInfo(scriptFile);
String[] categoryPath = info.getCategory(); String[] categoryPath = info.getCategory();
scriptRoot.insert(categoryPath); scriptRoot.insert(categoryPath);
} }
@@ -823,7 +815,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
for (ResourceFile script : scripts) { for (ResourceFile script : scripts) {
// First get the ScriptInfo object and refresh, which will ensure any // First get the ScriptInfo object and refresh, which will ensure any
// info data (ie: script icons) will be reloaded. // info data (ie: script icons) will be reloaded.
ScriptInfo info = GhidraScriptUtil.getExistingScriptInfo(script); ScriptInfo info = infoManager.getExistingScriptInfo(script);
info.refresh(); info.refresh();
ScriptAction scriptAction = actionManager.get(script); ScriptAction scriptAction = actionManager.get(script);
@@ -851,7 +843,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
// note: turn String[] to List<String> to use hashing // note: turn String[] to List<String> to use hashing
Set<List<String>> categories = new HashSet<>(); Set<List<String>> categories = new HashSet<>();
for (ScriptInfo info : GhidraScriptUtil.getScriptInfoIterable()) { for (ScriptInfo info : infoManager.getScriptInfoIterable()) {
String[] path = info.getCategory(); String[] path = info.getCategory();
List<String> category = Arrays.asList(path); List<String> category = Arrays.asList(path);
for (int i = 1; i <= category.size(); i++) { for (int i = 1; i <= category.size(); i++) {
@@ -938,7 +930,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
editorMap.put(newScript, editor); editorMap.put(newScript, editor);
editorMap.remove(oldScript); editorMap.remove(oldScript);
// create corresponding info before inserting in table // create corresponding info before inserting in table
GhidraScriptUtil.getScriptInfo(newScript); infoManager.getScriptInfo(newScript);
tableModel.insertScript(newScript); tableModel.insertScript(newScript);
} }
@@ -953,7 +945,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
} }
actionManager.removeAction(script); actionManager.removeAction(script);
GhidraScriptUtil.removeMetadata(script); infoManager.removeMetadata(script);
return true; return true;
} }
@@ -1012,7 +1004,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
// the selected script has been changed, update the description panel // the selected script has been changed, update the description panel
updateDescriptionPanel(); updateDescriptionPanel();
ScriptInfo info = GhidraScriptUtil.getExistingScriptInfo(script); ScriptInfo info = infoManager.getExistingScriptInfo(script);
updateCategoryTree(info.getCategory()); updateCategoryTree(info.getCategory());
} }
@@ -1029,7 +1021,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
@Override @Override
public List<String> transform(ResourceFile script) { public List<String> transform(ResourceFile script) {
ScriptInfo info = GhidraScriptUtil.getExistingScriptInfo(script); ScriptInfo info = infoManager.getExistingScriptInfo(script);
list.clear(); list.clear();
list.add(info.getName()); list.add(info.getName());
list.add(info.getDescription()); list.add(info.getDescription());
@@ -1063,7 +1055,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
String text = "Error! no script info!"; String text = "Error! no script info!";
ResourceFile script = getSelectedScript(); ResourceFile script = getSelectedScript();
if (script != null) { if (script != null) {
ScriptInfo info = GhidraScriptUtil.getExistingScriptInfo(script); ScriptInfo info = infoManager.getExistingScriptInfo(script);
if (info != null) { if (info != null) {
text = info.getToolTipText(); text = info.getToolTipText();
} }
@@ -1200,7 +1192,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
@Override @Override
public boolean acceptsRow(ResourceFile script) { public boolean acceptsRow(ResourceFile script) {
ScriptInfo info = GhidraScriptUtil.getExistingScriptInfo(script); ScriptInfo info = infoManager.getExistingScriptInfo(script);
String[] category = getSelectedCategoryPath(); String[] category = getSelectedCategoryPath();
if (category == null) { // root node if (category == null) { // root node
@@ -25,6 +25,8 @@ import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin; import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.plugin.core.eclipse.EclipseConnection; import ghidra.app.plugin.core.eclipse.EclipseConnection;
import ghidra.app.plugin.core.eclipse.EclipseIntegrationOptionsPlugin; import ghidra.app.plugin.core.eclipse.EclipseIntegrationOptionsPlugin;
import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.script.GhidraState; import ghidra.app.script.GhidraState;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
@@ -50,16 +52,31 @@ public class GhidraScriptMgrPlugin extends ProgramPlugin implements GhidraScript
final private GhidraScriptComponentProvider provider; final private GhidraScriptComponentProvider provider;
static private int loaded = 0;
final private BundleHost bundleHost;
public GhidraScriptMgrPlugin(PluginTool tool) { public GhidraScriptMgrPlugin(PluginTool tool) {
super(tool, true, true, true); super(tool, true, true, true);
if (loaded == 0) {
bundleHost = new BundleHost();
GhidraScriptUtil.initialize(bundleHost, null);
}
else {
bundleHost = GhidraScriptUtil.getBundleHost();
}
loaded += 1;
provider = new GhidraScriptComponentProvider(this); provider = new GhidraScriptComponentProvider(this, bundleHost);
} }
@Override @Override
protected void dispose() { protected void dispose() {
super.dispose(); super.dispose();
provider.dispose(); provider.dispose();
loaded -= 1;
if (loaded == 0) {
GhidraScriptUtil.dispose();
}
} }
@Override @Override
@@ -23,7 +23,7 @@ import javax.swing.*;
import docking.widgets.table.*; import docking.widgets.table.*;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.GhidraScriptInfoManager;
import ghidra.app.script.ScriptInfo; import ghidra.app.script.ScriptInfo;
import ghidra.docking.settings.Settings; import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.ServiceProvider;
@@ -44,10 +44,13 @@ class GhidraScriptTableModel extends GDynamicColumnTableModel<ResourceFile, Obje
private GhidraScriptComponentProvider provider; private GhidraScriptComponentProvider provider;
private List<ResourceFile> scriptList = new ArrayList<>(); private List<ResourceFile> scriptList = new ArrayList<>();
final private GhidraScriptInfoManager infoManager;
GhidraScriptTableModel(GhidraScriptComponentProvider provider) { GhidraScriptTableModel(GhidraScriptComponentProvider provider,
GhidraScriptInfoManager infoManager) {
super(provider.getTool()); super(provider.getTool());
this.provider = provider; this.provider = provider;
this.infoManager = infoManager;
} }
@Override @Override
@@ -105,7 +108,7 @@ class GhidraScriptTableModel extends GDynamicColumnTableModel<ResourceFile, Obje
scriptList.add(script); scriptList.add(script);
} }
} }
fireTableRowsInserted(rowStart, rowStart + scriptFiles.size()-1); fireTableRowsInserted(rowStart, rowStart + scriptFiles.size() - 1);
} }
void removeScript(ResourceFile script) { void removeScript(ResourceFile script) {
@@ -238,7 +241,7 @@ class GhidraScriptTableModel extends GDynamicColumnTableModel<ResourceFile, Obje
JLabel label = (JLabel) super.getTableCellRendererComponent(data); JLabel label = (JLabel) super.getTableCellRendererComponent(data);
ResourceFile file = (ResourceFile) data.getRowObject(); ResourceFile file = (ResourceFile) data.getRowObject();
ScriptInfo info = GhidraScriptUtil.getExistingScriptInfo(file); ScriptInfo info = infoManager.getExistingScriptInfo(file);
label.setText(null); label.setText(null);
label.setToolTipText(null); label.setToolTipText(null);
@@ -279,7 +282,7 @@ class GhidraScriptTableModel extends GDynamicColumnTableModel<ResourceFile, Obje
@Override @Override
public ImageIcon getValue(ResourceFile rowObject, Settings settings, Object data, public ImageIcon getValue(ResourceFile rowObject, Settings settings, Object data,
ServiceProvider sp) throws IllegalArgumentException { ServiceProvider sp) throws IllegalArgumentException {
ScriptInfo info = GhidraScriptUtil.getExistingScriptInfo(rowObject); ScriptInfo info = infoManager.getExistingScriptInfo(rowObject);
if (info.isCompileErrors() || info.isDuplicate()) { if (info.isCompileErrors() || info.isDuplicate()) {
return ERROR_IMG; return ERROR_IMG;
} }
@@ -329,7 +332,7 @@ class GhidraScriptTableModel extends GDynamicColumnTableModel<ResourceFile, Obje
@Override @Override
public String getValue(ResourceFile rowObject, Settings settings, Object data, public String getValue(ResourceFile rowObject, Settings settings, Object data,
ServiceProvider sp) throws IllegalArgumentException { ServiceProvider sp) throws IllegalArgumentException {
ScriptInfo info = GhidraScriptUtil.getExistingScriptInfo(rowObject); ScriptInfo info = infoManager.getExistingScriptInfo(rowObject);
return info.getDescription(); return info.getDescription();
} }
@@ -401,7 +404,7 @@ class GhidraScriptTableModel extends GDynamicColumnTableModel<ResourceFile, Obje
@Override @Override
public KeyBindingsInfo getValue(ResourceFile rowObject, Settings settings, Object data, public KeyBindingsInfo getValue(ResourceFile rowObject, Settings settings, Object data,
ServiceProvider sp) throws IllegalArgumentException { ServiceProvider sp) throws IllegalArgumentException {
ScriptInfo info = GhidraScriptUtil.getExistingScriptInfo(rowObject); ScriptInfo info = infoManager.getExistingScriptInfo(rowObject);
KeyStroke actionKeyStroke = provider.getActionManager().getKeyBinding(rowObject); KeyStroke actionKeyStroke = provider.getActionManager().getKeyBinding(rowObject);
boolean isActionBinding = false; boolean isActionBinding = false;
KeyStroke keyBinding = info.getKeyBinding(); KeyStroke keyBinding = info.getKeyBinding();
@@ -459,7 +462,7 @@ class GhidraScriptTableModel extends GDynamicColumnTableModel<ResourceFile, Obje
@Override @Override
public String getValue(ResourceFile rowObject, Settings settings, Object data, public String getValue(ResourceFile rowObject, Settings settings, Object data,
ServiceProvider sp) throws IllegalArgumentException { ServiceProvider sp) throws IllegalArgumentException {
ScriptInfo info = GhidraScriptUtil.getExistingScriptInfo(rowObject); ScriptInfo info = infoManager.getExistingScriptInfo(rowObject);
return getCategoryString(info); return getCategoryString(info);
} }
@@ -36,7 +36,7 @@ import ghidra.app.script.*;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
public class SaveDialog extends DialogComponentProvider implements ListSelectionListener { public class SaveDialog extends DialogComponentProvider implements ListSelectionListener {
private GhidraScriptComponentProvider componentProvider; protected GhidraScriptComponentProvider componentProvider;
private GhidraScriptProvider provider; private GhidraScriptProvider provider;
private List<ResourceFile> paths; private List<ResourceFile> paths;
@@ -49,7 +49,8 @@ public class SaveDialog extends DialogComponentProvider implements ListSelection
public SaveDialog(Component parent, String title, public SaveDialog(Component parent, String title,
GhidraScriptComponentProvider componentProvider, ResourceFile scriptFile, GhidraScriptComponentProvider componentProvider, ResourceFile scriptFile,
HelpLocation help) { HelpLocation help) {
this(parent, title, componentProvider, componentProvider.getWritableScriptDirectories(), scriptFile, help); this(parent, title, componentProvider, componentProvider.getWritableScriptDirectories(),
scriptFile, help);
} }
public SaveDialog(Component parent, String title, public SaveDialog(Component parent, String title,
@@ -161,7 +162,7 @@ public class SaveDialog extends DialogComponentProvider implements ListSelection
} }
protected String getDuplicateNameErrorMessage(String name) { protected String getDuplicateNameErrorMessage(String name) {
ScriptInfo existingInfo = GhidraScriptUtil.getExistingScriptInfo(name); ScriptInfo existingInfo = componentProvider.getInfoManager().getExistingScriptInfo(name);
if (existingInfo != null) { if (existingInfo != null) {
// make sure the script has not been deleted // make sure the script has not been deleted
ResourceFile sourceFile = existingInfo.getSourceFile(); ResourceFile sourceFile = existingInfo.getSourceFile();
@@ -15,20 +15,18 @@
*/ */
package ghidra.app.plugin.core.script; package ghidra.app.plugin.core.script;
import generic.jar.ResourceFile;
import ghidra.app.script.GhidraScriptUtil;
import ghidra.util.HelpLocation;
import java.awt.Component; import java.awt.Component;
import java.io.File; import java.io.File;
import generic.jar.ResourceFile;
import ghidra.util.HelpLocation;
class SaveNewScriptDialog extends SaveDialog { class SaveNewScriptDialog extends SaveDialog {
SaveNewScriptDialog(Component parent, String title, SaveNewScriptDialog(Component parent, String title,
GhidraScriptComponentProvider componentProvider, ResourceFile scriptFile, GhidraScriptComponentProvider componentProvider, ResourceFile scriptFile,
HelpLocation help) { HelpLocation help) {
super(parent, title, componentProvider, scriptFile, help); super(parent, title, componentProvider, scriptFile, help);
} }
/** /**
@@ -37,7 +35,7 @@ class SaveNewScriptDialog extends SaveDialog {
*/ */
@Override @Override
protected String getDuplicateNameErrorMessage(String name) { protected String getDuplicateNameErrorMessage(String name) {
if (GhidraScriptUtil.alreadyExists(name)) { if (componentProvider.getInfoManager().alreadyExists(name)) {
return "Duplicate script name."; return "Duplicate script name.";
} }
@@ -21,7 +21,7 @@ import javax.swing.KeyStroke;
import docking.ActionContext; import docking.ActionContext;
import docking.action.*; import docking.action.*;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.GhidraScriptInfoManager;
import ghidra.app.script.ScriptInfo; import ghidra.app.script.ScriptInfo;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.SystemUtilities; import ghidra.util.SystemUtilities;
@@ -30,6 +30,7 @@ class ScriptAction extends DockingAction {
private static final String SCRIPT_GROUP = "_SCRIPT_GROUP_"; private static final String SCRIPT_GROUP = "_SCRIPT_GROUP_";
private GhidraScriptMgrPlugin plugin; private GhidraScriptMgrPlugin plugin;
private GhidraScriptInfoManager infoManager;
private ResourceFile script; private ResourceFile script;
/** Signals that the keybinding value has been set by the user from the GUI (used for persistence) */ /** Signals that the keybinding value has been set by the user from the GUI (used for persistence) */
@@ -38,6 +39,7 @@ class ScriptAction extends DockingAction {
ScriptAction(GhidraScriptMgrPlugin plugin, ResourceFile script) { ScriptAction(GhidraScriptMgrPlugin plugin, ResourceFile script) {
super(script.getName(), plugin.getName()); super(script.getName(), plugin.getName());
this.plugin = plugin; this.plugin = plugin;
this.infoManager = plugin.getProvider().getInfoManager();
this.script = script; this.script = script;
setEnabled(true); setEnabled(true);
setHelpLocation(new HelpLocation(plugin.getName(), plugin.getName())); setHelpLocation(new HelpLocation(plugin.getName(), plugin.getName()));
@@ -90,7 +92,7 @@ class ScriptAction extends DockingAction {
} }
// check to see if we have a fallback value // check to see if we have a fallback value
ScriptInfo info = GhidraScriptUtil.getExistingScriptInfo(script); ScriptInfo info = infoManager.getExistingScriptInfo(script);
KeyStroke metadataKeyStroke = info.getKeyBinding(); KeyStroke metadataKeyStroke = info.getKeyBinding();
if (metadataKeyStroke == null) { if (metadataKeyStroke == null) {
// there is no fallback value; the current keybinding data is what we want // there is no fallback value; the current keybinding data is what we want
@@ -105,7 +107,7 @@ class ScriptAction extends DockingAction {
// we have a user defined keybinding if the keystroke for the action differs from // we have a user defined keybinding if the keystroke for the action differs from
// that which is defined in the metadata of the script // that which is defined in the metadata of the script
KeyStroke actionKeyStroke = keyBindingData.getKeyBinding(); KeyStroke actionKeyStroke = keyBindingData.getKeyBinding();
ScriptInfo info = GhidraScriptUtil.getExistingScriptInfo(script); ScriptInfo info = infoManager.getExistingScriptInfo(script);
KeyStroke metadataKeyBinding = info.getKeyBinding(); KeyStroke metadataKeyBinding = info.getKeyBinding();
isUserDefinedKeyBinding = !SystemUtilities.isEqual(actionKeyStroke, metadataKeyBinding); isUserDefinedKeyBinding = !SystemUtilities.isEqual(actionKeyStroke, metadataKeyBinding);
} }
@@ -119,7 +121,7 @@ class ScriptAction extends DockingAction {
} }
void refresh() { void refresh() {
ScriptInfo info = GhidraScriptUtil.getExistingScriptInfo(script); ScriptInfo info = infoManager.getExistingScriptInfo(script);
KeyStroke stroke = info.getKeyBinding(); KeyStroke stroke = info.getKeyBinding();
if (!isUserDefinedKeyBinding) { if (!isUserDefinedKeyBinding) {
setKeyBindingData(new KeyBindingData(stroke)); setKeyBindingData(new KeyBindingData(stroke));
@@ -0,0 +1,193 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.script;
import java.util.*;
import generic.jar.ResourceFile;
import ghidra.util.Msg;
/**
* A utility class for managing script directories and ScriptInfo objects.
*/
public class GhidraScriptInfoManager {
private Map<ResourceFile, ScriptInfo> scriptFileToInfoMap = new HashMap<>();
private Map<String, List<ResourceFile>> scriptNameToFilesMap = new HashMap<>();
public void dispose() {
clearMetadata();
}
/**
* clear ScriptInfo metadata cached by GhidraScriptUtil
*/
public void clearMetadata() {
scriptFileToInfoMap.clear();
scriptNameToFilesMap.clear();
}
/**
* Removes the ScriptInfo object for the specified file
* @param scriptFile the script file
*/
public void removeMetadata(ResourceFile scriptFile) {
scriptFileToInfoMap.remove(scriptFile);
String name = scriptFile.getName();
List<ResourceFile> files = scriptNameToFilesMap.get(name);
if (files != null) {
Iterator<ResourceFile> iter = files.iterator();
while (iter.hasNext()) {
ResourceFile rFile = iter.next();
if (scriptFile.equals(rFile)) {
iter.remove();
break;
}
}
if (files.isEmpty()) {
scriptNameToFilesMap.remove(name);
}
}
}
/**
* get all scripts
* @return an iterable over all script info objects
*/
public Iterable<ScriptInfo> getScriptInfoIterable() {
return () -> scriptFileToInfoMap.values().iterator();
}
/**
* Returns the script info object for the specified script file,
* construct a new one if necessary.
*
* Only call this method if you expect to be creating ScriptInfo objects.
* Prefer getExistingScriptInfo instead.
*
* @param scriptFile the script file
* @return the script info object for the specified script file
*/
public ScriptInfo getScriptInfo(ResourceFile scriptFile) {
ScriptInfo info = scriptFileToInfoMap.get(scriptFile);
if (info != null) {
return info;
}
info = GhidraScriptUtil.newScriptInfo(scriptFile);
scriptFileToInfoMap.put(scriptFile, info);
String name = scriptFile.getName();
List<ResourceFile> matchingFiles =
scriptNameToFilesMap.computeIfAbsent(name, (n) -> new ArrayList<>());
matchingFiles.add(scriptFile);
markAnyDuplicates(matchingFiles);
return info;
}
/**
* Returns true if a ScriptInfo object exists for
* the specified script file.
* @param scriptFile the script file
* @return true if a ScriptInfo object exists
*/
public boolean containsMetadata(ResourceFile scriptFile) {
return scriptFileToInfoMap.containsKey(scriptFile);
}
public ScriptInfo getExistingScriptInfo(ResourceFile script) {
ScriptInfo info = scriptFileToInfoMap.get(script);
if (info == null) {
String s = (script.exists() ? "" : "non") + "existing script" + script.toString() +
" is missing expected ScriptInfo";
System.err.println(s);
Msg.showError(GhidraScriptInfoManager.class, null, "ScriptInfo lookup", s);
}
return info;
}
/**
* Returns the existing script info for the given name. The script environment limits
* scripts such that names are unique. If this method returns a non-null value, then the
* name given name is taken.
*
* @param scriptName the name of the script for which to get a ScriptInfo
* @return a ScriptInfo matching the given name; null if no script by that name is known to
* the script manager
*/
public ScriptInfo getExistingScriptInfo(String scriptName) {
List<ResourceFile> matchingFiles = scriptNameToFilesMap.get(scriptName);
if (matchingFiles == null || matchingFiles.isEmpty()) {
return null;
}
return scriptFileToInfoMap.get(matchingFiles.get(0));
}
/**
* Looks through all of the current {@link ScriptInfo}s to see if one already exists with
* the given name.
* @param scriptName The name to check
* @return true if the name is not taken by an existing {@link ScriptInfo}.
*/
public boolean alreadyExists(String scriptName) {
return getExistingScriptInfo(scriptName) != null;
}
private void markAnyDuplicates(List<ResourceFile> files) {
boolean isDuplicate = files.size() > 1;
files.forEach(f -> scriptFileToInfoMap.get(f).setDuplicate(isDuplicate));
}
/**
* Updates every known script's duplicate value.
*/
public void refreshDuplicates() {
scriptNameToFilesMap.values().forEach(files -> {
boolean isDuplicate = files.size() > 1;
files.forEach(file -> scriptFileToInfoMap.get(file).setDuplicate(isDuplicate));
});
}
/**
* Uses the given name to find a matching script. This method only works because of the
* limitation that all script names in Ghidra must be unique. If the given name has multiple
* script matches, then a warning will be logged.
*
* @param name The name for which to find a script
* @return The ScriptInfo that has the given name
*/
public ScriptInfo findScriptByName(String name) {
List<ResourceFile> matchingFiles = scriptNameToFilesMap.get(name);
if (matchingFiles != null && !matchingFiles.isEmpty()) {
ScriptInfo info = scriptFileToInfoMap.get(matchingFiles.get(0));
if (matchingFiles.size() > 1) {
Msg.warn(GhidraScriptInfoManager.class, "Found duplicate scripts for name: " +
name + ". Binding to script: " + info.getSourceFile());
}
return info;
}
ResourceFile file = GhidraScriptUtil.findScriptFileInPaths(
GhidraScriptUtil.getBundleHost().getBundlePaths(), name);
if (file == null) {
return null;
}
return getExistingScriptInfo(file); // this will cache the created info
}
}
@@ -15,19 +15,19 @@
*/ */
package ghidra.app.script; package ghidra.app.script;
import generic.jar.ResourceFile;
import ghidra.util.classfinder.ExtensionPoint;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import generic.jar.ResourceFile;
import ghidra.util.classfinder.ExtensionPoint;
/** /**
* NOTE: ALL GhidraScriptProvider CLASSES MUST END IN "ScriptProvider". If not, * NOTE: ALL GhidraScriptProvider CLASSES MUST END IN "ScriptProvider". If not,
* the ClassSearcher will not find them. * the ClassSearcher will not find them.
* *
*/ */
public abstract class GhidraScriptProvider implements ExtensionPoint, public abstract class GhidraScriptProvider
Comparable<GhidraScriptProvider> { implements ExtensionPoint, Comparable<GhidraScriptProvider> {
@Override @Override
public String toString() { public String toString() {
@@ -59,11 +59,7 @@ public abstract class GhidraScriptProvider implements ExtensionPoint,
* @return true if the script was completely deleted and cleaned up * @return true if the script was completely deleted and cleaned up
*/ */
public boolean deleteScript(ResourceFile scriptSource) { public boolean deleteScript(ResourceFile scriptSource) {
boolean deleted = !scriptSource.exists() || scriptSource.delete(); return !scriptSource.exists() || scriptSource.delete();
if (deleted) {
GhidraScriptUtil.removeMetadata(scriptSource);
}
return deleted;
} }
/** /**
@@ -44,17 +44,13 @@ public class GhidraScriptUtil {
*/ */
public static String USER_SCRIPTS_DIR = buildUserScriptsDirectory(); public static String USER_SCRIPTS_DIR = buildUserScriptsDirectory();
private static Map<ResourceFile, ScriptInfo> scriptFileToInfoMap = new HashMap<>();
private static Map<String, List<ResourceFile>> scriptNameToFilesMap = new HashMap<>();
static BundleHost _bundleHost; static BundleHost _bundleHost;
public static BundleHost getBundleHost() { public static BundleHost getBundleHost() {
return _bundleHost; return _bundleHost;
} }
public static void initialize(BundleHost bundleHost) { private static void setBundleHost(BundleHost bundleHost) {
if (_bundleHost != null) { if (_bundleHost != null) {
throw new RuntimeException("GhidraScriptUtil initialized multiple times!"); throw new RuntimeException("GhidraScriptUtil initialized multiple times!");
} }
@@ -70,15 +66,14 @@ public class GhidraScriptUtil {
} }
/** /**
* initialization for headless runs. * initialize state of GhidraScriptUtil with user, system paths, and optional extra system paths.
* *
* @param bundleHost the host to use * @param bundleHost the host to use
* @param extraSystemPaths additional system paths for this run, can be null * @param extraSystemPaths additional system paths for this run, can be null
* *
*/ */
public static void initialize(BundleHost bundleHost, List<String> extraSystemPaths) { public static void initialize(BundleHost bundleHost, List<String> extraSystemPaths) {
initialize(bundleHost); setBundleHost(bundleHost);
if (extraSystemPaths != null) { if (extraSystemPaths != null) {
for (String path : extraSystemPaths) { for (String path : extraSystemPaths) {
bundleHost.addGhidraBundle(new ResourceFile(path), true, true); bundleHost.addGhidraBundle(new ResourceFile(path), true, true);
@@ -90,19 +85,13 @@ public class GhidraScriptUtil {
} }
public static void dispose() { public static void dispose() {
clearMetadata(); if (_bundleHost != null) {
_bundleHost = null; _bundleHost.dispose();
_bundleHost = null;
}
providers = null; providers = null;
} }
/**
* clear ScriptInfo metadata cached by GhidraScriptUtil
*/
public static void clearMetadata() {
scriptFileToInfoMap.clear();
scriptNameToFilesMap.clear();
}
/** /**
* Returns a list of the current script directories. * Returns a list of the current script directories.
* @return a list of the current script directories * @return a list of the current script directories
@@ -112,6 +101,20 @@ public class GhidraScriptUtil {
Collectors.toList()); Collectors.toList());
} }
public static ResourceFile getSourceDirectoryContaining(ResourceFile sourceFile) {
String sourcePath = sourceFile.getAbsolutePath();
for (ResourceFile sourceDir : getScriptSourceDirectories()) {
if (sourcePath.startsWith(sourceDir.getAbsolutePath() + File.separatorChar)) {
return sourceDir;
}
}
return null;
}
public static ResourceFile findScriptByName(String scriptName) {
return findScriptFileInPaths(getScriptSourceDirectories(), scriptName);
}
/** /**
* User's home scripts directory. Some tests may override the default using the * User's home scripts directory. Some tests may override the default using the
* SystemUtilities.USER_SCRIPTS_DIR system property. * SystemUtilities.USER_SCRIPTS_DIR system property.
@@ -224,130 +227,6 @@ public class GhidraScriptUtil {
return name.substring(0, pos); return name.substring(0, pos);
} }
/**
* Removes the ScriptInfo object for the specified file
* @param scriptFile the script file
*/
public static void removeMetadata(ResourceFile scriptFile) {
scriptFileToInfoMap.remove(scriptFile);
String name = scriptFile.getName();
List<ResourceFile> files = scriptNameToFilesMap.get(name);
if (files != null) {
Iterator<ResourceFile> iter = files.iterator();
while (iter.hasNext()) {
ResourceFile rFile = iter.next();
if (scriptFile.equals(rFile)) {
iter.remove();
break;
}
}
if (files.isEmpty()) {
scriptNameToFilesMap.remove(name);
}
}
}
/**
* get all scripts
* @return an iterable over all script info objects
*/
public static Iterable<ScriptInfo> getScriptInfoIterable() {
return () -> scriptFileToInfoMap.values().iterator();
}
/**
* Returns the script info object for the specified script file,
* construct a new one if necessary.
*
* Only call this method if you expect to be creating ScriptInfo objects.
* Prefer getExistingScriptInfo instead.
*
* @param scriptFile the script file
* @return the script info object for the specified script file
*/
public static ScriptInfo getScriptInfo(ResourceFile scriptFile) {
ScriptInfo info = scriptFileToInfoMap.get(scriptFile);
if (info != null) {
return info;
}
GhidraScriptProvider gsp = getProvider(scriptFile);
info = new ScriptInfo(gsp, scriptFile);
scriptFileToInfoMap.put(scriptFile, info);
String name = scriptFile.getName();
List<ResourceFile> matchingFiles =
scriptNameToFilesMap.computeIfAbsent(name, (n) -> new ArrayList<>());
matchingFiles.add(scriptFile);
markAnyDuplicates(matchingFiles);
return info;
}
/**
* Returns true if a ScriptInfo object exists for
* the specified script file.
* @param scriptFile the script file
* @return true if a ScriptInfo object exists
*/
public static boolean containsMetadata(ResourceFile scriptFile) {
return scriptFileToInfoMap.containsKey(scriptFile);
}
public static ScriptInfo getExistingScriptInfo(ResourceFile script) {
ScriptInfo info = scriptFileToInfoMap.get(script);
if (info == null) {
String s = (script.exists() ? "" : "non") + "existing script" + script.toString() +
" is missing info we thought was there";
System.err.println(s);
Msg.showError(GhidraScriptUtil.class, null, "ScriptInfo lookup", s);
}
return info;
}
/**
* Returns the existing script info for the given name. The script environment limits
* scripts such that names are unique. If this method returns a non-null value, then the
* name given name is taken.
*
* @param scriptName the name of the script for which to get a ScriptInfo
* @return a ScriptInfo matching the given name; null if no script by that name is known to
* the script manager
*/
public static ScriptInfo getExistingScriptInfo(String scriptName) {
List<ResourceFile> matchingFiles = scriptNameToFilesMap.get(scriptName);
if (matchingFiles == null || matchingFiles.isEmpty()) {
return null;
}
return scriptFileToInfoMap.get(matchingFiles.get(0));
}
/**
* Looks through all of the current {@link ScriptInfo}s to see if one already exists with
* the given name.
* @param scriptName The name to check
* @return true if the name is not taken by an existing {@link ScriptInfo}.
*/
public static boolean alreadyExists(String scriptName) {
return getExistingScriptInfo(scriptName) != null;
}
private static void markAnyDuplicates(List<ResourceFile> files) {
boolean isDuplicate = files.size() > 1;
files.forEach(f -> scriptFileToInfoMap.get(f).setDuplicate(isDuplicate));
}
/**
* Updates every known script's duplicate value.
*/
public static void refreshDuplicates() {
scriptNameToFilesMap.values().forEach(files -> {
boolean isDuplicate = files.size() > 1;
files.forEach(file -> scriptFileToInfoMap.get(file).setDuplicate(isDuplicate));
});
}
/** /**
* Returns a list of all Ghidra script providers * Returns a list of all Ghidra script providers
* *
@@ -435,6 +314,10 @@ public class GhidraScriptUtil {
return new ResourceFile(parentDirctory, className); return new ResourceFile(parentDirctory, className);
} }
public static ScriptInfo newScriptInfo(ResourceFile file) {
return new ScriptInfo(getProvider(file), file);
}
/** /**
* Fixup name issues, such as package parts in the name and inner class names. * Fixup name issues, such as package parts in the name and inner class names.
* <p> * <p>
@@ -457,8 +340,8 @@ public class GhidraScriptUtil {
return path + ".java"; return path + ".java";
} }
/** Returns true if the given filename exists in any of the given directories */ static ResourceFile findScriptFileInPaths(
private static ResourceFile findScriptFileInPaths(Collection<ResourceFile> scriptDirectories, Collection<ResourceFile> scriptDirectories,
String filename) { String filename) {
String validatedName = fixupName(filename); String validatedName = fixupName(filename);
@@ -474,33 +357,6 @@ public class GhidraScriptUtil {
return null; return null;
} }
/**
* Uses the given name to find a matching script. This method only works because of the
* limitation that all script names in Ghidra must be unique. If the given name has multiple
* script matches, then a warning will be logged.
*
* @param name The name for which to find a script
* @return The ScriptInfo that has the given name
*/
public static ScriptInfo findScriptByName(String name) {
List<ResourceFile> matchingFiles = scriptNameToFilesMap.get(name);
if (matchingFiles != null && !matchingFiles.isEmpty()) {
ScriptInfo info = scriptFileToInfoMap.get(matchingFiles.get(0));
if (matchingFiles.size() > 1) {
Msg.warn(GhidraScriptUtil.class, "Found duplicate scripts for name: " + name +
". Binding to script: " + info.getSourceFile());
}
return info;
}
ResourceFile file = findScriptFileInPaths(_bundleHost.getBundlePaths(), name);
if (file == null) {
return null;
}
return getExistingScriptInfo(file); // this will cache the created info
}
/* only used by GhidraScriptAnalyzerAdapter */ /* only used by GhidraScriptAnalyzerAdapter */
/** /**
* Runs the specified script with the specified state * Runs the specified script with the specified state
@@ -537,31 +393,4 @@ public class GhidraScriptUtil {
return true; return true;
} }
@Deprecated
private static void updateAvailableScriptFilesForDirectory(List<ResourceFile> scriptAccumulator,
ResourceFile directory) {
ResourceFile[] files = directory.listFiles();
if (files == null) {
return;
}
for (ResourceFile scriptFile : files) {
if (scriptFile.isFile() && hasScriptProvider(scriptFile)) {
scriptAccumulator.add(scriptFile);
}
}
}
/*
* used only by RecipeEditorDialog
*/
@Deprecated
public static List<ResourceFile> getAllScripts() {
List<ResourceFile> scriptList = new ArrayList<>();
for (ResourceFile dirPath : _bundleHost.getBundlePaths()) {
updateAvailableScriptFilesForDirectory(scriptList, dirPath);
}
return scriptList;
}
} }
@@ -31,7 +31,7 @@ public class JavaScriptProvider extends GhidraScriptProvider {
} }
public GhidraSourceBundle getBundleForSource(ResourceFile sourceFile) { public GhidraSourceBundle getBundleForSource(ResourceFile sourceFile) {
ResourceFile sourceDir = getSourceDirectoryContaining(sourceFile); ResourceFile sourceDir = GhidraScriptUtil.getSourceDirectoryContaining(sourceFile);
if (sourceDir == null) { if (sourceDir == null) {
return null; return null;
} }
@@ -67,8 +67,6 @@ public class JavaScriptProvider extends GhidraScriptProvider {
@Override @Override
public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer) public GhidraScript getScriptInstance(ResourceFile sourceFile, PrintWriter writer)
throws ClassNotFoundException, InstantiationException, IllegalAccessException { throws ClassNotFoundException, InstantiationException, IllegalAccessException {
// in headless operation, ScriptInfo objects can be created here
ScriptInfo info = GhidraScriptUtil.getScriptInfo(sourceFile);
try { try {
Class<?> clazz = loadClass(sourceFile, writer); Class<?> clazz = loadClass(sourceFile, writer);
Object object; Object object;
@@ -83,16 +81,13 @@ public class JavaScriptProvider extends GhidraScriptProvider {
String message = "Not a valid Ghidra script: " + sourceFile.getName(); String message = "Not a valid Ghidra script: " + sourceFile.getName();
writer.println(message); writer.println(message);
Msg.error(this, message); Msg.error(this, message);
info.setCompileErrors(true);
return null; // class is not GhidraScript return null; // class is not GhidraScript
} }
catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
info.setCompileErrors(true);
throw e; throw e;
} }
catch (Exception e) { catch (Exception e) {
info.setCompileErrors(true);
throw new ClassNotFoundException("", e); throw new ClassNotFoundException("", e);
} }
} }
@@ -109,16 +104,6 @@ public class JavaScriptProvider extends GhidraScriptProvider {
return clazz; return clazz;
} }
public static ResourceFile getSourceDirectoryContaining(ResourceFile sourceFile) {
String sourcePath = sourceFile.getAbsolutePath();
for (ResourceFile sourceDir : GhidraScriptUtil.getScriptSourceDirectories()) {
if (sourcePath.startsWith(sourceDir.getAbsolutePath() + File.separatorChar)) {
return sourceDir;
}
}
return null;
}
@Override @Override
public void createNewScript(ResourceFile newScript, String category) throws IOException { public void createNewScript(ResourceFile newScript, String category) throws IOException {
String scriptName = newScript.getName(); String scriptName = newScript.getName();
@@ -78,7 +78,7 @@ public class ScriptInfo {
* @param provider the script provider (for example, java or python) * @param provider the script provider (for example, java or python)
* @param sourceFile the script source file * @param sourceFile the script source file
*/ */
public ScriptInfo(GhidraScriptProvider provider, ResourceFile sourceFile) { ScriptInfo(GhidraScriptProvider provider, ResourceFile sourceFile) {
this.provider = provider; this.provider = provider;
this.sourceFile = sourceFile; this.sourceFile = sourceFile;
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,13 +15,12 @@
*/ */
package ghidra.framework.analysis; package ghidra.framework.analysis;
import org.jdom.Element;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.script.ScriptInfo;
import ghidra.app.services.*; import ghidra.app.services.*;
import org.jdom.Element;
class AnalyzerInfo implements Comparable<AnalyzerInfo> { class AnalyzerInfo implements Comparable<AnalyzerInfo> {
static final String XML_ELEMENT_NAME = "ANALYZER"; static final String XML_ELEMENT_NAME = "ANALYZER";
static final String CLASS_NAME = "CLASS_NAME"; static final String CLASS_NAME = "CLASS_NAME";
@@ -48,10 +46,12 @@ class AnalyzerInfo implements Comparable<AnalyzerInfo> {
// first make all One-Shot Analyzers sort before all other types. // first make all One-Shot Analyzers sort before all other types.
AnalyzerType myType = analyzer.getAnalysisType(); AnalyzerType myType = analyzer.getAnalysisType();
AnalyzerType otherType = o.analyzer.getAnalysisType(); AnalyzerType otherType = o.analyzer.getAnalysisType();
if (myType == AnalyzerType.ONE_SHOT_ANALYZER && otherType != AnalyzerType.ONE_SHOT_ANALYZER) { if (myType == AnalyzerType.ONE_SHOT_ANALYZER &&
otherType != AnalyzerType.ONE_SHOT_ANALYZER) {
return -1; return -1;
} }
if (myType != AnalyzerType.ONE_SHOT_ANALYZER && otherType == AnalyzerType.ONE_SHOT_ANALYZER) { if (myType != AnalyzerType.ONE_SHOT_ANALYZER &&
otherType == AnalyzerType.ONE_SHOT_ANALYZER) {
return 1; return 1;
} }
@@ -125,13 +125,13 @@ class AnalyzerInfo implements Comparable<AnalyzerInfo> {
return startPhase; return startPhase;
} }
public static AnalyzerInfo createInfoForWrappedAnalzyer(AnalysisRecipe recipe, Element element) { public static AnalyzerInfo createInfoForWrappedAnalzyer(AnalysisRecipe recipe,
Element element) {
String scriptName = element.getAttributeValue("SCRIPT_NAME"); String scriptName = element.getAttributeValue("SCRIPT_NAME");
String type = element.getAttributeValue("ANALYZER_TYPE"); String type = element.getAttributeValue("ANALYZER_TYPE");
AnalyzerType analyzerType = AnalyzerType.valueOf(type); AnalyzerType analyzerType = AnalyzerType.valueOf(type);
int priority = Integer.parseInt(element.getAttributeValue("PRIORITY")); int priority = Integer.parseInt(element.getAttributeValue("PRIORITY"));
ScriptInfo scriptInfo = GhidraScriptUtil.findScriptByName(scriptName); ResourceFile file = GhidraScriptUtil.findScriptByName(scriptName);
ResourceFile file = scriptInfo.getSourceFile();
Analyzer analyzer = new GhidraScriptAnalyzerAdapter(file, analyzerType, priority); Analyzer analyzer = new GhidraScriptAnalyzerAdapter(file, analyzerType, priority);
return new AnalyzerInfo(recipe, analyzer, true); return new AnalyzerInfo(recipe, analyzer, true);
} }
@@ -48,7 +48,7 @@ public class GhidraScriptAnalyzerAdapter extends AbstractAnalyzer {
} }
private static String getDescription(ResourceFile file) { private static String getDescription(ResourceFile file) {
return GhidraScriptUtil.getScriptInfo(file).getDescription(); return GhidraScriptUtil.newScriptInfo(file).getDescription();
} }
public void setPrintWriter(PrintWriter writer) { public void setPrintWriter(PrintWriter writer) {
@@ -26,7 +26,6 @@ import docking.widgets.label.GLabel;
import docking.widgets.textfield.IntegerTextField; import docking.widgets.textfield.IntegerTextField;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.script.ScriptInfo;
import ghidra.app.services.AnalyzerType; import ghidra.app.services.AnalyzerType;
import ghidra.util.layout.HorizontalLayout; import ghidra.util.layout.HorizontalLayout;
import ghidra.util.layout.VerticalLayout; import ghidra.util.layout.VerticalLayout;
@@ -36,8 +35,9 @@ public class GhidraScriptSelectionDialog extends ListSelectionDialog<ResourceFil
private IntegerTextField priorityField; private IntegerTextField priorityField;
public GhidraScriptSelectionDialog() { public GhidraScriptSelectionDialog() {
super("Create Script Based Analyzer", "Script Name:", GhidraScriptUtil.getAllScripts(), super("Create Script Based Analyzer", "Script Name:",
new ScriptNameConverter(), new ScriptDescriptionConverter()); GhidraScriptUtil.getScriptSourceDirectories(), new ScriptNameConverter(),
new ScriptDescriptionConverter());
} }
@Override @Override
@@ -116,8 +116,7 @@ public class GhidraScriptSelectionDialog extends ListSelectionDialog<ResourceFil
private static class ScriptDescriptionConverter implements DataToStringConverter<ResourceFile> { private static class ScriptDescriptionConverter implements DataToStringConverter<ResourceFile> {
@Override @Override
public String getString(ResourceFile resourceFile) { public String getString(ResourceFile resourceFile) {
ScriptInfo info = GhidraScriptUtil.getScriptInfo(resourceFile); return GhidraScriptUtil.newScriptInfo(resourceFile).getDescription();
return info.getDescription();
} }
} }
@@ -231,13 +231,12 @@ public abstract class AbstractGhidraScriptMgrPluginTest
} }
protected void assertScriptManagerKnowsAbout(ResourceFile script) { protected void assertScriptManagerKnowsAbout(ResourceFile script) {
assertTrue(GhidraScriptUtil.containsMetadata(script)); assertTrue(provider.getInfoManager().containsMetadata(script));
assertNull(provider.getActionManager().get(script)); assertNull(provider.getActionManager().get(script));
} }
protected void assertScriptManagerForgotAbout(ResourceFile script) { protected void assertScriptManagerForgotAbout(ResourceFile script) {
assertFalse(provider.getInfoManager().containsMetadata(script));
assertFalse(GhidraScriptUtil.containsMetadata(script));
assertNull(provider.getActionManager().get(script)); assertNull(provider.getActionManager().get(script));
assertNull(provider.getEditorMap().get(script)); assertNull(provider.getEditorMap().get(script));
} }
@@ -989,7 +988,8 @@ public abstract class AbstractGhidraScriptMgrPluginTest
// destroy any NewScriptxxx files...and Temp ones too // destroy any NewScriptxxx files...and Temp ones too
BundleStatusComponentProvider bundleStatusComponentProvider = BundleStatusComponentProvider bundleStatusComponentProvider =
(BundleStatusComponentProvider) TestUtils.getInstanceField("bundleStatusComponentProvider", provider); (BundleStatusComponentProvider) TestUtils.getInstanceField(
"bundleStatusComponentProvider", provider);
List<ResourceFile> paths = bundleStatusComponentProvider.getModel().getEnabledPaths(); List<ResourceFile> paths = bundleStatusComponentProvider.getModel().getEnabledPaths();
for (ResourceFile path : paths) { for (ResourceFile path : paths) {
@@ -1161,7 +1161,7 @@ public abstract class AbstractGhidraScriptMgrPluginTest
} }
protected ResourceFile findScript(String name) { protected ResourceFile findScript(String name) {
ScriptInfo info = GhidraScriptUtil.getExistingScriptInfo(name); ScriptInfo info = provider.getInfoManager().getExistingScriptInfo(name);
assertNotNull("Cannot find script by the given name: " + name, info); assertNotNull("Cannot find script by the given name: " + name, info);
return info.getSourceFile(); return info.getSourceFile();
} }
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,6 +15,9 @@
*/ */
package ghidra.app.plugin.prototype.MicrosoftCodeAnalyzerPlugin; package ghidra.app.plugin.prototype.MicrosoftCodeAnalyzerPlugin;
import java.io.PrintWriter;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.analysis.AutoAnalysisManager; import ghidra.app.plugin.core.analysis.AutoAnalysisManager;
import ghidra.app.script.*; import ghidra.app.script.*;
import ghidra.app.services.*; import ghidra.app.services.*;
@@ -32,8 +34,6 @@ import ghidra.util.Msg;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
import java.io.PrintWriter;
public class WindowsResourceReferenceAnalyzer extends AbstractAnalyzer { public class WindowsResourceReferenceAnalyzer extends AbstractAnalyzer {
private static final String NAME = "WindowsResourceReference"; private static final String NAME = "WindowsResourceReference";
private static final String DESCRIPTION = private static final String DESCRIPTION =
@@ -81,23 +81,21 @@ public class WindowsResourceReferenceAnalyzer extends AbstractAnalyzer {
PluginTool tool = analysisManager.getAnalysisTool(); PluginTool tool = analysisManager.getAnalysisTool();
Project project = findProject(tool); Project project = findProject(tool);
GhidraState state = GhidraState state = new GhidraState(tool, project, program,
new GhidraState(tool, project, program, new ProgramLocation(program, new ProgramLocation(program, set.getMinAddress()), new ProgramSelection(set), null);
set.getMinAddress()), new ProgramSelection(set), null);
try { try {
ScriptInfo scriptInfo = GhidraScriptUtil.findScriptByName(scriptName); ResourceFile sourceFile = GhidraScriptUtil.findScriptByName(scriptName);
if (scriptInfo == null) { if (sourceFile == null) {
throw new IllegalAccessException("Couldn't find script"); throw new IllegalAccessException("Couldn't find script");
} }
GhidraScriptProvider provider = GhidraScriptProvider provider = GhidraScriptUtil.getProvider(sourceFile);
GhidraScriptUtil.getProvider(scriptInfo.getSourceFile());
if (provider == null) { if (provider == null) {
throw new IllegalAccessException("Couldn't find script provider"); throw new IllegalAccessException("Couldn't find script provider");
} }
PrintWriter writer = getOutputMsgStream(tool); PrintWriter writer = getOutputMsgStream(tool);
GhidraScript script = provider.getScriptInstance(scriptInfo.getSourceFile(), writer); GhidraScript script = provider.getScriptInstance(sourceFile, writer);
script.set(state, monitor, writer); script.set(state, monitor, writer);
// This code was added so the analyzer won't print script messages to console // This code was added so the analyzer won't print script messages to console
@@ -23,6 +23,7 @@ import java.util.ArrayList;
import org.junit.*; import org.junit.*;
import generic.jar.ResourceFile; import generic.jar.ResourceFile;
import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.framework.task.GScheduledTask; import ghidra.framework.task.GScheduledTask;
@@ -77,6 +78,12 @@ public class AnalysisManagerTest extends AbstractGhidraHeadlessIntegrationTest {
programBuilder.createMemory("AAA", "0x100", 0x1000); programBuilder.createMemory("AAA", "0x100", 0x1000);
program = programBuilder.getProgram(); program = programBuilder.getProgram();
analyzers = new ArrayList<>(); analyzers = new ArrayList<>();
GhidraScriptUtil.initialize(new BundleHost(), null);
}
@After
public void cleanup() {
GhidraScriptUtil.dispose();
} }
@Test @Test
@@ -21,11 +21,11 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.jdom.Element; import org.jdom.Element;
import org.junit.Before; import org.junit.*;
import org.junit.Test;
import generic.jar.ResourceFile;
import ghidra.app.plugin.core.osgi.BundleHost;
import ghidra.app.script.GhidraScriptUtil; import ghidra.app.script.GhidraScriptUtil;
import ghidra.app.script.ScriptInfo;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB; import ghidra.program.database.ProgramDB;
@@ -47,6 +47,12 @@ public class AnalysisRecipeTest extends AbstractGhidraHeadlessIntegrationTest {
programBuilder.createMemory("AAA", "0x100", 0x1000); programBuilder.createMemory("AAA", "0x100", 0x1000);
program = programBuilder.getProgram(); program = programBuilder.getProgram();
analyzers = new ArrayList<>(); analyzers = new ArrayList<>();
GhidraScriptUtil.initialize(new BundleHost(), null);
}
@After
public void cleanup() {
GhidraScriptUtil.dispose();
} }
@Test @Test
@@ -88,9 +94,9 @@ public class AnalysisRecipeTest extends AbstractGhidraHeadlessIntegrationTest {
analyzers.add(analyzer1); analyzers.add(analyzer1);
analyzers.add(analyzer2); analyzers.add(analyzer2);
recipe = new AnalysisRecipe("Test Recipe", analyzers, program); recipe = new AnalysisRecipe("Test Recipe", analyzers, program);
ScriptInfo info = GhidraScriptUtil.findScriptByName("HelloWorldScript.java"); ResourceFile sourceFile = GhidraScriptUtil.findScriptByName("HelloWorldScript.java");
assertNotNull(info); assertNotNull(sourceFile);
recipe.addScriptAnalyzer(info.getSourceFile(), AnalyzerType.INSTRUCTION_ANALYZER, 15); recipe.addScriptAnalyzer(sourceFile, AnalyzerType.INSTRUCTION_ANALYZER, 15);
AnalysisPhase lastPhase = recipe.getLastPhase(); AnalysisPhase lastPhase = recipe.getLastPhase();
AnalysisPhase firstPhase = recipe.createPhase(); AnalysisPhase firstPhase = recipe.createPhase();