From 2015f135428ae8b89678b1c0c20afc08946f7d0f Mon Sep 17 00:00:00 2001 From: "Jason P. Leasure" Date: Mon, 8 Jun 2020 13:00:26 -0400 Subject: [PATCH] style fixes in ghidra.app. {plugin.core.script script} --- .../script/GhidraScriptActionManager.java | 252 +++++------------- .../script/GhidraScriptComponentProvider.java | 174 ++++++------ .../GhidraScriptEditorComponentProvider.java | 12 +- .../core/script/GhidraScriptMgrPlugin.java | 15 +- .../core/script/GhidraScriptTableModel.java | 7 +- .../app/plugin/core/script/Ingredient.java | 3 +- .../core/script/PickProviderDialog.java | 15 +- .../app/plugin/core/script/SaveDialog.java | 19 +- .../core/script/ScriptCategoryNode.java | 2 +- .../java/ghidra/app/script/GhidraScript.java | 53 ++-- .../app/script/GhidraScriptInfoManager.java | 9 + .../app/script/GhidraScriptProperties.java | 29 +- ...draScriptUnsupportedClassVersionError.java | 3 +- .../ghidra/app/script/GhidraScriptUtil.java | 70 +++-- .../java/ghidra/app/script/GhidraState.java | 47 +++- .../ghidra/app/script/JavaScriptProvider.java | 32 ++- .../script/ResourceFileJavaFileManager.java | 17 +- .../script/ResourceFileJavaFileObject.java | 11 + .../java/ghidra/app/script/ScriptInfo.java | 21 +- .../ghidra/app/script/StringTransformer.java | 3 +- .../AbstractGhidraScriptMgrPluginTest.java | 18 +- .../GhidraScriptMgrPluginScreenShots.java | 2 +- 22 files changed, 442 insertions(+), 372 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptActionManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptActionManager.java index c9c9ad02e7..bb4cb6e8fb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptActionManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptActionManager.java @@ -24,6 +24,7 @@ import java.util.*; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import javax.swing.Icon; import javax.swing.KeyStroke; import docking.ActionContext; @@ -49,18 +50,15 @@ class GhidraScriptActionManager { KeyEvent.VK_R, DockingUtils.CONTROL_KEY_MODIFIER_MASK | InputEvent.SHIFT_DOWN_MASK); private static final String SCRIPT_ACTIONS_KEY = "Scripts_Actions_Key"; + private static final String RESOURCE_FILE_ACTION_RUN_GROUP = "1"; + private GhidraScriptComponentProvider provider; private GhidraScriptMgrPlugin plugin; private GhidraScriptInfoManager infoManager; - private DockingAction refreshAction; - private DockingAction bundleStatusAction; + private DockingAction showBundleStatusAction; private DockingAction newAction; - private DockingAction runAction; private DockingAction runLastAction; private DockingAction globalRunLastAction; - private DockingAction editAction; - private DockingAction eclipseAction; - private DockingAction deleteAction; private DockingAction renameAction; private DockingAction keyBindingAction; private DockingAction helpAction; @@ -174,15 +172,12 @@ class GhidraScriptActionManager { globalRunLastAction.firePropertyChanged(DockingActionIf.DESCRIPTION_PROPERTY, "", newDesc); } - private void createActions() { - // - // 'run' actions - // - String runGroup = "1"; - runAction = new DockingAction("Run", plugin.getName()) { + private DockingAction createScriptAction(String name, String menuEntry, + String actionDescription, Icon icon, String toolBarGroup, Runnable runnable) { + DockingAction action = new DockingAction(name, plugin.getName()) { @Override public void actionPerformed(ActionContext context) { - provider.runScript(); + runnable.run(); } @Override @@ -191,188 +186,79 @@ class GhidraScriptActionManager { return contextObject instanceof ResourceFile; } }; - runAction.setPopupMenuData(new MenuData(new String[] { "Run" }, - ResourceManager.loadImage("images/play.png"), null)); - runAction.setToolBarData( - new ToolBarData(ResourceManager.loadImage("images/play.png"), runGroup)); + action.setPopupMenuData(new MenuData(new String[] { menuEntry }, icon)); + action.setToolBarData(new ToolBarData(icon, toolBarGroup)); - runAction.setDescription("Run Script"); - runAction.setEnabled(false); - plugin.getTool().addLocalAction(provider, runAction); + action.setDescription(actionDescription); + action.setEnabled(false); - runLastAction = new RerunLastScriptAction(runGroup); + plugin.getTool().addLocalAction(provider, action); + + return action; + } + + private DockingAction createScriptTableAction(String name, String actionDescription, Icon icon, + Runnable runnable) { + DockingAction action = new DockingAction(name, plugin.getName()) { + @Override + public void actionPerformed(ActionContext context) { + runnable.run(); + } + + @Override + public boolean isAddToPopup(ActionContext context) { + Object contextObject = context.getContextObject(); + return (contextObject instanceof GTable) || (contextObject instanceof ResourceFile); + } + }; + action.setPopupMenuData(new MenuData(new String[] { name }, icon)); + action.setToolBarData(new ToolBarData(icon, null)); + + action.setDescription(actionDescription); + action.setEnabled(true); + + plugin.getTool().addLocalAction(provider, action); + + return action; + } + + private void createActions() { + createScriptAction("Run", "Run Script", "Run Script", + ResourceManager.loadImage("images/play.png"), RESOURCE_FILE_ACTION_RUN_GROUP, + provider::runScript); + + runLastAction = new RerunLastScriptAction(RESOURCE_FILE_ACTION_RUN_GROUP); plugin.getTool().addLocalAction(provider, runLastAction); + globalRunLastAction = new RerunLastScriptAction("Xtra"); plugin.getTool().addAction(globalRunLastAction); - // - // End 'run' actions - // + createScriptAction("Edit", "Edit with basic editor", "Edit Script with basic editor", + ResourceManager.loadImage("images/accessories-text-editor.png"), null, + provider::editScriptBuiltin); - editAction = new DockingAction("Edit", plugin.getName()) { - @Override - public void actionPerformed(ActionContext context) { - provider.editScriptBuiltin(); - } + createScriptAction("EditEclipse", "Edit with Eclipse", "Edit Script with Eclipse", + ResourceManager.loadImage("images/eclipse.png"), null, provider::editScriptEclipse); - @Override - public boolean isEnabledForContext(ActionContext context) { - Object contextObject = context.getContextObject(); - return contextObject instanceof ResourceFile; - } - }; - editAction.setPopupMenuData(new MenuData(new String[] { "Edit with basic editor" }, - ResourceManager.loadImage("images/accessories-text-editor.png"), null)); - editAction.setToolBarData( - new ToolBarData(ResourceManager.loadImage("images/accessories-text-editor.png"), null)); - editAction.setDescription("Edit Script with basic editor"); - editAction.setEnabled(false); - plugin.getTool().addLocalAction(provider, editAction); + keyBindingAction = + createScriptAction("Key Binding", "Assign Key Binding", "Assign Key Binding", + ResourceManager.loadImage("images/key.png"), null, provider::assignKeyBinding); - eclipseAction = new DockingAction("EditEclipse", plugin.getName()) { - @Override - public void actionPerformed(ActionContext context) { - provider.editScriptEclipse(); - } + createScriptAction("Delete", "Delete", "Delete Script", + ResourceManager.loadImage("images/edit-delete.png"), null, provider::deleteScript); - @Override - public boolean isEnabledForContext(ActionContext context) { - Object contextObject = context.getContextObject(); - return contextObject instanceof ResourceFile; - } - }; - eclipseAction.setPopupMenuData(new MenuData(new String[] { "Edit with Eclipse" }, - ResourceManager.loadImage("images/eclipse.png"), null)); - eclipseAction.setToolBarData( - new ToolBarData(ResourceManager.loadImage("images/eclipse.png"), null)); - eclipseAction.setDescription("Edit Script with Eclipse"); - eclipseAction.setEnabled(false); - plugin.getTool().addLocalAction(provider, eclipseAction); + renameAction = createScriptAction("Rename", "Rename", "Rename Script", + ResourceManager.loadImage("images/textfield_rename.png"), null, provider::renameScript); - keyBindingAction = new DockingAction("Key Binding", plugin.getName()) { - @Override - public void actionPerformed(ActionContext context) { - provider.assignKeyBinding(); - } + newAction = createScriptTableAction("New", "Create New Script", + ResourceManager.loadImage("images/script_add.png"), provider::newScript); - @Override - public boolean isEnabledForContext(ActionContext context) { - Object contextObject = context.getContextObject(); - return contextObject instanceof ResourceFile; - } - }; - keyBindingAction.setPopupMenuData(new MenuData(new String[] { "Assign Key Binding" }, - ResourceManager.loadImage("images/key.png"), null)); - keyBindingAction.setToolBarData( - new ToolBarData(ResourceManager.loadImage("images/key.png"), null)); + createScriptTableAction("Refresh", "Refresh Script List", + Icons.REFRESH_ICON, provider::refresh); - keyBindingAction.setDescription("Assign Key Binding"); - keyBindingAction.setEnabled(false); - plugin.getTool().addLocalAction(provider, keyBindingAction); - - deleteAction = new DockingAction("Delete", plugin.getName()) { - @Override - public void actionPerformed(ActionContext context) { - provider.deleteScript(); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - Object contextObject = context.getContextObject(); - return contextObject instanceof ResourceFile; - } - }; - deleteAction.setPopupMenuData(new MenuData(new String[] { "Delete" }, - ResourceManager.loadImage("images/edit-delete.png"), null)); - deleteAction.setToolBarData( - new ToolBarData(ResourceManager.loadImage("images/edit-delete.png"), null)); - - deleteAction.setDescription("Delete Script"); - deleteAction.setEnabled(false); - plugin.getTool().addLocalAction(provider, deleteAction); - - renameAction = new DockingAction("Rename", plugin.getName()) { - @Override - public void actionPerformed(ActionContext context) { - provider.renameScript(); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - Object contextObject = context.getContextObject(); - return contextObject instanceof ResourceFile; - } - }; - renameAction.setPopupMenuData(new MenuData(new String[] { "Rename" }, - ResourceManager.loadImage("images/textfield_rename.png"), null)); - renameAction.setToolBarData( - new ToolBarData(ResourceManager.loadImage("images/textfield_rename.png"), null)); - - renameAction.setDescription("Rename Script"); - renameAction.setEnabled(false); - plugin.getTool().addLocalAction(provider, renameAction); - - newAction = new DockingAction("New", plugin.getName()) { - @Override - public void actionPerformed(ActionContext context) { - provider.newScript(); - } - - @Override - public boolean isAddToPopup(ActionContext context) { - Object contextObject = context.getContextObject(); - return (contextObject instanceof GTable) || (contextObject instanceof ResourceFile); - } - }; - newAction.setPopupMenuData(new MenuData(new String[] { "New" }, - ResourceManager.loadImage("images/script_add.png"), null)); - newAction.setToolBarData( - new ToolBarData(ResourceManager.loadImage("images/script_add.png"), null)); - - newAction.setDescription("Create New Script"); - newAction.setEnabled(true); - plugin.getTool().addLocalAction(provider, newAction); - - refreshAction = new DockingAction("Refresh", plugin.getName()) { - @Override - public void actionPerformed(ActionContext context) { - provider.refresh(); - } - - @Override - public boolean isAddToPopup(ActionContext context) { - Object contextObject = context.getContextObject(); - return (contextObject instanceof GTable) || (contextObject instanceof ResourceFile); - } - }; - refreshAction.setPopupMenuData( - new MenuData(new String[] { "Refresh" }, Icons.REFRESH_ICON, null)); - refreshAction.setToolBarData(new ToolBarData(Icons.REFRESH_ICON, null)); - - refreshAction.setDescription("Refresh Script List"); - refreshAction.setEnabled(true); - plugin.getTool().addLocalAction(provider, refreshAction); - - bundleStatusAction = new DockingAction("Script Directories", plugin.getName()) { - @Override - public void actionPerformed(ActionContext context) { - provider.showBundleStatusComponent(); - } - - @Override - public boolean isAddToPopup(ActionContext context) { - Object contextObject = context.getContextObject(); - return (contextObject instanceof GTable) || (contextObject instanceof ResourceFile); - } - }; - bundleStatusAction.setPopupMenuData(new MenuData(new String[] { "Bundle Status" }, - ResourceManager.loadImage("images/text_list_bullets.png"), null)); - bundleStatusAction.setToolBarData( - new ToolBarData(ResourceManager.loadImage("images/text_list_bullets.png"), null)); - - bundleStatusAction.setDescription("Bundle Status"); - bundleStatusAction.setEnabled(true); - plugin.getTool().addLocalAction(provider, bundleStatusAction); + showBundleStatusAction = createScriptTableAction("Script Directories", + "Manage Script Directories", ResourceManager.loadImage("images/text_list_bullets.png"), + provider::showBundleStatusComponent); helpAction = new DockingAction("Ghidra API Help", plugin.getName()) { @Override @@ -441,7 +327,7 @@ class GhidraScriptActionManager { } HelpLocation getPathHelpLocation() { - return new HelpLocation(plugin.getName(), bundleStatusAction.getName()); + return new HelpLocation(plugin.getName(), showBundleStatusAction.getName()); } HelpLocation getKeyBindingHelpLocation() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java index 00eb6a9ff5..60765808a7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptComponentProvider.java @@ -20,6 +20,7 @@ import java.awt.Rectangle; import java.awt.event.*; import java.io.*; import java.util.*; +import java.util.function.Predicate; import java.util.stream.Collectors; import javax.swing.*; @@ -57,13 +58,12 @@ import util.CollectionUtils; import utilities.util.FileUtilities; public class GhidraScriptComponentProvider extends ComponentProviderAdapter { + static final String WINDOW_GROUP = "Script Group"; private static final double TOP_PREFERRED_RESIZE_WEIGHT = .80; private static final String DESCRIPTION_DIVIDER_LOCATION = "DESCRIPTION_DIVIDER_LOCATION"; private static final String FILTER_TEXT = "FILTER_TEXT"; - static final String WINDOW_GROUP = "Script Group"; - private Map editorMap = new HashMap<>(); private final GhidraScriptMgrPlugin plugin; private JPanel component; @@ -126,7 +126,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { updateTitle(); } - private void build() { + private void buildCategoryTree() { scriptRoot = new RootNode(); scriptCategoryTree = new GTree(scriptRoot); @@ -159,6 +159,10 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { scriptCategoryTree.getSelectionModel() .setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + } + + private void build() { + buildCategoryTree(); tableModel = new GhidraScriptTableModel(this, infoManager); @@ -231,10 +235,11 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { component.add(dataDescriptionSplit, BorderLayout.CENTER); } - //================================================================================================== - // Inner Classes - //================================================================================================== - + /** + * Restore state for bundles, user actions, and filter. + * + * @param saveState the state object + */ public void readConfigState(SaveState saveState) { bundleHost.restoreManagedBundleState(saveState, getTool()); @@ -259,6 +264,12 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { tableFilterPanel.setFilterText(filterText); } + /** + * Save state for bundles, user actions, and filter. + * + * @param saveState the state object + */ + public void writeConfigState(SaveState saveState) { bundleHost.saveManagedBundleState(saveState); @@ -283,6 +294,9 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { bundleStatusComponentProvider.dispose(); } + /** + * @return the bundle host used for scripting, ultimately from {@link GhidraScriptUtil#getBundleHost()} + */ public BundleHost getBundleHost() { return bundleHost; } @@ -299,10 +313,6 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { return editorMap; } - void showBundleStatusComponent() { - bundleStatusComponentProvider.setVisible(true); - } - void assignKeyBinding() { ResourceFile script = getSelectedScript(); ScriptAction action = actionManager.createAction(script); @@ -360,82 +370,72 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { renameScriptByCopying(script, provider, renameFile); } + /** + * Copy a script, renaming references to the class name. + * + * @param sourceScript source script + * @param destinationScript destination script + * @throws IOException if we fail to create temp, write contents, copy, or delete temp + */ + private void copyScript(ResourceFile sourceScript, ResourceFile destinationScript) + throws IOException { + String oldClassName = GhidraScriptUtil.getBaseName(sourceScript); + String newClassName = GhidraScriptUtil.getBaseName(destinationScript); + + ResourceFile parentFile = sourceScript.getParentFile(); + ResourceFile temp = new ResourceFile(parentFile, "ghidraScript.tmp"); + try (PrintWriter writer = new PrintWriter(temp.getOutputStream())) { + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(sourceScript.getInputStream()))) { + while (true) { + String line = reader.readLine(); + if (line == null) { + break; + } + writer.println(line.replaceAll(oldClassName, newClassName)); + } + } + } + FileUtilities.copyFile(temp, destinationScript, TaskMonitor.DUMMY); + temp.delete(); + } + private void renameScriptByCopying(ResourceFile script, GhidraScriptProvider provider, ResourceFile renameFile) { - String oldClassName = GhidraScriptUtil.getBaseName(script); - String newClassName = GhidraScriptUtil.getBaseName(renameFile); - - ResourceFile temp = null; - PrintWriter writer = null; - BufferedReader reader = null; try { - - ResourceFile parentFile = script.getParentFile(); - temp = new ResourceFile(parentFile, "ghidraScript.tmp"); - writer = new PrintWriter(temp.getOutputStream()); - reader = new BufferedReader(new InputStreamReader(script.getInputStream())); - while (true) { - String line = reader.readLine(); - if (line == null) { - break; - } - writer.println(line.replaceAll(oldClassName, newClassName)); - } - reader.close(); - writer.close(); - - FileUtilities.copyFile(temp, renameFile, TaskMonitor.DUMMY); - - if (!renameFile.exists()) { - Msg.showWarn(getClass(), getComponent(), "Unable to rename script", - "The rename operation failed.\nPlease check file permissions."); - return; - } - - if (!provider.deleteScript(script)) { - Msg.showWarn(getClass(), getComponent(), "Unable to rename script", - "Unable to remove original file.\nPlease check file permissions."); - renameFile.delete(); - return; - } - infoManager.removeMetadata(script); - - if (actionManager.hasScriptAction(script)) { - KeyStroke ks = actionManager.getKeyBinding(script); - actionManager.removeAction(script); - ScriptAction action = actionManager.createAction(renameFile); - action.setKeyBindingData(new KeyBindingData(ks)); - } - - assert !infoManager - .containsMetadata(renameFile) : "renamed script already has metadata"; - infoManager.getScriptInfo(renameFile); - - tableModel.switchScript(script, renameFile); - setSelectedScript(renameFile); + copyScript(script, renameFile); } catch (IOException e) { Msg.showError(getClass(), getComponent(), "Unable to rename script", e.getMessage()); return; } - finally { - if (reader != null) { - try { - reader.close(); - } - catch (IOException e) { - // we tried - } - } - if (writer != null) { - writer.close(); - } - - if (temp != null) { - temp.delete(); - } + if (!renameFile.exists()) { + Msg.showWarn(getClass(), getComponent(), "Unable to rename script", + "The rename operation failed.\nPlease check file permissions."); + return; } + + if (!provider.deleteScript(script)) { + Msg.showWarn(getClass(), getComponent(), "Unable to rename script", + "Unable to remove original file.\nPlease check file permissions."); + renameFile.delete(); + return; + } + infoManager.removeMetadata(script); + + if (actionManager.hasScriptAction(script)) { + KeyStroke ks = actionManager.getKeyBinding(script); + actionManager.removeAction(script); + ScriptAction action = actionManager.createAction(renameFile); + action.setKeyBindingData(new KeyBindingData(ks)); + } + + assert !infoManager.containsMetadata(renameFile) : "renamed script already has metadata"; + infoManager.getScriptInfo(renameFile); + + tableModel.switchScript(script, renameFile); + setSelectedScript(renameFile); } JTable getTable() { @@ -450,19 +450,27 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { return tableModel.getScriptAt(tableFilterPanel.getModelRow(viewRowIndex)); } + /** + * @return enabled bundle paths from the scripting bundle host + */ public List getScriptDirectories() { return bundleHost.getGhidraBundles() .stream() - .filter(bundle -> bundle.isEnabled() && bundle instanceof GhidraSourceBundle) + .filter(GhidraSourceBundle.class::isInstance) + .filter(GhidraBundle::isEnabled) .map(GhidraBundle::getPath) .collect(Collectors.toList()); } + /** + * @return non-system bundle paths from the scripting bundle host + */ public List getWritableScriptDirectories() { return bundleHost.getGhidraBundles() .stream() .filter(GhidraSourceBundle.class::isInstance) - .filter(bundle -> !bundle.isSystemBundle()) + .filter(Predicate.not(GhidraBundle::isSystemBundle)) + .filter(GhidraBundle::isEnabled) .map(GhidraBundle::getPath) .collect(Collectors.toList()); } @@ -496,7 +504,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { } else { Msg.showInfo(getClass(), getComponent(), getName(), - "Unable to delete script '" + script.getName() + "'" + "\n" + + "Unable to delete script '" + script.getName() + "'\n" + "Please verify the file permissions."); } } @@ -521,7 +529,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { } } - public void enableScriptDirectory(ResourceFile scriptDir) { + void enableScriptDirectory(ResourceFile scriptDir) { bundleHost.enablePath(scriptDir); Msg.showInfo(this, getComponent(), "Script Path Added/Enabled", "The directory has been automatically enabled for use:\n" + @@ -699,6 +707,10 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter { return categoryPath; } + void showBundleStatusComponent() { + bundleStatusComponentProvider.setVisible(true); + } + class RefreshingBundleHostListener implements BundleHostListener { @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptEditorComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptEditorComponentProvider.java index c38b19c17b..f5334e3bc6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptEditorComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptEditorComponentProvider.java @@ -41,9 +41,8 @@ import resources.Icons; import resources.ResourceManager; public class GhidraScriptEditorComponentProvider extends ComponentProvider { - - private static final int MAX_UNDO_REDO_SIZE = 50; - + static final String EDITOR_COMPONENT_NAME="EDITOR"; + static final String CHANGE_DESTINATION_TITLE = "Where Would You Like to Store Your Changes?"; static final String FILE_ON_DISK_CHANGED_TITLE = "File Changed on Disk"; static final String FILE_ON_DISK_MISSING_TITLE = "File on Disk is Missing"; @@ -53,7 +52,9 @@ public class GhidraScriptEditorComponentProvider extends ComponentProvider { static final String KEEP_CHANGES_TEXT = "Keep Changes"; static final String DISCARD_CHANGES_TEXT = "Discard Changes"; - static Font defaultFont = new Font("monospaced", Font.PLAIN, 12); + private static final int MAX_UNDO_REDO_SIZE = 50; + + private static Font defaultFont = new Font("monospaced", Font.PLAIN, 12); static void restoreState(SaveState saveState) { String name = saveState.getString("DEFAULT_FONT_NAME", "Monospaced"); @@ -68,6 +69,7 @@ public class GhidraScriptEditorComponentProvider extends ComponentProvider { saveState.putInt("DEFAULT_FONT_SIZE", defaultFont.getSize()); } + private GhidraScriptMgrPlugin plugin; private GhidraScriptComponentProvider provider; private String title; @@ -675,7 +677,7 @@ public class GhidraScriptEditorComponentProvider extends ComponentProvider { super(text); setFont(defaultFont); - setName("EDITOR"); + setName(EDITOR_COMPONENT_NAME); setWrapStyleWord(false); Document document = getDocument(); document.addUndoableEditListener(e -> { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin.java index 309bd455f2..cdf84677ca 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptMgrPlugin.java @@ -26,8 +26,7 @@ import ghidra.app.plugin.ProgramPlugin; import ghidra.app.plugin.core.eclipse.EclipseConnection; 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.*; import ghidra.app.services.*; import ghidra.framework.options.SaveState; import ghidra.framework.options.ToolOptions; @@ -49,12 +48,17 @@ import ghidra.util.task.TaskListener; ) //@formatter:on public class GhidraScriptMgrPlugin extends ProgramPlugin implements GhidraScriptService { + private static int loaded = 0; private final GhidraScriptComponentProvider provider; - private static int loaded = 0; private final BundleHost bundleHost; + /** + * {@link GhidraScriptMgrPlugin} is the entry point for all {@link GhidraScript} capabilities. + * + * @param tool the tool this plugin is added to + */ public GhidraScriptMgrPlugin(PluginTool tool) { super(tool, true, true, true); if (loaded == 0) { @@ -111,6 +115,11 @@ public class GhidraScriptMgrPlugin extends ProgramPlugin implements GhidraScript provider.runScript(scriptName, listener); } + /** + * Attempts to run a script in a {@link RunScriptTask}. + * + * @param scriptFile the script's source file + */ public void runScript(ResourceFile scriptFile) { provider.runScript(scriptFile); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptTableModel.java index 72f9718b3d..f3a17d0f87 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/GhidraScriptTableModel.java @@ -35,13 +35,12 @@ import ghidra.util.table.column.GColumnRenderer; import resources.Icons; class GhidraScriptTableModel extends GDynamicColumnTableModel { + static final String SCRIPT_ACTION_COLUMN_NAME = "In Tool"; + static final String SCRIPT_STATUS_COLUMN_NAME = "Status"; private static final String EMPTY_STRING = ""; private static final ImageIcon ERROR_IMG = Icons.ERROR_ICON; - static final String SCRIPT_ACTION_COLUMN_NAME = "In Tool"; - static final String SCRIPT_STATUS_COLUMN_NAME = "Status"; - private GhidraScriptComponentProvider provider; private List scriptList = new ArrayList<>(); private final GhidraScriptInfoManager infoManager; @@ -276,7 +275,7 @@ class GhidraScriptTableModel extends GDynamicColumnTableModel providers; private ListPanel listPanel; private JComponent parent; private boolean wasCancelled; - private static String lastSelectedProviderDescription; - PickProviderDialog(JComponent parent, HelpLocation help) { super("New Script: Type"); this.parent = parent; @@ -55,6 +55,12 @@ public class PickProviderDialog extends DialogComponentProvider { setHelpLocation(help); } + /** + * Constructor used in testing only! + * + * @param testItems values to populate model with + * @param defaultItem the default selection + */ public PickProviderDialog(List testItems, String defaultItem) { super("New Script: Type"); @@ -76,6 +82,8 @@ public class PickProviderDialog extends DialogComponentProvider { /** * For testing... + * + * @param provider the provider selection */ void setSelectedProvider(GhidraScriptProvider provider) { listPanel.setSelectedValue(provider); @@ -95,6 +103,9 @@ public class PickProviderDialog extends DialogComponentProvider { return (GhidraScriptProvider) listPanel.getSelectedValue(); } + /** + * close any open dialog + */ public void dispose() { close(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/SaveDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/SaveDialog.java index 0ac87bd269..ce9980c9cb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/SaveDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/SaveDialog.java @@ -37,6 +37,8 @@ import ghidra.util.HelpLocation; public class SaveDialog extends DialogComponentProvider implements ListSelectionListener { protected GhidraScriptComponentProvider componentProvider; + protected ResourceFile scriptFile; + private GhidraScriptProvider provider; private List paths; @@ -44,15 +46,22 @@ public class SaveDialog extends DialogComponentProvider implements ListSelection private JTextField nameField; private boolean cancelled; - protected ResourceFile scriptFile; - - public SaveDialog(Component parent, String title, - GhidraScriptComponentProvider componentProvider, ResourceFile scriptFile, - HelpLocation help) { + SaveDialog(Component parent, String title, GhidraScriptComponentProvider componentProvider, + ResourceFile scriptFile, HelpLocation help) { this(parent, title, componentProvider, componentProvider.getWritableScriptDirectories(), scriptFile, help); } + /** + * Only called directly from testing! + * + * @param parent parent component + * @param title dialog title + * @param componentProvider the provider + * @param scriptDirs list of directories to give as options when saving + * @param scriptFile the default save location + * @param help contextual help, e.g. for rename or save + */ public SaveDialog(Component parent, String title, GhidraScriptComponentProvider componentProvider, List scriptDirs, ResourceFile scriptFile, HelpLocation help) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/ScriptCategoryNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/ScriptCategoryNode.java index 018f98657f..b202e9504b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/ScriptCategoryNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/script/ScriptCategoryNode.java @@ -27,7 +27,7 @@ public class ScriptCategoryNode extends GTreeNode { private final String name; - public ScriptCategoryNode(String name) { + ScriptCategoryNode(String name) { this.name = name; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java index 9aad8c20b8..a32f2731a5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java @@ -129,6 +129,9 @@ import ghidra.util.task.TaskMonitor; * @see ghidra.program.model.listing.Program */ public abstract class GhidraScript extends FlatProgramAPI { + // Stores last-selected value for askXxx() methods, used to pre-populate askXxx() + // GUI dialogs if they are run more than once + private static Map, Object>> askMap = new HashMap<>(); protected ResourceFile sourceFile; protected GhidraState state; @@ -174,16 +177,19 @@ public abstract class GhidraScript extends FlatProgramAPI { SUSPENDED } - // Stores last-selected value for askXxx() methods, used to pre-populate askXxx() - // GUI dialogs if they are run more than once - private static Map, Object>> askMap = new HashMap<>(); - /** * The run method is where the script specific code is placed. * @throws Exception if any exception occurs. */ protected abstract void run() throws Exception; + /** + * Set the context for this script. + * + * @param state state object + * @param monitor the monitor to use during run + * @param writer the target of script "print" statements + */ public final void set(GhidraState state, TaskMonitor monitor, PrintWriter writer) { this.state = state; this.monitor = monitor; @@ -191,6 +197,14 @@ public abstract class GhidraScript extends FlatProgramAPI { loadVariablesFromState(); } + /** + * Execute/run script and {@link #doCleanup} afterwards. + * + * @param runState state object + * @param runMonitor the monitor to use during run + * @param runWriter the target of script "print" statements + * @throws Exception if the script excepts + */ public final void execute(GhidraState runState, TaskMonitor runMonitor, PrintWriter runWriter) throws Exception { boolean success = false; @@ -203,8 +217,8 @@ public abstract class GhidraScript extends FlatProgramAPI { } } - private final void doExecute(GhidraState runState, TaskMonitor runMonitor, - PrintWriter runWriter) throws Exception { + private void doExecute(GhidraState runState, TaskMonitor runMonitor, PrintWriter runWriter) + throws Exception { this.state = runState; this.monitor = runMonitor; this.writer = runWriter; @@ -478,6 +492,11 @@ public abstract class GhidraScript extends FlatProgramAPI { return state; } + /** + * Set the script {@link #currentAddress}, {@link #currentLocation}, and update state object. + * + * @param address the new address + */ public final void setCurrentLocation(Address address) { state.setCurrentAddress(address); this.currentAddress = address; @@ -545,8 +564,8 @@ public abstract class GhidraScript extends FlatProgramAPI { if (isRunningHeadless()) { // only change client authenticator in headless mode try { - HeadlessClientAuthenticator.installHeadlessClientAuthenticator( - ClientUtil.getUserName(), null, false); + HeadlessClientAuthenticator + .installHeadlessClientAuthenticator(ClientUtil.getUserName(), null, false); } catch (IOException e) { throw new RuntimeException("Unexpected Exception", e); @@ -1408,7 +1427,7 @@ public abstract class GhidraScript extends FlatProgramAPI { // Tests if text actually equals "true" or "false String tempBool = analysisOptionValue.toLowerCase(); - if (tempBool.equals("true") || tempBool.equals("false")) { + if ("true".equals(tempBool) || "false".equals(tempBool)) { options.setBoolean(analysisOption, Boolean.valueOf(tempBool)); } @@ -1785,13 +1804,13 @@ public abstract class GhidraScript extends FlatProgramAPI { } else { try { - SwingUtilities.invokeAndWait( - () -> Msg.showInfo(getClass(), null, name, message)); + SwingUtilities + .invokeAndWait(() -> Msg.showInfo(getClass(), null, name, message)); } - catch (InterruptedException e1) { + catch (InterruptedException e) { // shouldn't happen } - catch (InvocationTargetException e1) { + catch (InvocationTargetException e) { // shouldn't happen } } @@ -1970,7 +1989,7 @@ public abstract class GhidraScript extends FlatProgramAPI { } private interface CancellableFunction { - public R apply(T t) throws CancelledException; + R apply(T t) throws CancelledException; } /** @@ -2778,10 +2797,10 @@ public abstract class GhidraScript extends FlatProgramAPI { * @throws IllegalArgumentException if the parsed value is not a valid double. */ public double parseDouble(String val) { - if (val.equalsIgnoreCase("pi")) { + if ("pi".equalsIgnoreCase(val)) { return Math.PI; } - if (val.equalsIgnoreCase("e")) { + if ("e".equalsIgnoreCase(val)) { return Math.E; } try { @@ -3254,7 +3273,7 @@ public abstract class GhidraScript extends FlatProgramAPI { * @throws IllegalArgumentException if the parsed value is not a valid boolean. */ public Boolean parseBoolean(String val) { - if (val.equalsIgnoreCase("true") || val.equalsIgnoreCase("false")) { + if ("true".equalsIgnoreCase(val) || "false".equalsIgnoreCase(val)) { return Boolean.parseBoolean(val); } throw new IllegalArgumentException("Invalid boolean: " + val); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptInfoManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptInfoManager.java index 4520d383af..fe6ef5c804 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptInfoManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptInfoManager.java @@ -28,6 +28,9 @@ public class GhidraScriptInfoManager { private Map scriptFileToInfoMap = new HashMap<>(); private Map> scriptNameToFilesMap = new HashMap<>(); + /** + * clear stored metadata + */ public void dispose() { clearMetadata(); } @@ -110,6 +113,12 @@ public class GhidraScriptInfoManager { return scriptFileToInfoMap.containsKey(scriptFile); } + /** + * Get {@link ScriptInfo} for {@code script} under the assumption that it's already managed. + * + * @param script the script + * @return info or null if the assumption was wrong. If null is returned, an error dialog is shown + */ public ScriptInfo getExistingScriptInfo(ResourceFile script) { ScriptInfo info = scriptFileToInfoMap.get(script); if (info == null) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProperties.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProperties.java index cbb6ae31f3..eef2cb8191 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProperties.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptProperties.java @@ -33,7 +33,7 @@ public class GhidraScriptProperties { private HashMap propertiesMap; private String baseName; - public GhidraScriptProperties() { + GhidraScriptProperties() { propertiesMap = new HashMap<>(); } @@ -73,6 +73,9 @@ public class GhidraScriptProperties { } } + /** + * @return the properties file name + */ public String getFilename() { return baseName + ".properties"; } @@ -138,6 +141,10 @@ public class GhidraScriptProperties { return propertiesMap.put(key.trim(), value); } + /** + * @param keyString the property name + * @return the value of the key in the properties file, or an empty string if no property exists + */ public String getValue(String keyString) { if (propertiesMap.size() == 0) { @@ -151,10 +158,19 @@ public class GhidraScriptProperties { return ""; } + /** + * @return true if there are no properties + */ public boolean isEmpty() { return (propertiesMap.size() == 0); } + /** + * Remove the named property + * + * @param keyString the property name + * @return the previous value or null + */ protected String remove(String keyString) { return propertiesMap.remove(keyString); } @@ -163,14 +179,25 @@ public class GhidraScriptProperties { propertiesMap.clear(); } + /** + * @param keyString a property name + * @return true if the key exists in the property file + */ public boolean containsKey(String keyString) { return propertiesMap.containsKey(keyString); } + /** + * @param valueString a value string + * @return true if any property has the given value + */ public boolean containsValue(String valueString) { return propertiesMap.containsValue(valueString); } + /** + * @return the property names for all properties + */ public Set keySet() { return propertiesMap.keySet(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUnsupportedClassVersionError.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUnsupportedClassVersionError.java index fbff17c54a..3c977a40c6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUnsupportedClassVersionError.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUnsupportedClassVersionError.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +19,7 @@ import generic.jar.ResourceFile; class GhidraScriptUnsupportedClassVersionError extends RuntimeException { - private ResourceFile classFile; + private final ResourceFile classFile; GhidraScriptUnsupportedClassVersionError(UnsupportedClassVersionError cause, ResourceFile classFile) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java index 0478bb1509..6c1e24768d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScriptUtil.java @@ -15,7 +15,8 @@ */ package ghidra.app.script; -import java.io.*; +import java.io.File; +import java.io.IOException; import java.nio.file.Files; import java.util.*; import java.util.stream.Collectors; @@ -31,31 +32,38 @@ import ghidra.util.classfinder.ClassSearcher; * A utility class for managing script directories and ScriptInfo objects. */ public class GhidraScriptUtil { - - private static final String SCRIPTS_SUBDIR_NAME = "ghidra_scripts"; - private static final String DEV_SCRIPTS_SUBDIR_NAME = "developer_scripts"; - - private static List providers = null; - /** * User's home scripts directory */ public static String USER_SCRIPTS_DIR = buildUserScriptsDirectory(); - static BundleHost _bundleHost; + private static BundleHost bundleHost; + private static final String SCRIPTS_SUBDIR_NAME = "ghidra_scripts"; + private static final String DEV_SCRIPTS_SUBDIR_NAME = "developer_scripts"; + + private static List providers; + + /** + * @return the bundle host used for scripting + */ public static BundleHost getBundleHost() { - return _bundleHost; + return bundleHost; } - private static void setBundleHost(BundleHost bundleHost) { - if (_bundleHost != null) { + /** + * set the bundle host and start the framework + * + * @param aBundleHost the bundle host + */ + private static void setBundleHost(BundleHost aBundleHost) { + if (bundleHost != null) { throw new RuntimeException("GhidraScriptUtil initialized multiple times!"); } try { - _bundleHost = bundleHost; - _bundleHost.startFramework(); + bundleHost = aBundleHost; + bundleHost.startFramework(); } catch (OSGiException | IOException e) { e.printStackTrace(); @@ -66,26 +74,29 @@ public class GhidraScriptUtil { /** * initialize state of GhidraScriptUtil with user, system paths, and optional extra system paths. * - * @param bundleHost the host to use + * @param aBundleHost the host to use * @param extraSystemPaths additional system paths for this run, can be null * */ - public static void initialize(BundleHost bundleHost, List extraSystemPaths) { - setBundleHost(bundleHost); + public static void initialize(BundleHost aBundleHost, List extraSystemPaths) { + setBundleHost(aBundleHost); if (extraSystemPaths != null) { for (String path : extraSystemPaths) { bundleHost.add(new ResourceFile(path), true, true); } } - bundleHost.add(GhidraScriptUtil.getUserScriptDirectory(), true, false); - bundleHost.add(GhidraScriptUtil.getSystemScriptPaths(), true, true); + bundleHost.add(getUserScriptDirectory(), true, false); + bundleHost.add(getSystemScriptPaths(), true, true); } + /** + * dispose of the bundle host and providers list + */ public static void dispose() { - if (_bundleHost != null) { - _bundleHost.dispose(); - _bundleHost = null; + if (bundleHost != null) { + bundleHost.dispose(); + bundleHost = null; } providers = null; } @@ -95,8 +106,10 @@ public class GhidraScriptUtil { * @return a list of the current script directories */ public static List getScriptSourceDirectories() { - return _bundleHost.getBundlePaths().stream().filter(ResourceFile::isDirectory).collect( - Collectors.toList()); + return bundleHost.getBundlePaths() + .stream() + .filter(ResourceFile::isDirectory) + .collect(Collectors.toList()); } public static ResourceFile getSourceDirectoryContaining(ResourceFile sourceFile) { @@ -199,8 +212,10 @@ public class GhidraScriptUtil { @Deprecated public static List getExplodedCompiledSourceBundlePaths() { try { - return Files.list(BundleHost.getOsgiDir()).filter(Files::isDirectory).map( - x -> new ResourceFile(x.toFile())).collect(Collectors.toList()); + return Files.list(BundleHost.getOsgiDir()) + .filter(Files::isDirectory) + .map(x -> new ResourceFile(x.toFile())) + .collect(Collectors.toList()); } catch (IOException e) { Msg.showError(GhidraScriptUtil.class, null, "error", @@ -231,7 +246,7 @@ public class GhidraScriptUtil { * @return a list of all Ghidra script providers */ // Note: this method is synchronized so that two threads do not try to create the list when null - public synchronized static List getProviders() { + public static synchronized List getProviders() { if (providers == null) { List newProviders = new ArrayList<>(ClassSearcher.getInstances(GhidraScriptProvider.class)); @@ -338,8 +353,7 @@ public class GhidraScriptUtil { return path + ".java"; } - static ResourceFile findScriptFileInPaths( - Collection scriptDirectories, + static ResourceFile findScriptFileInPaths(Collection scriptDirectories, String filename) { String validatedName = fixupName(filename); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraState.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraState.java index 1cb9c5855e..b04c4dbf31 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraState.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraState.java @@ -41,7 +41,7 @@ public class GhidraState { private ProgramSelection currentSelection; private ProgramSelection currentHighlight; private HashMap envmap = new HashMap<>(); - private GatherParamPanel gatherParamPanel = null; + private GatherParamPanel gatherParamPanel; private Project project; private final boolean isGlobalState; @@ -105,6 +105,7 @@ public class GhidraState { /** * Sets the current program. + * @param program the new program object */ public void setCurrentProgram(Program program) { if (program == currentProgram) { @@ -117,11 +118,19 @@ public class GhidraState { gatherParamPanel.currentProgramChanged(); } + /** + * @return the address of the current location + */ public Address getCurrentAddress() { return currentLocation != null ? currentLocation.getAddress() : null; } + /** + * If it differs, set the current location to the given address and fire a {@link ProgramLocationPluginEvent}. + * + * @param address the address + */ public void setCurrentAddress(Address address) { if (SystemUtilities.isEqual(address, getCurrentAddress())) { return; @@ -129,10 +138,18 @@ public class GhidraState { setCurrentLocation(new ProgramLocation(currentProgram, address)); } + /** + * @return the current location + */ public ProgramLocation getCurrentLocation() { return currentLocation; } + /** + * If it differs, set the current location and fire a {@link ProgramLocationPluginEvent}. + * + * @param location + */ public void setCurrentLocation(ProgramLocation location) { if (SystemUtilities.isEqual(currentLocation, location)) { return; @@ -144,10 +161,18 @@ public class GhidraState { } } + /** + * @return the currently highlighted selection + */ public ProgramSelection getCurrentHighlight() { return currentHighlight; } + /** + * Set the currently highlighted selection and fire a {@link ProgramHighlightPluginEvent}. + * + * @param highlight the selection + */ public void setCurrentHighlight(ProgramSelection highlight) { if (SystemUtilities.isEqual(currentHighlight, highlight)) { return; @@ -162,10 +187,18 @@ public class GhidraState { } } + /** + * @return the current selection + */ public ProgramSelection getCurrentSelection() { return currentSelection; } + /** + * Set the current selection and fire a {@link kProgramSelectionPluginEvent}. + * + * @param selection the selection + */ public void setCurrentSelection(ProgramSelection selection) { if (SystemUtilities.isEqual(currentSelection, selection)) { return; @@ -181,27 +214,27 @@ public class GhidraState { } public void addEnvironmentVar(String name, byte value) { - envmap.put(name, new Byte(value)); + envmap.put(name, Byte.valueOf(value)); } public void addEnvironmentVar(String name, short value) { - envmap.put(name, new Short(value)); + envmap.put(name, Short.valueOf(value)); } public void addEnvironmentVar(String name, int value) { - envmap.put(name, new Integer(value)); + envmap.put(name, Integer.valueOf(value)); } public void addEnvironmentVar(String name, long value) { - envmap.put(name, new Long(value)); + envmap.put(name, Long.valueOf(value)); } public void addEnvironmentVar(String name, float value) { - envmap.put(name, new Float(value)); + envmap.put(name, Float.valueOf(value)); } public void addEnvironmentVar(String name, double value) { - envmap.put(name, new Double(value)); + envmap.put(name, Double.valueOf(value)); } public void addEnvironmentVar(String name, Object value) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java index b2ca70d44f..815b9ad87e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/JavaScriptProvider.java @@ -24,18 +24,27 @@ import ghidra.app.plugin.core.osgi.*; import ghidra.util.Msg; public class JavaScriptProvider extends GhidraScriptProvider { - final private BundleHost _bundleHost; + private final BundleHost bundleHost; + /** + * Create a new {@link JavaScriptProvider} associated with the current bundle host used by scripting. + */ public JavaScriptProvider() { - _bundleHost = GhidraScriptUtil.getBundleHost(); + bundleHost = GhidraScriptUtil.getBundleHost(); } + /** + * Get the {@link GhidraSourceBundle} containing the given source file, assuming it already exists. + * + * @param sourceFile the source file + * @return the bundle + */ public GhidraSourceBundle getBundleForSource(ResourceFile sourceFile) { ResourceFile sourceDir = GhidraScriptUtil.getSourceDirectoryContaining(sourceFile); if (sourceDir == null) { return null; } - return (GhidraSourceBundle) _bundleHost.getExistingGhidraBundle(sourceDir); + return (GhidraSourceBundle) bundleHost.getExistingGhidraBundle(sourceDir); } @Override @@ -53,7 +62,7 @@ public class JavaScriptProvider extends GhidraScriptProvider { try { Bundle osgiBundle = getBundleForSource(sourceFile).getOSGiBundle(); if (osgiBundle != null) { - _bundleHost.deactivateSynchronously(osgiBundle); + bundleHost.deactivateSynchronously(osgiBundle); } } catch (GhidraBundleException | InterruptedException e) { @@ -92,13 +101,22 @@ public class JavaScriptProvider extends GhidraScriptProvider { } } + /** + * Activate and build the {@link GhidraSourceBundle} containing {@code sourceFile} + * then load the script's class from its class loader. + * + * @param sourceFile the source file + * @param writer the target for build messages + * @return the loaded {@link Class} object + * @throws Exception if build, activation, or class loading fail + */ public Class loadClass(ResourceFile sourceFile, PrintWriter writer) throws Exception { GhidraSourceBundle gb = getBundleForSource(sourceFile); gb.build(writer); - - Bundle b = _bundleHost.install(gb); - _bundleHost.activateSynchronously(b); + Bundle b = bundleHost.install(gb); + + bundleHost.activateSynchronously(b); String classname = gb.classNameForScript(sourceFile); Class clazz = b.loadClass(classname); // throws ClassNotFoundException diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/ResourceFileJavaFileManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/ResourceFileJavaFileManager.java index 1deec3632b..3c4f05d67f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/ResourceFileJavaFileManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/ResourceFileJavaFileManager.java @@ -34,11 +34,18 @@ public class ResourceFileJavaFileManager implements JavaFileManager { private StandardJavaFileManager fileManager; private List sourceDirs; - private Set avoid; + private Set filesToAvoid; - public ResourceFileJavaFileManager(List sourceDirs, Set avoid) { + /** + * Create a {@link JavaFileManager} for use by the {@link JavaCompiler}. + * + * @param sourceDirs the directories containing source + * @param filesToAvoid known "bad" files to hide from the compiler + */ + public ResourceFileJavaFileManager(List sourceDirs, + Set filesToAvoid) { this.sourceDirs = sourceDirs; - this.avoid = avoid; + this.filesToAvoid = filesToAvoid; JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler(); if (javaCompiler == null) { throw new AssertException("Can't find java compiler"); @@ -79,7 +86,7 @@ public class ResourceFileJavaFileManager implements JavaFileManager { private void gatherFiles(ResourceFile root, ResourceFile file, List accumulator, Set kinds, boolean recurse) { List listFiles = new ArrayList<>(Arrays.asList(file.listFiles())); - listFiles.removeAll(avoid); + listFiles.removeAll(filesToAvoid); for (ResourceFile resourceFile : listFiles) { if (resourceFile.isDirectory()) { if (recurse) { @@ -154,7 +161,7 @@ public class ResourceFileJavaFileManager implements JavaFileManager { @Override public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException { - if (!location.equals(StandardLocation.SOURCE_PATH) || className.equals("module-info")) { + if (!location.equals(StandardLocation.SOURCE_PATH) || "module-info".equals(className)) { // Our Ghidra scripts will not use Java 9's module definition file (module-info.java). return fileManager.getJavaFileForInput(location, className, kind); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/ResourceFileJavaFileObject.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/ResourceFileJavaFileObject.java index 08a9a98c44..8531ea7896 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/ResourceFileJavaFileObject.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/ResourceFileJavaFileObject.java @@ -20,6 +20,7 @@ import java.net.URI; import javax.lang.model.element.Modifier; import javax.lang.model.element.NestingKind; +import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import generic.jar.ResourceFile; @@ -35,6 +36,13 @@ public class ResourceFileJavaFileObject implements JavaFileObject { private String pathName; private Kind kind; + /** + * Represents a {@link ResourceFile} for a {@link JavaCompiler} via a {@link ResourceFileJavaFileManager} + * + * @param sourceRoot the root source directory + * @param file the file + * @param kind the kind + */ public ResourceFileJavaFileObject(ResourceFile sourceRoot, ResourceFile file, Kind kind) { this.file = file; this.kind = kind; @@ -43,6 +51,9 @@ public class ResourceFileJavaFileObject implements JavaFileObject { pathName = file.getAbsolutePath().substring(sourceRootPath.length() + 1); } + /** + * @return the {@link ResourceFile} this object represents + */ public ResourceFile getFile() { return file; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptInfo.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptInfo.java index 2c18083f5e..c38e1129c5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptInfo.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptInfo.java @@ -36,12 +36,8 @@ import resources.ResourceManager; * This class parses the meta-data about a script. */ public class ScriptInfo { - - private static final Pattern DOCUMENTATION_START = Pattern.compile("/\\*"); - private static final Pattern DOCUMENTATION_END = Pattern.compile("\\*/"); - /** - * The delimiter used to categories and menu paths. + * The delimiter used in categories and menu paths. */ public static final String DELIMITTER = "."; @@ -51,6 +47,9 @@ public class ScriptInfo { static final String AT_MENUPATH = "@menupath"; static final String AT_TOOLBAR = "@toolbar"; + private static final Pattern DOCUMENTATION_START = Pattern.compile("/\\*"); + private static final Pattern DOCUMENTATION_END = Pattern.compile("\\*/"); + // omit from METADATA to avoid pre-populating in new scripts private static final String AT_IMPORTPACKAGE = "@importpackage"; @@ -231,7 +230,7 @@ public class ScriptInfo { description = buffer.toString(); modified = sourceFile.lastModified(); } - catch (Exception e) { + catch (IOException e) { Msg.debug(this, "Unexpected exception reading script: " + sourceFile, e); } finally { @@ -403,6 +402,9 @@ public class ScriptInfo { return keyBinding; } + /** + * @return an error resulting from parsing keybinding metadata + */ public String getKeyBindingErrorMessage() { return keybindingErrorMessage; } @@ -460,7 +462,6 @@ public class ScriptInfo { String space = HTML_SPACE; String htmlAuthor = bold("Author:") + space + escapeHTML(toString(author)); String htmlCategory = bold("Category:") + space + escapeHTML(toString(category)); - String htmlKeyBinding = bold("Key Binding:") + space + getKeybindingToolTip(); String htmlMenuPath = bold("Menu Path:") + space + escapeHTML(toString(menupath)); @@ -505,10 +506,16 @@ public class ScriptInfo { return StringUtils.defaultString(joined); } + /** + * @return true if the script either has compiler errors, or is a duplicate + */ public boolean hasErrors() { return isCompileErrors() || isDuplicate(); } + /** + * @return a generic error message + */ public String getErrorMessage() { if (isCompileErrors()) { return "Script contains compiler errors"; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/StringTransformer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/StringTransformer.java index 25b8983204..5ebbe7594f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/StringTransformer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/StringTransformer.java @@ -16,6 +16,5 @@ package ghidra.app.script; public interface StringTransformer { - - public T apply(String s); + T apply(String s); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java index 724d9afcae..7927919d60 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/script/AbstractGhidraScriptMgrPluginTest.java @@ -174,11 +174,11 @@ public abstract class AbstractGhidraScriptMgrPluginTest env.dispose(); } - static protected void wipe(ResourceFile path) throws IOException { + protected static void wipe(ResourceFile path) throws IOException { wipe(Paths.get(path.getAbsolutePath())); } - static protected void wipe(Path path) throws IOException { + protected static void wipe(Path path) throws IOException { if (Files.exists(path)) { for (Path p : (Iterable) Files.walk(path) .sorted(Comparator.reverseOrder())::iterator) { @@ -392,7 +392,7 @@ public abstract class AbstractGhidraScriptMgrPluginTest assertNotNull(editor); - editorTextArea = (JTextArea) findComponentByName(editor.getComponent(), "EDITOR"); + editorTextArea = (JTextArea) findComponentByName(editor.getComponent(), GhidraScriptEditorComponentProvider.EDITOR_COMPONENT_NAME); assertNotNull(editorTextArea); buffer = new StringBuffer(editorTextArea.getText()); @@ -411,7 +411,7 @@ public abstract class AbstractGhidraScriptMgrPluginTest // initialize our editor variable to the newly opened editor editor = waitForComponentProvider(GhidraScriptEditorComponentProvider.class); - editorTextArea = (JTextArea) findComponentByName(editor.getComponent(), "EDITOR"); + editorTextArea = (JTextArea) findComponentByName(editor.getComponent(), GhidraScriptEditorComponentProvider.EDITOR_COMPONENT_NAME); waitForSwing(); @@ -850,7 +850,7 @@ public abstract class AbstractGhidraScriptMgrPluginTest Map map = provider.getEditorMap(); GhidraScriptEditorComponentProvider fileEditor = map.get(file); final JTextArea textArea = - (JTextArea) findComponentByName(fileEditor.getComponent(), "EDITOR"); + (JTextArea) findComponentByName(fileEditor.getComponent(), GhidraScriptEditorComponentProvider.EDITOR_COMPONENT_NAME); assertNotNull(textArea); final String[] box = new String[1]; @@ -1164,7 +1164,7 @@ public abstract class AbstractGhidraScriptMgrPluginTest return info.getSourceFile(); } - static protected String CANCELLABLE_SCRIPT_NAME = TestChangeProgramScript.class.getName(); + protected static String CANCELLABLE_SCRIPT_NAME = TestChangeProgramScript.class.getName(); protected void cancel() throws Exception { Window window = waitForWindowByTitleContaining(CANCELLABLE_SCRIPT_NAME); @@ -1494,7 +1494,7 @@ public abstract class AbstractGhidraScriptMgrPluginTest protected JTextComponent grabScriptEditorTextArea() { GhidraScriptEditorComponentProvider scriptEditor = grabScriptEditor(); - JTextArea textArea = (JTextArea) findComponentByName(scriptEditor.getComponent(), "EDITOR"); + JTextArea textArea = (JTextArea) findComponentByName(scriptEditor.getComponent(), GhidraScriptEditorComponentProvider.EDITOR_COMPONENT_NAME); assertNotNull(textArea); return textArea; } @@ -1596,9 +1596,9 @@ public abstract class AbstractGhidraScriptMgrPluginTest } } } - catch (CancelledException ce) { + catch (CancelledException e) { doneLatch.countDown(); - throw ce; + throw e; } doneLatch.countDown(); diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java index a0f1966a75..15495f121f 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GhidraScriptMgrPluginScreenShots.java @@ -119,7 +119,7 @@ public class GhidraScriptMgrPluginScreenShots extends GhidraScreenShotGenerator BundleStatusComponentProvider bundleStatusComponentProvider = showProvider(BundleStatusComponentProvider.class); - bundleStatusComponentProvider.getModel().setPathsForTesting(paths); + bundleStatusComponentProvider.setPathsForTesting(paths); waitForComponentProvider(BundleStatusComponentProvider.class); captureComponent(bundleStatusComponentProvider.getComponent());