diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionTagPlugin/images/FullWindow.png b/Ghidra/Features/Base/src/main/help/help/topics/FunctionTagPlugin/images/FullWindow.png index f869f9ae6f..3c8d624e37 100644 Binary files a/Ghidra/Features/Base/src/main/help/help/topics/FunctionTagPlugin/images/FullWindow.png and b/Ghidra/Features/Base/src/main/help/help/topics/FunctionTagPlugin/images/FullWindow.png differ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/AddFunctionTagCmd.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/AddFunctionTagCmd.java index 6b844208af..da8dc86e4c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/AddFunctionTagCmd.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/AddFunctionTagCmd.java @@ -18,14 +18,13 @@ package ghidra.app.cmd.function; import ghidra.framework.cmd.Command; import ghidra.framework.model.DomainObject; import ghidra.program.database.ProgramDB; -import ghidra.program.database.function.FunctionManagerDB; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.FunctionManager; /** * Command for assigning a tag to a function. Executing this will pop up a dialog - * allowing the user to assign tags to a function. - * + * allowing the user to assign tags to a function. */ public class AddFunctionTagCmd implements Command { @@ -34,7 +33,7 @@ public class AddFunctionTagCmd implements Command { private String errorMsg = ""; /** - * Constructor. + * Constructor * * @param tagName the name of the tag to add * @param entryPoint the function address @@ -44,15 +43,11 @@ public class AddFunctionTagCmd implements Command { this.entryPoint = entryPoint; } - /****************************************************************************** - * PUBLIC METHODS - ******************************************************************************/ - @Override public boolean applyTo(DomainObject obj) { ProgramDB program = (ProgramDB) obj; - FunctionManagerDB functionManagerDB = (FunctionManagerDB) program.getFunctionManager(); - Function function = functionManagerDB.getFunctionAt(entryPoint); + FunctionManager functionManager = program.getFunctionManager(); + Function function = functionManager.getFunctionAt(entryPoint); if (function == null) { errorMsg = "Function not found at: " + entryPoint.toString(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/ChangeFunctionTagCmd.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/ChangeFunctionTagCmd.java index dd519a737f..3d83657344 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/ChangeFunctionTagCmd.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/ChangeFunctionTagCmd.java @@ -18,26 +18,24 @@ package ghidra.app.cmd.function; import ghidra.framework.cmd.Command; import ghidra.framework.model.DomainObject; import ghidra.program.database.ProgramDB; -import ghidra.program.database.function.FunctionManagerDB; -import ghidra.program.model.listing.FunctionTag; -import ghidra.program.model.listing.FunctionTagManager; +import ghidra.program.model.listing.*; /** - * Updates the name or comment field for a given function tag. + * Updates the name or comment field for a given function tag */ public class ChangeFunctionTagCmd implements Command { private final int field; private final String tagName; private final String newVal; - + private String errorMsg = ""; public static final int TAG_NAME_CHANGED = 0; public static final int TAG_COMMENT_CHANGED = 1; /** - * Constructor. + * Constructor * * @param tagName the name of the tag to change * @param newVal the new value to set @@ -52,16 +50,12 @@ public class ChangeFunctionTagCmd implements Command { this.field = field; } - /****************************************************************************** - * PUBLIC METHODS - ******************************************************************************/ - @Override public boolean applyTo(DomainObject obj) { ProgramDB program = (ProgramDB) obj; - FunctionManagerDB functionManagerDB = (FunctionManagerDB) program.getFunctionManager(); - FunctionTagManager functionTagManager = functionManagerDB.getFunctionTagManager(); - FunctionTag tag = functionTagManager.getFunctionTag(tagName); + FunctionManager functionManager = program.getFunctionManager(); + FunctionTagManager tagManager = functionManager.getFunctionTagManager(); + FunctionTag tag = tagManager.getFunctionTag(tagName); if (tag == null) { errorMsg = "Function Tag not found: " + tagName; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/CreateFunctionTagCmd.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/CreateFunctionTagCmd.java index 9c6b133400..76fde6867b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/CreateFunctionTagCmd.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/CreateFunctionTagCmd.java @@ -18,12 +18,11 @@ package ghidra.app.cmd.function; import ghidra.framework.cmd.Command; import ghidra.framework.model.DomainObject; import ghidra.program.database.ProgramDB; -import ghidra.program.database.function.FunctionManagerDB; +import ghidra.program.model.listing.FunctionManager; import ghidra.program.model.listing.FunctionTagManager; /** - * Command for assigning a tag to a function. - * + * Command for assigning a tag to a function */ public class CreateFunctionTagCmd implements Command { @@ -31,7 +30,7 @@ public class CreateFunctionTagCmd implements Command { private String comment; /** - * Constructor. + * Constructor * * @param name the name of the new tag */ @@ -41,7 +40,7 @@ public class CreateFunctionTagCmd implements Command { } /** - * Constructor. + * Constructor * * @param name the name of the new tag * @param comment the tag comment @@ -51,16 +50,12 @@ public class CreateFunctionTagCmd implements Command { this.comment = comment; } - /****************************************************************************** - * PUBLIC METHODS - ******************************************************************************/ - @Override public boolean applyTo(DomainObject obj) { ProgramDB program = (ProgramDB) obj; - FunctionManagerDB functionManagerDB = (FunctionManagerDB) program.getFunctionManager(); - FunctionTagManager functionTagManager = functionManagerDB.getFunctionTagManager(); - functionTagManager.createFunctionTag(name, comment); + FunctionManager functionManager = program.getFunctionManager(); + FunctionTagManager tagManager = functionManager.getFunctionTagManager(); + tagManager.createFunctionTag(name, comment); return true; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/DeleteFunctionTagCmd.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/DeleteFunctionTagCmd.java index b6ac6efc2a..df09d1ac26 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/DeleteFunctionTagCmd.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/DeleteFunctionTagCmd.java @@ -18,19 +18,19 @@ package ghidra.app.cmd.function; import ghidra.framework.cmd.BackgroundCommand; import ghidra.framework.model.DomainObject; import ghidra.program.database.ProgramDB; -import ghidra.program.database.function.FunctionManagerDB; +import ghidra.program.model.listing.FunctionManager; import ghidra.program.model.listing.FunctionTag; import ghidra.util.task.TaskMonitor; /** - * Command for deleting a tag from the system. + * Command for deleting a tag from the system */ public class DeleteFunctionTagCmd extends BackgroundCommand { private String tagName; /** - * Constructor. + * Constructor * * @param tagName the name of the tag to delete */ @@ -38,21 +38,15 @@ public class DeleteFunctionTagCmd extends BackgroundCommand { this.tagName = tagName; } - /****************************************************************************** - * PUBLIC METHODS - ******************************************************************************/ - @Override public boolean applyTo(DomainObject obj, TaskMonitor monitor) { ProgramDB program = (ProgramDB) obj; - FunctionManagerDB functionManager = (FunctionManagerDB) program.getFunctionManager(); + FunctionManager functionManager = program.getFunctionManager(); FunctionTag tag = functionManager.getFunctionTagManager().getFunctionTag(tagName); - if (tag != null) { tag.delete(); } - return true; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/RemoveFunctionTagCmd.java b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/RemoveFunctionTagCmd.java index fce2a58ad7..2b42e078d7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/RemoveFunctionTagCmd.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/cmd/function/RemoveFunctionTagCmd.java @@ -18,13 +18,12 @@ package ghidra.app.cmd.function; import ghidra.framework.cmd.Command; import ghidra.framework.model.DomainObject; import ghidra.program.database.ProgramDB; -import ghidra.program.database.function.FunctionManagerDB; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.FunctionManager; /** - * Command for removing a tag from a function. - * + * Command for removing a tag from a function */ public class RemoveFunctionTagCmd implements Command { @@ -32,6 +31,8 @@ public class RemoveFunctionTagCmd implements Command { private String tagName; /** + * Constructor + * * @param tagName the name of the tag to remove * @param entryPoint the address of the function */ @@ -40,19 +41,12 @@ public class RemoveFunctionTagCmd implements Command { this.entryPoint = entryPoint; } - /****************************************************************************** - * PUBLIC METHODS - ******************************************************************************/ - @Override public boolean applyTo(DomainObject obj) { ProgramDB program = (ProgramDB) obj; - FunctionManagerDB functionManagerDB = (FunctionManagerDB) program.getFunctionManager(); - Function function = functionManagerDB.getFunctionAt(entryPoint); + FunctionManager functionManager = program.getFunctionManager(); + Function function = functionManager.getFunctionAt(entryPoint); function.removeTag(tagName); - - // The remove function does not return a success/fail statutus, so just return - // and move on. return true; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagButtonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagButtonPanel.java index 3aa0b5e103..a0e17897cc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagButtonPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagButtonPanel.java @@ -24,7 +24,7 @@ import resources.Icons; import resources.ResourceManager; /** - * Provides buttons to be used with the {@link FunctionTagsComponentProvider}. + * Provides buttons to be used with the {@link FunctionTagProvider}. * These buttons allow users to add or remove tags from functions, or delete * tags altogether. *

diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagPlugin.java index 77a372364e..77842e29c8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagPlugin.java @@ -26,7 +26,7 @@ import ghidra.program.util.ProgramLocation; /** * Plugin for managing function tags. This works with the associated - * {@link FunctionTagsComponentProvider} to allow users to view and + * {@link FunctionTagProvider} to allow users to view and * edit function tags both globally and for individual functions. * */ @@ -40,18 +40,18 @@ import ghidra.program.util.ProgramLocation; ) //@formatter:on public class FunctionTagPlugin extends ProgramPlugin { - + public final static String FUNCTION_TAG_MENU_SUBGROUP = "TagFunction"; // Action visible when right-clicking on a function in the listing. private EditFunctionTagsAction editFunctionTagsAction; // The display object for this plugin. - private FunctionTagsComponentProvider provider; + private FunctionTagProvider provider; public FunctionTagPlugin(PluginTool tool) { super(tool, true, false); - provider = new FunctionTagsComponentProvider(this, getCurrentProgram()); + provider = new FunctionTagProvider(this, getCurrentProgram()); createActions(); } @@ -64,7 +64,7 @@ public class FunctionTagPlugin extends ProgramPlugin { * * @return the component provider */ - public FunctionTagsComponentProvider getProvider() { + public FunctionTagProvider getProvider() { return provider; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagsComponentProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagProvider.java similarity index 64% rename from Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagsComponentProvider.java rename to Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagProvider.java index 08b2e6a389..9fec84ac4e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagsComponentProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagProvider.java @@ -16,24 +16,29 @@ package ghidra.app.plugin.core.function.tags; import java.awt.*; -import java.util.ArrayList; +import java.util.*; import java.util.List; -import java.util.Set; import javax.swing.*; +import javax.swing.border.BevelBorder; +import javax.swing.border.Border; + +import org.apache.commons.lang3.StringUtils; import docking.widgets.label.GLabel; import docking.widgets.textfield.HintTextField; import ghidra.app.cmd.function.CreateFunctionTagCmd; import ghidra.app.context.ProgramActionContext; import ghidra.framework.cmd.Command; -import ghidra.framework.model.DomainObjectChangedEvent; -import ghidra.framework.model.DomainObjectListener; +import ghidra.framework.model.*; import ghidra.framework.plugintool.ComponentProviderAdapter; +import ghidra.program.database.function.FunctionManagerDB; import ghidra.program.model.address.Address; import ghidra.program.model.listing.*; import ghidra.program.util.*; import ghidra.util.*; +import ghidra.util.task.SwingUpdateManager; +import resources.ResourceManager; /** * Displays all the function tags in the database and identifies which ones have @@ -44,13 +49,13 @@ import ghidra.util.*; *

  • Edit tags (both name and comment)
  • *
  • Delete tags
  • *
  • Assign tags to the currently selected function
  • - *
  • Remove tags from the currently selected function
  • + *
  • Remove tags from the currently selected function
  • * - * This provider can be shown by right-clicking on a function and selecting the + * This provider can be shown by right-clicking on a function and selecting the * "Edit Tags" option, or by selecting the "Edit Function Tags" option from the * "Window" menu. */ -public class FunctionTagsComponentProvider extends ComponentProviderAdapter +public class FunctionTagProvider extends ComponentProviderAdapter implements DomainObjectListener { private Color BORDER_COLOR = Color.GRAY; @@ -69,27 +74,48 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter private int MIN_WIDTH = 850; private int MIN_HEIGHT = 350; - // The current program location selected in the listing. + private SwingUpdateManager updater = new SwingUpdateManager(this::doUpdate); + + // The current program location selected in the listing. private ProgramLocation currentLocation = null; // Character used as a separator when entering multiple tags in // the create tag entry field. - private static String INPUT_DELIMITER = ","; + private static final String INPUT_DELIMITER = ","; + + /** + * Optional! If there is a file with this name which can be found by the + * {@link ResourceManager}, and it contains a valid list of tag names, + * they will be loaded. The file must be XML with the following + * structure: + * + * + * + * TAG1 + * tag comment + * + * + * + */ + private static String TAG_FILE = "functionTags.xml"; + + // Keeps a list of the original tags as loaded from file. This is necessary when switching + // between programs where we need to know the original state of the disabled tags. Without + // this we would need to reload from file on each new program activation. + private Set tagsFromFile; /** * Constructor - * + * * @param plugin the function tag plugin * @param program the current program */ - public FunctionTagsComponentProvider(FunctionTagPlugin plugin, Program program) { + public FunctionTagProvider(FunctionTagPlugin plugin, Program program) { super(plugin.getTool(), "Function Tags", plugin.getName(), ProgramActionContext.class); setHelpLocation(new HelpLocation(plugin.getName(), plugin.getName())); this.program = program; - mainPanel = createWorkPanel(); - addToTool(); } @@ -97,24 +123,9 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter * PUBLIC METHODS ******************************************************************************/ - /** - * Completely clears the UI and loads the tag table from scratch. Note that - * the model will be completely reset based on whatever the current location is - * in the listing. - */ - public void reload() { - - Swing.runLater(() -> { - tagInputField.setText(""); - updateTitle(currentLocation); - updateTagLists(); - }); - } - @Override public void componentShown() { - updateTagLists(); - updateTitle(currentLocation); + updateView(); } @Override @@ -122,21 +133,16 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter return mainPanel; } - HintTextField getTagInputField() { - return tagInputField; - } - /** - * Invoked when a new location has been detected in the listing. When + * Invoked when a new location has been detected in the listing. When * this happens we need to update the tag list to show what tags are assigned * at the current location. - * + * * @param loc the address selected in the listing */ public void locationChanged(ProgramLocation loc) { currentLocation = loc; - updateTitle(loc); - updateTagLists(); + updateView(); } public void programActivated(Program activatedProgram) { @@ -145,7 +151,7 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter // Add a listener so we pick up domain object change events (add/delete/remove, etc...) activatedProgram.addListener(this); - updateTagLists(); + updateTagViews(); } public void programDeactivated(Program deactivatedProgram) { @@ -155,31 +161,60 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter /** * This class needs to listen for changes to the domain object (tag create, delete, etc...) - * so it can update the display accordingly. - * + * so it can update the display accordingly. + * * @param ev the change event */ @Override public void domainObjectChanged(DomainObjectChangedEvent ev) { - if (ev.containsEvent(ChangeManager.DOCR_FUNCTION_TAG_CHANGED) || + + if (!isVisible()) { + return; + } + + if (ev.containsEvent(DomainObject.DO_OBJECT_RESTORED) || ev.containsEvent(ChangeManager.DOCR_FUNCTION_TAG_CREATED) || ev.containsEvent(ChangeManager.DOCR_FUNCTION_TAG_DELETED) || ev.containsEvent(ChangeManager.DOCR_TAG_REMOVED_FROM_FUNCTION) || ev.containsEvent(ChangeManager.DOCR_TAG_ADDED_TO_FUNCTION)) { - reload(); + updater.updateLater(); + return; + } + + if (ev.containsEvent(ChangeManager.DOCR_FUNCTION_TAG_CHANGED)) { + repaint(); } } - /****************************************************************************** - * PRIVATE METHODS - ******************************************************************************/ + private void doUpdate() { + reload(); + } + + private void reload() { + allFunctionsPanel.refresh(); + + Function function = getFunction(currentLocation); + sourcePanel.refresh(function); + targetPanel.refresh(function); + } + + private void updateView() { + updateTitle(currentLocation); + updateTagViews(); + } + + private void repaint() { + sourcePanel.repaint(); + targetPanel.repaint(); + allFunctionsPanel.repaint(); + } private void updateTitle(ProgramLocation location) { if (!isVisible()) { return; } - Function function = getFunctionAtLocation(location); + Function function = getFunction(location); if (function == null) { setSubTitle("NOT A FUNCTION"); } @@ -210,8 +245,8 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter allFunctionsPanel.setBorder(BorderFactory.createLineBorder(BORDER_COLOR)); // If we don't set this, then the splitter won't be able to shrink the - // target panels below the size required by its header, which can be large - // because of the amount of text displayed. Keep the minimum size setting on + // target panels below the size required by its header, which can be large + // because of the amount of text displayed. Keep the minimum size setting on // the source panel, however. That is generally small. targetPanel.setMinimumSize(new Dimension(0, 0)); @@ -236,12 +271,12 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter * Updates the button panel depending on the selection state of the * tag lists. Also updates the {@link AllFunctionsPanel} so it can update * its list. - * + * * @param panel the panel that generated the selection event */ public void selectionChanged(TagListPanel panel) { - Function function = getFunctionAtLocation(currentLocation); + Function function = getFunction(currentLocation); if (panel instanceof SourceTagsPanel) { buttonPanel.sourcePanelSelectionChanged(function != null); @@ -258,22 +293,70 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter allFunctionsPanel.setSelectedTags(sourceTags); } - TargetTagsPanel getTargetPanel() { + public AllFunctionsPanel getAllFunctionsPanel() { + return allFunctionsPanel; + } + + public TargetTagsPanel getTargetPanel() { return targetPanel; } - SourceTagsPanel getSourcePanel() { + public HintTextField getTagInputField() { + return tagInputField; + } + + public SourceTagsPanel getSourcePanel() { return sourcePanel; } + public FunctionTagButtonPanel getButtonPanel() { + return buttonPanel; + } + + public JPanel getInputPanel() { + return inputPanel; + } + + Set backgroundLoadTags() { + // Add any tags from the file system that are not in the db + List dbTags = getAllTagsFromDatabase(); + Set allTags = new HashSet<>(dbTags); + allTags.addAll(getFileTags()); + return allTags; + } + /** - * Returns the {@link Function} at the given program location. If not a function, or - * if the location is not a pointer to a function returns null. - * + * Loads tags from the external file specified. + * + * @return the loaded tags + */ + private Set getFileTags() { + if (tagsFromFile == null) { + tagsFromFile = FunctionTagLoader.loadTags(TAG_FILE); + } + return tagsFromFile; + } + + /** + * Returns an array of all tags stored in the database. + * + * @return list of tags + */ + private List getAllTagsFromDatabase() { + if (program == null) { + return Collections.emptyList(); + } + FunctionManagerDB functionManagerDB = (FunctionManagerDB) program.getFunctionManager(); + return functionManagerDB.getFunctionTagManager().getAllFunctionTags(); + } + + /** + * Returns the {@link Function} for the given program location + * * @param loc the program location * @return function containing the location, or null if not applicable */ - private Function getFunctionAtLocation(ProgramLocation loc) { + private Function getFunction(ProgramLocation loc) { Address functionAddress = getFunctionAddress(loc); if (functionAddress == null) { @@ -285,7 +368,7 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter /** * Retrieves the address of the function associated with the given program location. - * + * * @param loc the program location * @return the entry point of the function, or null if not valid */ @@ -295,8 +378,7 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter return null; } - // If the user clicks on an instruction within a function we want to show - // the tags. + // If the user clicks on an instruction within a function we want to show the tags if (program.getFunctionManager().isInFunction(loc.getAddress())) { return loc.getAddress(); } @@ -311,13 +393,11 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter } /** - * Refreshes the contents of the table with the current program and location. This - * should be called any time a program is activated or the location in the listing - * has changed. + * Refreshes the contents of the tables with the current program and location */ - private void updateTagLists() { + private void updateTagViews() { - if (sourcePanel == null || targetPanel == null || allFunctionsPanel == null) { + if (mainPanel == null || !isVisible()) { return; } @@ -325,7 +405,7 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter targetPanel.setProgram(program); allFunctionsPanel.setProgram(program); - // Get the currently selected tags and use them to update the all functions panel. If + // Get the currently selected tags and use them to update the all functions panel. If // there is no current selection, leave the table as-is. Set sTags = sourcePanel.getSelectedTags(); Set tTags = targetPanel.getSelectedTags(); @@ -334,53 +414,63 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter allFunctionsPanel.refresh(sTags); } - Function function = getFunctionAtLocation(currentLocation); + Function function = getFunction(currentLocation); sourcePanel.refresh(function); targetPanel.refresh(function); } /** - * Parses all items in the text input field and adds them as new tags. + * Parses all items in the text input field and adds them as new tags. */ private void processCreates() { if (program == null) { - Msg.showInfo(this, tool.getActiveWindow(), "No program!", + Msg.showInfo(this, tool.getActiveWindow(), "No Program", "You must load a program before trying to create tags"); return; } + + List dropped = new ArrayList<>(); List names = getInputNames(); for (String name : names) { - // Only execute the create command if a tag with the given name does not - // already exist. - if (sourcePanel.tagExists(name) || targetPanel.tagExists(name)) { - Msg.showInfo(this, tool.getActiveWindow(), "Duplicate Tag Name", - "There is already a tag with the name [" + name + "]. Please try again."); + // only execute the create command if a tag with the given name does not already exist + // (note: this could fail if the model is not yet loaded, but this is unlikely. The + // only fallout is that the error message would not be shown--the database will not add + // the tag twice.) + if (sourcePanel.tagExists(name)) { + dropped.add(name); } else { Command cmd = new CreateFunctionTagCmd(name); tool.execute(cmd, program); } } + + if (!dropped.isEmpty()) { + String text = StringUtils.join(dropped, ", "); + Msg.showInfo(this, tool.getActiveWindow(), "Duplicate Tag Names", + "Tags aleady exist. Ignoring the following tags: " + text); + } + + Swing.runLater(() -> tagInputField.setText("")); } /** - * Returns a list of tag names the user has entered in the input field. + * Returns a list of tag names the user has entered in the input` field. * Note: This assumes that multiple entries are comma-delimited. - * + * * @return the list of tag names to create */ private List getInputNames() { - // First split the string on the delimiter to get all the entries. + // first split the string on the delimiter to get all the entries String[] names = tagInputField.getText().split(INPUT_DELIMITER); - // Trim each item to remove any leading/trailing whitespace and add to - // the return list. - ArrayList nameList = new ArrayList<>(); + // trim each item to remove any leading/trailing whitespace and add to the return list + List nameList = new ArrayList<>(); for (String name : names) { - if (!name.trim().isEmpty()) { + if (!StringUtils.isBlank(name)) { nameList.add(name.trim()); } } @@ -390,19 +480,25 @@ public class FunctionTagsComponentProvider extends ComponentProviderAdapter /** * Creates the text-entry panel for adding new tag names. - * + * * @return the new text input panel */ private JPanel createInputPanel() { - inputPanel = new JPanel(new BorderLayout()); tagInputField = new HintTextField("tag 1, tag 2, ..."); tagInputField.setName("tagInputTF"); tagInputField.addActionListener(e -> processCreates()); + inputPanel = new JPanel(); + Border outsideBorder = BorderFactory.createBevelBorder(BevelBorder.LOWERED); + Border insideBorder = BorderFactory.createEmptyBorder(5, 2, 2, 2); + inputPanel.setBorder(BorderFactory.createCompoundBorder(outsideBorder, insideBorder)); + inputPanel.setLayout(new BoxLayout(inputPanel, BoxLayout.LINE_AXIS)); inputPanel.add(new GLabel(" Create new tag(s):"), BorderLayout.WEST); + inputPanel.add(Box.createHorizontalStrut(5)); inputPanel.add(tagInputField, BorderLayout.CENTER); return inputPanel; } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagRowObject.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagRowObject.java index 14587fd5ba..777648230a 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagRowObject.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagRowObject.java @@ -22,8 +22,9 @@ class FunctionTagRowObject { private FunctionTag tag; private int count; - FunctionTagRowObject(FunctionTag tag) { + FunctionTagRowObject(FunctionTag tag, int count) { this.tag = tag; + this.count = count; } FunctionTag getTag() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagTableModel.java index 0e3ff066de..55c46a013c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/FunctionTagTableModel.java @@ -15,10 +15,7 @@ */ package ghidra.app.plugin.core.function.tags; -import java.util.*; -import java.util.function.Supplier; - -import org.apache.commons.collections4.map.LazyMap; +import java.util.Set; import docking.widgets.table.AbstractDynamicTableColumnStub; import docking.widgets.table.TableColumnDescriptor; @@ -27,7 +24,6 @@ import ghidra.docking.settings.Settings; import ghidra.framework.plugintool.ServiceProvider; import ghidra.program.model.listing.*; import ghidra.util.datastruct.Accumulator; -import ghidra.util.datastruct.Counter; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -37,12 +33,12 @@ import ghidra.util.task.TaskMonitor; public class FunctionTagTableModel extends ThreadedTableModel { private Program program; - private Supplier> tagLoader; + private TagListPanel tagListPanel; protected FunctionTagTableModel(String modelName, ServiceProvider serviceProvider, - Supplier> tagLoader) { + TagListPanel tagLoader) { super(modelName, serviceProvider); - this.tagLoader = tagLoader; + this.tagListPanel = tagLoader; } public void setProgram(Program program) { @@ -57,37 +53,15 @@ public class FunctionTagTableModel extends ThreadedTableModel countsByName = getFunctionTagCountsByName(monitor); - for (FunctionTag tag : tagLoader.get()) { - - FunctionTagRowObject rowObject = new FunctionTagRowObject(tag); - Counter counter = countsByName.get(tag.getName()); - rowObject.setCount(counter.count); - accumulator.add(rowObject); - } - } - - private Map getFunctionTagCountsByName(TaskMonitor monitor) - throws CancelledException { - FunctionManager functionManager = program.getFunctionManager(); - monitor.initialize(functionManager.getFunctionCount()); - - Map map = LazyMap.lazyMap(new HashMap<>(), () -> new Counter()); - FunctionIterator it = functionManager.getFunctions(true); - for (Function function : it) { + FunctionTagManager tagManager = functionManager.getFunctionTagManager(); + Set tags = tagListPanel.backgroundLoadTags(); + monitor.initialize(tags.size()); + for (FunctionTag tag : tags) { monitor.checkCanceled(); - - Set functionTags = function.getTags(); - for (FunctionTag tag : functionTags) { - - String name = tag.getName(); - map.get(name).count++; - } - + accumulator.add(new FunctionTagRowObject(tag, tagManager.getUseCount(tag))); monitor.incrementProgress(1); } - return map; } @Override @@ -115,11 +89,23 @@ public class FunctionTagTableModel extends ThreadedTableModel row.getName().equals(name)); + return getRowObject(name) != null; + } + + /** + * Returns the row object that matches the given tag name + * @param name the tag name + * @return the row object + */ + public FunctionTagRowObject getRowObject(String name) { + return getAllData().stream() + .filter(row -> row.getName().equals(name)) + .findFirst() + .orElseGet(() -> null); } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/InMemoryFunctionTag.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/InMemoryFunctionTag.java index 7f8b7c6c27..e13f5a1155 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/InMemoryFunctionTag.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/InMemoryFunctionTag.java @@ -23,7 +23,7 @@ import ghidra.program.model.listing.FunctionTag; * This class provides an implementation of the {@link FunctionTag} interface for * tags that are not yet ready to be inserted into the database. This was created * to allow tags to be imported from an external file and made available to the user - * through the {@link FunctionTagsComponentProvider} UI without needing to formally + * through the {@link FunctionTagProvider} UI without needing to formally * add them to the {@code FunctionTagAdapter} table. */ class InMemoryFunctionTag implements FunctionTag { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/SourceTagsPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/SourceTagsPanel.java index 204acf6140..5835def9df 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/SourceTagsPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/SourceTagsPanel.java @@ -15,44 +15,20 @@ */ package ghidra.app.plugin.core.function.tags; -import java.util.*; +import java.util.Set; import ghidra.app.cmd.function.AddFunctionTagCmd; import ghidra.app.cmd.function.CreateFunctionTagCmd; import ghidra.framework.cmd.Command; import ghidra.framework.plugintool.PluginTool; -import ghidra.program.database.function.FunctionManagerDB; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.FunctionTag; -import resources.ResourceManager; /** - * List for displaying all tags in the program that have NOT yet been assigned - * to a function. + * List for displaying all tags in the programs */ public class SourceTagsPanel extends TagListPanel { - /** - * Optional! If there is a file with this name which can be found by the - * {@link ResourceManager}, and it contains a valid list of tag names, - * they will be loaded. The file must be XML with the following - * structure: - * - * - * - * TAG1 - * tag comment - * - * - * - */ - private static String TAG_FILE = "functionTags.xml"; - - // Keeps a list of the original tags as loaded from file. This is necessary when switching - // between programs where we need to know the original state of the disabled tags. Without - // this we would need to reload from file on each new program activation. - private Set tagsFromFile; - /** * Constructor * @@ -60,12 +36,9 @@ public class SourceTagsPanel extends TagListPanel { * @param tool the plugin tool * @param title the title of the panel */ - public SourceTagsPanel(FunctionTagsComponentProvider provider, PluginTool tool, String title) { + public SourceTagsPanel(FunctionTagProvider provider, PluginTool tool, String title) { super(provider, tool, title); - // Load any tags from external sources - tagsFromFile = loadTags(); - table.setDisabled(true); } @@ -106,13 +79,7 @@ public class SourceTagsPanel extends TagListPanel { @Override protected Set backgroundLoadTags() { - - List dbTags = getAllTagsFromDatabase(); - - // Add any tags from the file system that are not in the db - Set allTags = new HashSet<>(dbTags); - allTags.addAll(tagsFromFile); - return allTags; + return provider.backgroundLoadTags(); } /** @@ -129,30 +96,4 @@ public class SourceTagsPanel extends TagListPanel { return true; } - - /****************************************************************************** - * PRIVATE METHODS - ******************************************************************************/ - - /** - * Returns an array of all tags stored in the database. - * - * @return list of tags - */ - private List getAllTagsFromDatabase() { - if (program == null) { - return Collections.emptyList(); - } - FunctionManagerDB functionManagerDB = (FunctionManagerDB) program.getFunctionManager(); - return functionManagerDB.getFunctionTagManager().getAllFunctionTags(); - } - - /** - * Loads tags from the external file specified. - * - * @return the loaded tags - */ - private Set loadTags() { - return FunctionTagLoader.loadTags(TAG_FILE); - } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/TagListPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/TagListPanel.java index fe8c40938e..4662340d24 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/TagListPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/TagListPanel.java @@ -45,6 +45,7 @@ import ghidra.util.table.GhidraThreadedTablePanel; public abstract class TagListPanel extends JPanel { protected PluginTool tool; + protected FunctionTagProvider provider; protected Program program; protected Function function; @@ -61,13 +62,13 @@ public abstract class TagListPanel extends JPanel { * @param tool the plugin tool * @param title the title of the panel */ - public TagListPanel(FunctionTagsComponentProvider provider, PluginTool tool, String title) { + public TagListPanel(FunctionTagProvider provider, PluginTool tool, String title) { this.tool = tool; + this.provider = provider; setLayout(new BorderLayout()); - model = new FunctionTagTableModel("Function Tags", provider.getTool(), - this::backgroundLoadTags); + model = new FunctionTagTableModel("Function Tags", provider.getTool(), this); GhidraThreadedTablePanel tablePanel = new GhidraThreadedTablePanel<>(model) { protected GTable createTable(ThreadedTableModel tm) { @@ -121,10 +122,6 @@ public abstract class TagListPanel extends JPanel { */ public abstract void refresh(Function newFunction); - /** - * Called by a background thread to load the tags for this panel - * @return the tags - */ protected abstract Set backgroundLoadTags(); void editRow(int row) { @@ -165,11 +162,11 @@ public abstract class TagListPanel extends JPanel { }); DockingWindowManager.showDialog(tool.getActiveWindow(), dialog); - if (dialog.isCanceled()) { - return; + String[] results = dialog.getValues(); + if (results[0] == null) { + return; // cancelled/closed } - String[] results = dialog.getValues(); String newName = results[0].trim(); String newComment = results[1].trim(); @@ -192,7 +189,7 @@ public abstract class TagListPanel extends JPanel { return model; } - FunctionTagTable getTable() { + public FunctionTagTable getTable() { return table; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/TargetTagsPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/TargetTagsPanel.java index d318a58b24..462375658e 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/TargetTagsPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/function/tags/TargetTagsPanel.java @@ -24,8 +24,7 @@ import ghidra.program.model.listing.Function; import ghidra.program.model.listing.FunctionTag; /** - * Displays a list of tags that have been assigned to the currently-selected - * function in the listing + * Displays a list of tags that have been assigned to the current function */ public class TargetTagsPanel extends TagListPanel { @@ -36,7 +35,7 @@ public class TargetTagsPanel extends TagListPanel { * @param tool the plugin tool * @param title the panel title */ - public TargetTagsPanel(FunctionTagsComponentProvider provider, + public TargetTagsPanel(FunctionTagProvider provider, PluginTool tool, String title) { super(provider, tool, title); diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/tags/FunctionTagEditorTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/tags/FunctionTagPluginTest.java similarity index 79% rename from Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/tags/FunctionTagEditorTest.java rename to Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/tags/FunctionTagPluginTest.java index 0dedb545dc..5405e817ea 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/tags/FunctionTagEditorTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/function/tags/FunctionTagPluginTest.java @@ -23,18 +23,19 @@ import java.util.*; import javax.swing.AbstractButton; +import org.apache.commons.lang3.StringUtils; import org.junit.*; import docking.ActionContext; import docking.action.DockingActionIf; import docking.widgets.dialogs.InputDialog; import docking.widgets.textfield.HintTextField; +import ghidra.app.cmd.function.DeleteFunctionCmd; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.gotoquery.GoToServicePlugin; import ghidra.app.services.ProgramManager; import ghidra.framework.plugintool.PluginTool; import ghidra.program.database.function.FunctionDB; -import ghidra.program.database.function.FunctionManagerDB; import ghidra.program.model.address.Address; import ghidra.program.model.listing.*; import ghidra.program.util.ProgramLocation; @@ -43,13 +44,13 @@ import ghidra.util.exception.UsrException; /** * Test class for the {@link FunctionTagPlugin}. This tests the ability to use the - * function tag edit GUI ({@link FunctionTagsComponentProvider} to create/edit/delete + * function tag edit GUI ({@link FunctionTagProvider} to create/edit/delete * tags. * * Test related to the merging and diffing of tags are defined in the * {@link FunctionTagMergeTest} class. */ -public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { +public class FunctionTagPluginTest extends AbstractGhidraHeadedIntegrationTest { private TestEnv env; private PluginTool tool; @@ -57,8 +58,6 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { private CodeBrowserPlugin cb; private FunctionTagPlugin plugin; - // Menu item available on a function entry point that launches the - // dialog. private DockingActionIf editFunctionTags; private Address NON_FUNCTION_ADDRESS; @@ -66,8 +65,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { private Address FUNCTION_ENTRY_ADDRESS_2; private Address FUNCTION_ENTRY_ADDRESS_3; - // The UI we're testing. - private FunctionTagsComponentProvider provider = null; + private FunctionTagProvider provider = null; @Before public void setUp() throws Exception { @@ -104,10 +102,6 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { env.dispose(); } - /**************************************************************************************** - * TESTS - ****************************************************************************************/ - /** * Tests that the menu option for bringing up the editor exists * on function headers only. @@ -143,13 +137,13 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testDeleteImmutableTag() throws Exception { - FunctionTagTable table = getTagListInPanel("sourcePanel"); + FunctionTagTable table = getSourceTable(); // Get an immutable tag from the source panel and set it to be selected. Verify that // the delete button is disabled. InMemoryFunctionTag immutableTag = getImmutableTag(); assertTrue("Must have at least one immutable tag for this test", immutableTag != null); - selectTagInList(immutableTag.getName(), table); + selectTagInTable(immutableTag.getName(), table); waitForSwing(); assertFalse(isButtonEnabled("deleteBtn")); @@ -164,7 +158,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { // Select just the non-immutable tag and verify that the delete button is now // enabled. - selectTagInList(tagName, table); + selectTagInTable(tagName, table); waitForSwing(); assertTrue(isButtonEnabled("deleteBtn")); } @@ -177,7 +171,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { @Test public void testDeleteImmutableTagAfterUse() throws Exception { - FunctionTagTable table = getTagListInPanel("targetPanel"); + FunctionTagTable table = getTargetTable(); // Get an immutable tag from the source panel. InMemoryFunctionTag tag = getImmutableTag(); @@ -191,7 +185,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { boolean inList = tagExists(tag.getName(), getAllTags()); assertTrue(inList); - selectTagInList(tag.getName(), table); + selectTagInTable(tag.getName(), table); waitForSwing(); assertTrue(isButtonEnabled("deleteBtn")); } @@ -292,8 +286,22 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { String name = "TEST"; createTag(name); addTagToFunction(name, FUNCTION_ENTRY_ADDRESS); + assertTableTagCount(name, 1); removeTagFromFunction(name, FUNCTION_ENTRY_ADDRESS); assertFalse(tagExists(name, getTags(FUNCTION_ENTRY_ADDRESS))); + assertTableTagCount(name, 0); + } + + @Test + public void testFunctionRemoved() throws Exception { + String name = "TEST"; + createTag(name); + addTagToFunction(name, FUNCTION_ENTRY_ADDRESS); + assertTableTagCount(name, 1); + + removeFunction(FUNCTION_ENTRY_ADDRESS); + assertTrue(tagExists(name, getAllTags())); + assertTableTagCount(name, 0); } /** @@ -305,7 +313,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { public void testViewFunctionsForTag() throws Exception { // Verify that the function panel is initially empty - AllFunctionsPanel functionsPanel = getFunctionsPanel(); + AllFunctionsPanel functionsPanel = provider.getAllFunctionsPanel(); List functions = functionsPanel.getFunctions(); assertTrue(functions.isEmpty()); @@ -315,8 +323,8 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { addTagToFunction(tagName1, FUNCTION_ENTRY_ADDRESS); // Select the tag in the target panel - FunctionTagTable table = getTagListInPanel("targetPanel"); - selectTagInList(tagName1, table); + FunctionTagTable table = getTargetTable(); + selectTagInTable(tagName1, table); waitForTableModel(functionsPanel.getTableModel()); // Verify that the function is shown in the function panel (check that @@ -338,7 +346,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { public void testMultipleFunctionsWithTag() throws Exception { // Verify that the function panel is initially empty - AllFunctionsPanel functionsPanel = getFunctionsPanel(); + AllFunctionsPanel functionsPanel = provider.getAllFunctionsPanel(); List functions = functionsPanel.getFunctions(); assertTrue(functions.isEmpty()); @@ -349,8 +357,8 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { addTagToFunction(tagName1, FUNCTION_ENTRY_ADDRESS_2); // Select the tag in the target panel - FunctionTagTable table = getTagListInPanel("targetPanel"); - selectTagInList(tagName1, table); + FunctionTagTable table = getTargetTable(); + selectTagInTable(tagName1, table); waitForTableModel(functionsPanel.getTableModel()); // Verify that both functions are shown in the function panel (check that @@ -374,7 +382,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { public void testViewMultipleFunctions() throws Exception { // Verify that the function panel is initially empty - AllFunctionsPanel functionsPanel = getFunctionsPanel(); + AllFunctionsPanel functionsPanel = provider.getAllFunctionsPanel(); List functions = functionsPanel.getFunctions(); assertTrue(functions.isEmpty()); @@ -391,8 +399,8 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { // Select both tags and verify that 3 functions are listed in // the functions panel goTo(tool, program, addr("00000000")); - FunctionTagTable table = getTagListInPanel("sourcePanel"); - selectTagInList(tagName1, table); + FunctionTagTable table = getSourceTable(); + selectTagInTable(tagName1, table); int index = table.getSelectedRow(); table.addRowSelectionInterval(index, index + 1); clickTableRange(table, index, 2); @@ -466,9 +474,9 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { cb.goTo(new ProgramLocation(program, address)); waitForSwing(); - FunctionTagTable list = getTagListInPanel("sourcePanel"); - selectTagInList(name, list); - clickButtonByName("addBtn"); + FunctionTagTable list = getSourceTable(); + selectTagInTable(name, list); + clickButton("addBtn"); } /** @@ -483,42 +491,21 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { cb.goTo(new ProgramLocation(program, address)); waitForSwing(); - FunctionTagTable table = getTagListInPanel("targetPanel"); - selectTagInList(name, table); - clickButtonByName("removeBtn"); + FunctionTagTable table = getTargetTable(); + selectTagInTable(name, table); + clickButton("removeBtn"); } - /** - * Gets the instance of the tag table in the given panel. - * - * @param panelName the name of the panel to search - * @return the function tag list - * @throws UsrException if there's an error retrieving the panel or tag list instances - */ - private FunctionTagTable getTagListInPanel(String panelName) throws UsrException { - Object comp = getInstanceField(panelName, provider); - if (comp == null) { - throw new UsrException("Error getting targetPanel field in provider"); - } - - Object table = getInstanceField("table", comp); - if (table == null) { - throw new UsrException("Error getting table in tag panel"); - } - - FunctionTagTable tagTable = (FunctionTagTable) table; - return tagTable; + private void removeFunction(Address address) { + applyCmd(program, new DeleteFunctionCmd(address)); } - /** - * Returns the functions panel - * - * @return the functions panel - */ - private AllFunctionsPanel getFunctionsPanel() { - AllFunctionsPanel panel = - (AllFunctionsPanel) getInstanceField("allFunctionsPanel", provider); - return panel; + private FunctionTagTable getTargetTable() { + return provider.getTargetPanel().getTable(); + } + + private FunctionTagTable getSourceTable() { + return provider.getSourcePanel().getTable(); } /** @@ -528,7 +515,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { * @param table the table to search * @throws Exception if there is a problem clicking cells in a list */ - private void selectTagInList(String name, FunctionTagTable table) throws Exception { + private void selectTagInTable(String name, FunctionTagTable table) throws Exception { assertTagExists(name, table); int row = 0; @@ -548,14 +535,9 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { * Clicks the button with the given name in the {@link FunctionTagButtonPanel}. * * @param name the button name - * @throws UsrException if there's an error retrieving the button panel instance */ - private void clickButtonByName(String name) throws UsrException { - Object buttonPanel = getInstanceField("buttonPanel", provider); - if (buttonPanel == null) { - throw new UsrException("Error getting button panel field in provider"); - } - FunctionTagButtonPanel btnPanel = (FunctionTagButtonPanel) buttonPanel; + private void clickButton(String name) { + FunctionTagButtonPanel btnPanel = provider.getButtonPanel(); pressButtonByName(btnPanel, name); waitForSwing(); } @@ -565,14 +547,9 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { * * @param name the button name * @return true if enabled - * @throws UsrException if there's an error retrieving the button panel instance */ - private boolean isButtonEnabled(String name) throws UsrException { - Object buttonPanel = getInstanceField("buttonPanel", provider); - if (buttonPanel == null) { - throw new UsrException("Error getting button panel field in provider"); - } - FunctionTagButtonPanel btnPanel = (FunctionTagButtonPanel) buttonPanel; + private boolean isButtonEnabled(String name) { + FunctionTagButtonPanel btnPanel = provider.getButtonPanel(); AbstractButton button = findAbstractButtonByName(btnPanel, name); return isEnabled(button); } @@ -591,11 +568,17 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { return null; } - /** - * Loads the notepad program. - * - * @throws Exception if there's an error creating the program builder - */ + private void assertTableTagCount(String name, int expected) { + + waitForSwing(); + FunctionTagTableModel model = provider.getSourcePanel().getModel(); + waitForTableModel(model); + + FunctionTagRowObject rowObject = model.getRowObject(name); + assertNotNull(rowObject); + assertEquals("Tag count in table is incorrect", expected, rowObject.getCount()); + } + private void loadProgram() throws Exception { ClassicSampleX86ProgramBuilder builder = new ClassicSampleX86ProgramBuilder(); @@ -604,37 +587,35 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { pm.openProgram(program.getDomainFile()); } - /** - * Displays the function tag dialog. - */ private void showDialog() { performAction(editFunctionTags, cb.getProvider(), false); - provider = waitForComponentProvider(FunctionTagsComponentProvider.class); + provider = waitForComponentProvider(FunctionTagProvider.class); tool.showComponentProvider(provider, true); } - /** - * Places the code browser cursor at the given function entry point. - * - * @param address entry point of a function - */ private void goToFunction(Address address) { cb.goTo(new ProgramLocation(program, address)); assertEquals(address, cb.getCurrentAddress()); } - /** - * Adds a tag to the database. - * - * @param name the name of the tag - */ - private void createTag(String name) { + private void createTag(String nameList) { HintTextField inputField = provider.getTagInputField(); - setText(inputField, name); + setText(inputField, nameList); triggerEnter(inputField); + waitForTasks(); waitForTables(); - waitForSwing(); + + Collection tags = getAllTags(); + String[] parts = nameList.split(","); + for (String name : parts) { + if (StringUtils.isBlank(name)) { + continue; + } + name = name.trim(); + assertTrue("Tag not created '" + name + "' from text '" + nameList + "'", + tagExists(name.trim(), tags)); + } } private void waitForTables() { @@ -644,20 +625,14 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { FunctionTagTableModel smodel = sourcePanel.getModel(); waitForTableModel(tmodel); waitForTableModel(smodel); + waitForSwing(); } - /** - * Removes a tag from the database. - * - * @param name the name of the tag - * @throws IOException if there's an error retrieving tags from the database - */ - private void deleteTag(String name) throws IOException { + private void deleteTag(String name) { FunctionTag tag = getTagForName(name, getAllTags()); tx(program, () -> tag.delete()); waitForTables(); - waitForSwing(); } /** @@ -691,7 +666,7 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { */ private InMemoryFunctionTag getImmutableTag() throws UsrException { - FunctionTagTable table = getTagListInPanel("sourcePanel"); + FunctionTagTable table = getSourceTable(); FunctionTagTableModel model = (FunctionTagTableModel) table.getModel(); Optional optional = model.getModelData().stream().filter(row -> row.isImmutable()).findAny(); @@ -700,35 +675,15 @@ public class FunctionTagEditorTest extends AbstractGhidraHeadedIntegrationTest { return (InMemoryFunctionTag) foundTag; } - /** - * Returns all tags in the database. - * - * @return list of all tags in the database - */ private Collection getAllTags() { - FunctionManagerDB functionManager = (FunctionManagerDB) program.getFunctionManager(); - return functionManager.getFunctionTagManager().getAllFunctionTags(); + return provider.backgroundLoadTags(); } - /** - * Returns true if a tag is in the given list. - * - * @param name the tag name to search for - * @param tags the list to inspect - * @return true if found - */ private boolean tagExists(String name, Collection tags) { FunctionTag tag = getTagForName(name, tags); return tag != null; } - /** - * Returns the {@link FunctionTag} object with the given name. - * - * @param name the tag name - * @param tags the list to inspect - * @return function tag, or null if not found - */ private FunctionTag getTagForName(String name, Collection tags) { for (FunctionTag tag : tags) { if (tag.getName().equals(name)) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/HintTextField.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/HintTextField.java index 21f33dde8e..7ed92cc045 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/HintTextField.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/textfield/HintTextField.java @@ -56,7 +56,7 @@ public class HintTextField extends JTextField { * * @param hint the hint text * @param required true if the field should be marked as required - */ + */ public HintTextField(String hint, boolean required) { this(hint, required, null); } @@ -81,14 +81,14 @@ public class HintTextField extends JTextField { * Key listener allows us to check field validity on every key typed */ public void addListeners() { - - getDocument().addDocumentListener( new DocumentListener() { + + getDocument().addDocumentListener(new DocumentListener() { @Override public void removeUpdate(DocumentEvent e) { validateField(); } - + @Override public void insertUpdate(DocumentEvent e) { validateField(); @@ -119,18 +119,20 @@ public class HintTextField extends JTextField { public void paintComponent(Graphics g) { super.paintComponent(g); - if (getText().isEmpty()) { - if (g instanceof Graphics2D) { - Graphics2D g2 = (Graphics2D) g; - g2.setColor(Color.lightGray); - g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); - - if (hint != null) { - g2.drawString(hint, 7, 19); - } - } + if (!getText().isEmpty() || hint == null) { + return; } + + Graphics2D g2 = (Graphics2D) g; + g2.setColor(Color.LIGHT_GRAY); + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + + Rectangle bounds = getBounds(); + int x = 10; // offset + int y = bounds.height - bounds.y; // baseline of text; bottom of the text field + + g2.drawString(hint, x, y); } /** diff --git a/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ConcurrentListenerSet.java b/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ConcurrentListenerSet.java index 8ffb40b034..355e97e147 100644 --- a/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ConcurrentListenerSet.java +++ b/Ghidra/Framework/Generic/src/main/java/generic/concurrent/ConcurrentListenerSet.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. @@ -16,7 +15,7 @@ */ package generic.concurrent; -import java.util.Iterator; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** @@ -26,7 +25,7 @@ import java.util.concurrent.ConcurrentHashMap; public class ConcurrentListenerSet implements Iterable { // we use a ConcurrentHashMap because Java has no ConcurrentHashSet - private ConcurrentHashMap storage = new ConcurrentHashMap(); + private ConcurrentHashMap storage = new ConcurrentHashMap<>(); public void add(T t) { storage.put(t, t); @@ -44,4 +43,8 @@ public class ConcurrentListenerSet implements Iterable { public Iterator iterator() { return storage.keySet().iterator(); } + + public List asList() { + return new ArrayList<>(storage.keySet()); + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionDB.java index 67d28beb6f..7d1131530e 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionDB.java @@ -283,8 +283,9 @@ public class FunctionDB extends DatabaseObject implements Function { manager.lock.acquire(); try { checkIsValid(); - return manager.getCodeManager().getComment(CodeUnit.REPEATABLE_COMMENT, - getEntryPoint()); + return manager.getCodeManager() + .getComment(CodeUnit.REPEATABLE_COMMENT, + getEntryPoint()); } finally { manager.lock.release(); @@ -301,8 +302,9 @@ public class FunctionDB extends DatabaseObject implements Function { manager.lock.acquire(); try { checkDeleted(); - manager.getCodeManager().setComment(getEntryPoint(), CodeUnit.REPEATABLE_COMMENT, - comment); + manager.getCodeManager() + .setComment(getEntryPoint(), CodeUnit.REPEATABLE_COMMENT, + comment); } finally { manager.lock.release(); @@ -885,8 +887,9 @@ public class FunctionDB extends DatabaseObject implements Function { } } } - program.getBookmarkManager().setBookmark(getEntryPoint(), BookmarkType.ERROR, - "Bad Variables Removed", "Removed " + badSymbols.size() + " bad variables"); + program.getBookmarkManager() + .setBookmark(getEntryPoint(), BookmarkType.ERROR, + "Bad Variables Removed", "Removed " + badSymbols.size() + " bad variables"); for (Symbol s : badSymbols) { s.delete(); } @@ -2703,8 +2706,10 @@ public class FunctionDB extends DatabaseObject implements Function { callFixupMap.remove(entryPoint); } else { - if (program.getCompilerSpec().getPcodeInjectLibrary().getPayload( - InjectPayload.CALLFIXUP_TYPE, name, null, null) == null) { + if (program.getCompilerSpec() + .getPcodeInjectLibrary() + .getPayload( + InjectPayload.CALLFIXUP_TYPE, name, null, null) == null) { Msg.warn(this, "Undefined CallFixup set at " + entryPoint + ": " + name); } callFixupMap.add(entryPoint, name); @@ -2815,13 +2820,12 @@ public class FunctionDB extends DatabaseObject implements Function { tag = tagManager.createFunctionTag(name, ""); } - FunctionTagMappingAdapter mappingAdapter = tagManager.getFunctionTagMappingAdapter(); - if (mappingAdapter.getRecord(getID(), tag.getId()) == null) { - mappingAdapter.createFunctionTagRecord(getID(), tag.getId()); + if (!tagManager.isTagApplied(getID(), tag.getId())) { + tagManager.applyFunctionTag(getID(), tag.getId()); Address addr = getEntryPoint(); - program.setChanged(ChangeManager.DOCR_TAG_ADDED_TO_FUNCTION, addr, addr, null, - null); + program.setChanged(ChangeManager.DOCR_TAG_ADDED_TO_FUNCTION, addr, addr, tag, + tag); } // Add to local cache @@ -2829,9 +2833,6 @@ public class FunctionDB extends DatabaseObject implements Function { tags.add(tag); } } - catch (IOException e) { - manager.dbError(e); - } finally { endUpdate(); manager.lock.release(); @@ -2853,13 +2854,12 @@ public class FunctionDB extends DatabaseObject implements Function { FunctionTagManagerDB tagManager = (FunctionTagManagerDB) manager.getFunctionTagManager(); - FunctionTagMappingAdapter mappingAdapter = tagManager.getFunctionTagMappingAdapter(); - boolean removed = mappingAdapter.removeFunctionTagRecord(getID(), tag.getId()); + boolean removed = tagManager.removeFunctionTag(getID(), tag.getId()); if (removed) { Address addr = getEntryPoint(); - program.setChanged(ChangeManager.DOCR_TAG_REMOVED_FROM_FUNCTION, addr, addr, null, - null); + program.setChanged(ChangeManager.DOCR_TAG_REMOVED_FROM_FUNCTION, addr, addr, tag, + tag); // Remove from the local cache. if (tags != null) { @@ -2867,9 +2867,6 @@ public class FunctionDB extends DatabaseObject implements Function { } } } - catch (IOException e) { - manager.dbError(e); - } finally { endUpdate(); manager.lock.release(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionManagerDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionManagerDB.java index 51c9e5d443..96a6cf0018 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionManagerDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionManagerDB.java @@ -21,7 +21,8 @@ import java.util.function.Predicate; import db.*; import generic.FilteredIterator; -import ghidra.program.database.*; +import ghidra.program.database.DBObjectCache; +import ghidra.program.database.ProgramDB; import ghidra.program.database.code.CodeManager; import ghidra.program.database.external.ExternalLocationDB; import ghidra.program.database.map.AddressMap; @@ -48,7 +49,7 @@ import ghidra.util.task.TaskMonitor; * all function related calls are routed to this class. * */ -public class FunctionManagerDB implements ManagerDB, FunctionManager { +public class FunctionManagerDB implements FunctionManager { private final String CALLFIXUP_MAP = "CallFixup"; // string map used to store call-fixup name @@ -64,7 +65,7 @@ public class FunctionManagerDB implements ManagerDB, FunctionManager { private NamespaceManager namespaceMgr; private SymbolManager symbolMgr; private CodeManager codeMgr; - private FunctionTagManager functionTagManager; + private FunctionTagManagerDB functionTagManager; private Namespace globalNamespace; private Predicate functionFilter = f -> { @@ -352,7 +353,7 @@ public class FunctionManagerDB implements ManagerDB, FunctionManager { throw new IllegalArgumentException( "Function entryPoint may not be created on defined data"); } - + if (namespaceMgr.overlapsNamespace(body) != null) { throw new OverlappingFunctionException(entryPoint); } @@ -552,7 +553,7 @@ public class FunctionManagerDB implements ManagerDB, FunctionManager { // Remove all tag mappings associated with this function for (FunctionTag tag : function.getTags()) { - tag.delete(); + function.removeTag(tag.getName()); } long functionID = function.getID(); @@ -930,6 +931,7 @@ public class FunctionManagerDB implements ManagerDB, FunctionManager { public void invalidateCache(boolean all) { lock.acquire(); try { + functionTagManager.invalidateCache(); callFixupMap = null; lastFuncID = -1; cache.invalidate(); @@ -996,14 +998,16 @@ public class FunctionManagerDB implements ManagerDB, FunctionManager { */ FunctionIteratorDB(boolean external, boolean forward) { if (external) { - it = program.getSymbolTable().getSymbols( - new AddressSet(AddressSpace.EXTERNAL_SPACE.getMinAddress(), - AddressSpace.EXTERNAL_SPACE.getMaxAddress()), - SymbolType.FUNCTION, forward); + it = program.getSymbolTable() + .getSymbols( + new AddressSet(AddressSpace.EXTERNAL_SPACE.getMinAddress(), + AddressSpace.EXTERNAL_SPACE.getMaxAddress()), + SymbolType.FUNCTION, forward); } else { - it = program.getSymbolTable().getSymbols(program.getMemory(), SymbolType.FUNCTION, - forward); + it = program.getSymbolTable() + .getSymbols(program.getMemory(), SymbolType.FUNCTION, + forward); } } @@ -1144,8 +1148,7 @@ public class FunctionManagerDB implements ManagerDB, FunctionManager { list.add(symbol); } } - for (int i = 0; i < list.size(); i++) { - Symbol symbol = list.get(i); + for (Symbol symbol : list) { symbol.delete(); } } @@ -1477,6 +1480,7 @@ public class FunctionManagerDB implements ManagerDB, FunctionManager { } } + @Override public FunctionTagManager getFunctionTagManager() { return functionTagManager; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagDB.java index f372ea3490..241566da4a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagDB.java @@ -53,9 +53,10 @@ public class FunctionTagDB extends DatabaseObject implements FunctionTag { comment = ""; } - if (!comment.equals(record.getString(FunctionTagAdapter.COMMENT_COL))) { + String oldValue = record.getString(FunctionTagAdapter.COMMENT_COL); + if (!comment.equals(oldValue)) { record.setString(FunctionTagAdapter.COMMENT_COL, comment); - mgr.updateFunctionTag(this); + mgr.updateFunctionTag(this, oldValue, comment); } } @@ -77,9 +78,10 @@ public class FunctionTagDB extends DatabaseObject implements FunctionTag { name = ""; } - if (!name.equals(record.getString(FunctionTagAdapter.NAME_COL))) { + String oldValue = record.getString(FunctionTagAdapter.NAME_COL); + if (!name.equals(oldValue)) { record.setString(FunctionTagAdapter.NAME_COL, name); - mgr.updateFunctionTag(this); + mgr.updateFunctionTag(this, oldValue, name); } } catch (IOException e) { @@ -137,7 +139,7 @@ public class FunctionTagDB extends DatabaseObject implements FunctionTag { // is null, use whatever is in the database. if (rec == null) { try { - rec = mgr.getFunctionTagAdapter().getRecord(key); + rec = mgr.getTagRecord(key); } catch (IOException e) { mgr.dbError(e); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagManagerDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagManagerDB.java index 022140ce6a..6dc63ecbcc 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagManagerDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagManagerDB.java @@ -18,6 +18,8 @@ package ghidra.program.database.function; import java.io.IOException; import java.util.*; +import org.apache.commons.collections4.map.LazyMap; + import db.*; import db.util.ErrorHandler; import ghidra.program.database.DBObjectCache; @@ -25,6 +27,7 @@ import ghidra.program.database.ProgramDB; import ghidra.program.model.listing.*; import ghidra.program.util.ChangeManager; import ghidra.util.Lock; +import ghidra.util.datastruct.Counter; import ghidra.util.exception.CancelledException; import ghidra.util.exception.VersionException; import ghidra.util.task.TaskMonitor; @@ -41,6 +44,8 @@ public class FunctionTagManagerDB implements FunctionTagManager, ErrorHandler { private DBObjectCache cache; + private Map tagCountCache; + protected final Lock lock; /** @@ -53,7 +58,7 @@ public class FunctionTagManagerDB implements FunctionTagManager, ErrorHandler { * @throws VersionException if the database is incompatible with the current * schema * @throws IOException if there is a problem accessing the database. - * @throws CancelledException + * @throws CancelledException if the program loading is cancelled */ FunctionTagManagerDB(DBHandle handle, int openMode, Lock lock, TaskMonitor monitor) throws VersionException, IOException, CancelledException { @@ -62,10 +67,9 @@ public class FunctionTagManagerDB implements FunctionTagManager, ErrorHandler { functionTagAdapter = FunctionTagAdapter.getAdapter(handle, openMode, monitor); functionTagMappingAdapter = FunctionTagMappingAdapter.getAdapter(handle, openMode, monitor); - cache = new DBObjectCache(100); + cache = new DBObjectCache<>(100); } - @Override public void setProgram(Program program) { this.program = (ProgramDB) program; } @@ -172,21 +176,89 @@ public class FunctionTagManagerDB implements FunctionTagManager, ErrorHandler { return null; } - void updateFunctionTag(FunctionTagDB tag) throws IOException { + boolean isTagApplied(long functionId, long tagId) { + + lock.acquire(); + + try { + return functionTagMappingAdapter.getRecord(functionId, tagId) != null; + } + catch (IOException e) { + dbError(e); + } + finally { + lock.release(); + } + return false; + } + + void applyFunctionTag(long functionId, long tagId) { + lock.acquire(); + + try { + FunctionTag tag = getFunctionTag(tagId); + if (tag == null) { + return; // shouldn't happen + } + + functionTagMappingAdapter.createFunctionTagRecord(functionId, tagId); + incrementCountCache(tag); + } + catch (IOException e) { + dbError(e); + } + finally { + lock.release(); + } + } + + private void incrementCountCache(FunctionTag tag) { + if (tagCountCache != null) { + tagCountCache.get(tag).count++; + } + } + + private void decrementCountCache(FunctionTag tag) { + if (tagCountCache != null) { + tagCountCache.get(tag).count--; + } + } + + boolean removeFunctionTag(long functionId, long tagId) { + + lock.acquire(); + + try { + FunctionTag tag = getFunctionTag(tagId); + if (tag == null) { + return false; // shouldn't happen + } + + if (functionTagMappingAdapter.removeFunctionTagRecord(functionId, tagId)) { + decrementCountCache(tag); + return true; + } + } + catch (IOException e) { + dbError(e); + } + finally { + lock.release(); + } + return false; + } + + void updateFunctionTag(FunctionTagDB tag, String oldValue, String newValue) throws IOException { // Update the tag attributes. functionTagAdapter.updateRecord(tag.getRecord()); // Notify subscribers of the change. - fireTagChangedNotification(ChangeManager.DOCR_FUNCTION_TAG_CHANGED, tag); + fireTagChangedNotification(ChangeManager.DOCR_FUNCTION_TAG_CHANGED, tag, oldValue, + newValue); invalidateFunctions(); } - /* (non-Javadoc) - * TODO: Be a bit smarter about this - if the cache contains all the tags in - * the db we can just return the cache itself. If not, then go to the - * db to get the remainder. - */ @Override public List getAllFunctionTags() { @@ -211,27 +283,25 @@ public class FunctionTagManagerDB implements FunctionTagManager, ErrorHandler { return Collections.emptyList(); } - public FunctionTagAdapter getFunctionTagAdapter() { - return functionTagAdapter; + public Record getTagRecord(long id) throws IOException { + return functionTagAdapter.getRecord(id); } - public FunctionTagMappingAdapter getFunctionTagMappingAdapter() { - return functionTagMappingAdapter; - } - - /***************************************************************************** - * PRIVATE METHODS - *****************************************************************************/ +//================================================================================================== +// Private Methods +//================================================================================================== /** * Sends a notification when a tag has been changed (edited or deleted). * * @param type {@link ChangeManager} change type * @param tag the tag that was changed - * @throws IOException + * @param oldValue the old value + * @param newValue the new value */ - private void fireTagChangedNotification(int type, FunctionTag tag) throws IOException { - program.tagChanged(tag, type, null, null); + private void fireTagChangedNotification(int type, FunctionTag tag, String oldValue, + String newValue) { + program.tagChanged(tag, type, oldValue, newValue); } /** @@ -239,9 +309,8 @@ public class FunctionTagManagerDB implements FunctionTagManager, ErrorHandler { * * @param type {@link ChangeManager} change type * @param tag the tag that was created - * @throws IOException */ - private void fireTagCreatedNotification(int type, FunctionTag tag) throws IOException { + private void fireTagCreatedNotification(int type, FunctionTag tag) { program.tagCreated(tag, type); } @@ -250,10 +319,9 @@ public class FunctionTagManagerDB implements FunctionTagManager, ErrorHandler { * * @param type the type of change * @param tag the tag that was deleted - * @throws IOException */ - private void fireTagDeletedNotification(int type, FunctionTag tag) throws IOException { - program.tagChanged(tag, type, null, null); + private void fireTagDeletedNotification(int type, FunctionTag tag) { + program.tagChanged(tag, type, tag, null); } /** @@ -275,6 +343,7 @@ public class FunctionTagManagerDB implements FunctionTagManager, ErrorHandler { * Deletes the given function tag. * * @param tag the tag to delete + * @throws IOException if there is an issue reading from the db */ void doDeleteTag(FunctionTag tag) throws IOException { @@ -304,20 +373,56 @@ public class FunctionTagManagerDB implements FunctionTagManager, ErrorHandler { * Returns all function tags associated with the given function id. * * @param functionId the function id - * @return - * @throws IOException + * @return the tags + * @throws IOException if there is an issue reading from the db */ Set getFunctionTagsByFunctionID(long functionId) throws IOException { Set tags = new HashSet<>(); - RecordIterator functionTagMappingRecords = + RecordIterator functionRecords = functionTagMappingAdapter.getRecordsByFunctionID(functionId); - while (functionTagMappingRecords.hasNext()) { - Record mappingRecord = functionTagMappingRecords.next(); + while (functionRecords.hasNext()) { + Record mappingRecord = functionRecords.next(); Record tagRecord = functionTagAdapter.getRecord( mappingRecord.getLongValue(FunctionTagMappingAdapter.TAG_ID_COL)); tags.add(getFunctionTagFromCache(tagRecord)); } return tags; } + + void invalidateCache() { + cache.invalidate(); + tagCountCache = null; + } + + @Override + public int getUseCount(FunctionTag tag) { + lock.acquire(); + try { + if (tagCountCache == null) { + buildTagCountCache(); + } + Counter counter = tagCountCache.get(tag); + return counter.count; + } + catch (IOException e) { + dbError(e); + return 0; + } + finally { + lock.release(); + } + } + + private void buildTagCountCache() throws IOException { + Map map = LazyMap.lazyMap(new HashMap<>(), () -> new Counter()); + RecordIterator records = functionTagMappingAdapter.getRecords(); + while (records.hasNext()) { + Record mappingRecord = records.next(); + long tagId = mappingRecord.getLongValue(FunctionTagMappingAdapter.TAG_ID_COL); + FunctionTag tag = getFunctionTag(tagId); + map.get(tag).count++; + } + tagCountCache = map; + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagMappingAdapter.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagMappingAdapter.java index f9c9d03f96..151014c195 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagMappingAdapter.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagMappingAdapter.java @@ -32,7 +32,7 @@ abstract class FunctionTagMappingAdapter { static final String TABLE_NAME = "Function Tag Map"; static final int CURRENT_VERSION = 0; - + static final int FUNCTION_ID_COL = 0; static final int TAG_ID_COL = 1; @@ -74,7 +74,7 @@ abstract class FunctionTagMappingAdapter { * * @param functionID index into the {@link SymbolTable} table * @return iterator of database records - * @throws IOException + * @throws IOException if database error occurs */ abstract RecordIterator getRecordsByFunctionID(long functionID) throws IOException; @@ -84,17 +84,17 @@ abstract class FunctionTagMappingAdapter { * @param functionID index into the {@link SymbolTable} table * @param tagID index into the {@link FunctionTagAdapter} table * @return null if not found - * @throws IOException + * @throws IOException if database error occurs */ abstract Record getRecord(long functionID, long tagID) throws IOException; /** * Creates a new record with the given function and tag ID's. * - * @param functionID - * @param tagID + * @param functionID the function's database id + * @param tagID the FunctionTags database id * @return newly-created database record - * @throws IOException + * @throws IOException if database error occurs */ abstract Record createFunctionTagRecord(long functionID, long tagID) throws IOException; @@ -106,7 +106,7 @@ abstract class FunctionTagMappingAdapter { * @param functionID index into the {@link SymbolTable} table * @param tagID index into the {@link FunctionTagAdapter} table * @return true if the remove was performed - * @throws IOException + * @throws IOException if database error occurs */ abstract boolean removeFunctionTagRecord(long functionID, long tagID) throws IOException; @@ -116,16 +116,22 @@ abstract class FunctionTagMappingAdapter { * whenever a tag is being deleted from the system. * * @param tagID index into the {@link FunctionTagAdapter} table - * @throws IOException + * @throws IOException if database error occurs */ abstract void removeFunctionTagRecord(long tagID) throws IOException; /** * Determine if the specified tag ID has been applied to a function - * @param tagId tag ID + * @param id tag ID * @return true if tag applied to one or more functions - * @throws IOException + * @throws IOException if database error occurs */ abstract boolean isTagAssigned(long id) throws IOException; + /** + * Returns a RecordIterator over all the records in this table + * @return a RecordIterator over all the records in this table + * @throws IOException if database error occurs + */ + protected abstract RecordIterator getRecords() throws IOException; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagMappingAdapterNoTable.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagMappingAdapterNoTable.java index 789432521b..71dd0a0fe3 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagMappingAdapterNoTable.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagMappingAdapterNoTable.java @@ -65,4 +65,9 @@ class FunctionTagMappingAdapterNoTable extends FunctionTagMappingAdapter { boolean isTagAssigned(long id) throws IOException { return false; } + + @Override + protected RecordIterator getRecords() throws IOException { + return new EmptyRecordIterator(); + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagMappingAdapterV0.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagMappingAdapterV0.java index 6bc1de6842..0b5ece9e93 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagMappingAdapterV0.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/function/FunctionTagMappingAdapterV0.java @@ -147,6 +147,14 @@ class FunctionTagMappingAdapterV0 extends FunctionTagMappingAdapter implements D return table.indexIterator(V0_FUNCTION_ID_COL, value, value, true); } + @Override + protected RecordIterator getRecords() throws IOException { + if (table == null) { + return new EmptyRecordIterator(); + } + return table.iterator(); + } + @Override boolean isTagAssigned(long tagID) throws IOException { if (table == null) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/Function.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/Function.java index 3bd8278a61..d8e0f388b4 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/Function.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/Function.java @@ -29,10 +29,7 @@ import ghidra.util.exception.InvalidInputException; import ghidra.util.task.TaskMonitor; /** - * Interface to define methods available on a function. Functions - * have a single entry point. - * - * + * Interface to define methods available on a function. Functions have a single entry point. */ public interface Function extends Namespace { @@ -110,8 +107,8 @@ public interface Function extends Namespace { public void setCallFixup(String name); /** - * Returns the current call-fixup name set on this instruction or null - * if one has not been set. + * Returns the current call-fixup name set on this instruction or null if one has not been set + * @return the name */ public String getCallFixup(); @@ -131,6 +128,7 @@ public interface Function extends Namespace { /** * Returns the function (same as plate) comment as an array of strings where * each item in the array is a line of text in the comment. + * @return the comments */ public String[] getCommentAsArray(); @@ -194,11 +192,15 @@ public interface Function extends Namespace { /** * Set the return data-type and storage. - *

    NOTE: The storage and source are ignored if the function does not have custom storage enabled. - * @param type - * @param storage + * + *

    NOTE: The storage and source are ignored if the function does not have custom storage + * enabled. + * + * @param type the data type + * @param storage the storage * @param source source to be combined with the overall signature source. - * @throws InvalidInputException if data type is not a fixed length or storage is improperly sized + * @throws InvalidInputException if data type is not a fixed length or storage is improperly + * sized */ public void setReturn(DataType type, VariableStorage storage, SourceType source) throws InvalidInputException; @@ -216,21 +218,22 @@ public interface Function extends Namespace { /** * Get the function's signature. - *

    WARNING! It is important to note that the calling convention may not be properly retained - * by the returned signature object if a non-generic calling convention is used by this function as - * defined by the program's compiler specification. + *

    WARNING! It is important to note that the calling convention may not be properly + * retained by the returned signature object if a non-generic calling convention is used by + * this function as defined by the program's compiler specification. + * * @param formalSignature if true only original raw types will be retained and * auto-params discarded (e.g., this, __return_storage_ptr__, etc.) within the returned * signature. If false, the effective signature will be returned where forced indirect * and auto-params are reflected in the signature. This option has no affect if the specified * function has custom storage enabled. - * * @return the function's signature */ public FunctionSignature getSignature(boolean formalSignature); /** * Return a string representation of the function signature + * * @param formalSignature if true only original raw return/parameter types will be retained and * auto-params discarded (e.g., this, __return_storage_ptr__, etc.) within the returned * signature. If false, the effective signature will be returned where forced indirect @@ -238,6 +241,7 @@ public interface Function extends Namespace { * function has custom storage enabled. * @param includeCallingConvention if true prototype will include call convention * declaration if known. + * @return the prototype */ public String getPrototypeString(boolean formalSignature, boolean includeCallingConvention); @@ -464,6 +468,7 @@ public interface Function extends Namespace { * @param fromOrdinal from ordinal position using the current numbering * @param toOrdinal the final position of the specified parameter * @return parameter which was moved + * @throws InvalidInputException if either ordinal is invalid * @deprecated The use of this method is discouraged. The function signature should generally be * adjusted with a single call to {@link #updateFunction(String, Variable, List, FunctionUpdateType, boolean, SourceType)} */ @@ -520,7 +525,7 @@ public interface Function extends Namespace { /** * Returns an array of all local and parameter variables - * @return + * @return the variables */ public Variable[] getAllVariables(); @@ -533,6 +538,7 @@ public interface Function extends Namespace { * @return the Variable added to the program. * @throws DuplicateNameException if another local variable or parameter already * has that name. + * @throws InvalidInputException if there is an error or conflict when resolving the variable */ public Variable addLocalVariable(Variable var, SourceType source) throws DuplicateNameException, InvalidInputException; @@ -553,14 +559,16 @@ public interface Function extends Namespace { public void setBody(AddressSetView newBody) throws OverlappingFunctionException; /** - * Returns true if this function has a variable argument list (VarArgs). + * Returns true if this function has a variable argument list (VarArgs) + * @return true if this function has a variable argument list (VarArgs) */ public boolean hasVarArgs(); /** - * Set whether parameters can be passed as a VarArg (variable argument list). + * Set whether parameters can be passed as a VarArg (variable argument list) * - * @param hasVarArgs true if this function has a variable argument list (ie printf(fmt, ...)). + * @param hasVarArgs true if this function has a variable argument list + * (e.g., printf(fmt, ...)). */ public void setVarArgs(boolean hasVarArgs); @@ -595,7 +603,7 @@ public interface Function extends Namespace { /** * Set whether or not this function uses custom variable storage - * @param hasCustomVariableStorage + * @param hasCustomVariableStorage true if this function uses custom storage */ public void setCustomVariableStorage(boolean hasCustomVariableStorage); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/FunctionManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/FunctionManager.java index 6414529a08..be598d8661 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/FunctionManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/FunctionManager.java @@ -15,22 +15,29 @@ */ package ghidra.program.model.listing; -import java.io.IOException; import java.util.Iterator; import java.util.List; -import ghidra.program.database.ProgramDB; +import ghidra.program.database.ManagerDB; import ghidra.program.database.function.OverlappingFunctionException; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSetView; import ghidra.program.model.lang.PrototypeModel; import ghidra.program.model.symbol.Namespace; import ghidra.program.model.symbol.SourceType; -import ghidra.util.exception.*; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.InvalidInputException; import ghidra.util.task.TaskMonitor; -public interface FunctionManager { +/** + * The manager for functions + */ +public interface FunctionManager extends ManagerDB { + /** + * Returns this manager's program + * @return the program + */ public Program getProgram(); /** @@ -50,8 +57,8 @@ public interface FunctionManager { public PrototypeModel getDefaultCallingConvention(); /** - * Gets the prototype model of the calling convention with the specified name in this program. - * + * Gets the prototype model of the calling convention with the specified name in this program + * @param name the calling convention name * @return the named function calling convention prototype model or null. */ public PrototypeModel getCallingConvention(String name); @@ -113,25 +120,28 @@ public interface FunctionManager { throws OverlappingFunctionException; /** - * Returns the total number of functions in the program including external functions. + * Returns the total number of functions in the program including external functions + * @return the count */ public int getFunctionCount(); /** - * Remove a function defined at entryPoint. + * Remove a function defined at entryPoint + * @param entryPoint the entry point + * @return true if the function was removed */ public boolean removeFunction(Address entryPoint); /** - * Get the function at entryPoint. - * - * @return null if there is no function at entryPoint. + * Get the function at entryPoint + * @param entryPoint the entry point + * @return null if there is no function at entryPoint */ public Function getFunctionAt(Address entryPoint); /** - * Get the function which resides at the specified address or is referenced from the specified - * address. + * Get the function which resides at the specified address or is referenced from the specified + * address * * @param address function address or address of pointer to a function. * @return referenced function or null @@ -147,19 +157,18 @@ public interface FunctionManager { public Function getFunctionContaining(Address addr); /** - * Returns an iterator over all non-external functions in address (entry point) order. - * + * Returns an iterator over all non-external functions in address (entry point) order * @param forward true means to iterate in ascending address order + * @return the iterator */ public FunctionIterator getFunctions(boolean forward); /** * Get an iterator over non-external functions starting at an address and ordered by entry - * address. + * address * * @param start starting address * @param forward true means to iterate in ascending address order - * * @return an iterator over functions. */ public FunctionIterator getFunctions(Address start, boolean forward); @@ -176,9 +185,10 @@ public interface FunctionManager { /** * Returns an iterator over all REAL functions in address (entry point) order (real functions - * have instructions, and aren't stubs). + * have instructions, and aren't stubs) * * @param forward true means to iterate in ascending address order + * @return the iterator */ public FunctionIterator getFunctionsNoStubs(boolean forward); @@ -220,39 +230,6 @@ public interface FunctionManager { */ public boolean isInFunction(Address addr); - /** - * @see ghidra.program.database.ManagerDB#moveAddressRange(ghidra.program.model.address.Address, - * ghidra.program.model.address.Address, long, ghidra.util.task.TaskMonitor) - */ - public void moveAddressRange(Address fromAddr, Address toAddr, long length, TaskMonitor monitor) - throws CancelledException; - - /** - * @see ghidra.program.database.ManagerDB#deleteAddressRange(ghidra.program.model.address.Address, - * ghidra.program.model.address.Address, ghidra.util.task.TaskMonitor) - */ - public void deleteAddressRange(Address startAddr, Address endAddr, TaskMonitor monitor) - throws CancelledException; - - /** - * @see ghidra.program.database.ManagerDB#setProgram(ghidra.program.database.ProgramDB) - */ - public void setProgram(ProgramDB program); - - /** - * @param currentRevision TODO - * @see ghidra.program.database.ManagerDB#programReady(int, int, ghidra.util.task.TaskMonitor) - */ - public void programReady(int openMode, int currentRevision, TaskMonitor monitor) - throws IOException, CancelledException; - - /* - * (non-Javadoc) - * - * @see ghidra.program.database.ManagerDB#invalidateCache(boolean) - */ - public void invalidateCache(boolean all); - /** * Return an iterator over functions that overlap the given address set. * @@ -268,10 +245,10 @@ public interface FunctionManager { * function. While this does not account for the actual instruction flow, it is hopefully * accurate enough for most situations. * - * @param instrAddr - * @param storageAddr + * @param instrAddr the instruction address + * @param storageAddr the storage address * @param size varnode size in bytes (1 is assumed if value <= 0) - * @param isRead + * @param isRead true if the reference is a read reference * @return referenced variable or null if one not found */ public Variable getReferencedVariable(Address instrAddr, Address storageAddr, int size, @@ -284,4 +261,30 @@ public interface FunctionManager { */ public Function getFunction(long key); + /** + * Returns the function tag manager + * @return the function tag manager + */ + public FunctionTagManager getFunctionTagManager(); + + /** + * Clears all data caches + * @param all if false, some managers may not need to update their cache if they can + * tell that its not necessary. If this flag is true, then all managers should clear + * their cache no matter what. + */ + @Override + public void invalidateCache(boolean all); // note: redeclared to not throw an exception + + /** + * Move all objects within an address range to a new location + * @param fromAddr the first address of the range to be moved + * @param toAddr the address where to the range is to be moved + * @param length the number of addresses to move + * @param monitor the task monitor to use in any upgrade operations + * @throws CancelledException if the user cancelled the operation via the task monitor + */ + @Override + void moveAddressRange(Address fromAddr, Address toAddr, long length, TaskMonitor monitor) + throws CancelledException; // note: redeclared to not throw an AddressOverflowException } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/FunctionTagManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/FunctionTagManager.java index c824c4a29f..6add062838 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/FunctionTagManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/FunctionTagManager.java @@ -18,39 +18,39 @@ package ghidra.program.model.listing; import java.util.List; /** - * Interface for managing function tags. Tags are simple objects consisting of - * a name and an optional comment, which can be applied to functions in Ghidra. + * Interface for managing function tags. Tags are simple objects consisting of a name and an + * optional comment, which can be applied to functions. * - * @see ghidra.program.database.function.FunctionTagAdapter FunctionTagAdapter - * @see ghidra.program.database.function.FunctionTagMappingAdapter FunctionTagMappingAdapter + * See ghidra.program.database.function.FunctionTagAdapter + * See ghidra.program.database.function.FunctionTagMappingAdapter */ public interface FunctionTagManager { /** - * Returns the function tag with the given name. + * Returns the function tag with the given name * * @param name the tag name * @return the function tag, or null if not found */ - FunctionTag getFunctionTag(String name); + public FunctionTag getFunctionTag(String name); /** - * Returns the function tag with the given database id. + * Returns the function tag with the given database id * * @param id the tags database id * @return the function tag, or null if not found */ - FunctionTag getFunctionTag(long id); + public FunctionTag getFunctionTag(long id); /** - * Returns all function tags in the database. + * Returns all function tags in the database * * @return list of function tags */ - List getAllFunctionTags(); + public List getAllFunctionTags(); /** - * Returns true if the given tag is assigned to a function. + * Returns true if the given tag is assigned to a function * * @param name the tag name * @return true if assigned to a function @@ -65,13 +65,12 @@ public interface FunctionTagManager { * @param comment the comment associated with the tag (optional) * @return the new function tag */ - FunctionTag createFunctionTag(String name, String comment); + public FunctionTag createFunctionTag(String name, String comment); /** - * Sets the program on this manager to allow the manager to persist changes - * and notify subscribers. - * - * @param program the program + * Returns the number of times the given tag has been applied to a function + * @param tag the tag + * @return the count */ - void setProgram(Program program); + public int getUseCount(FunctionTag tag); } diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/FunctionManagerTestDouble.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/FunctionManagerTestDouble.java index e4d918be73..e82f26d818 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/FunctionManagerTestDouble.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/FunctionManagerTestDouble.java @@ -187,4 +187,8 @@ public class FunctionManagerTestDouble implements FunctionManager { throw new UnsupportedOperationException(); } + @Override + public FunctionTagManager getFunctionTagManager() { + throw new UnsupportedOperationException(); + } } diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionTagPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionTagPluginScreenShots.java index 7bf45555ba..a1f332cf48 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionTagPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionTagPluginScreenShots.java @@ -32,58 +32,47 @@ import ghidra.program.model.listing.Function; import ghidra.program.model.listing.FunctionIterator; import ghidra.program.util.ProgramLocation; import ghidra.util.Swing; -import ghidra.util.exception.UsrException; public class FunctionTagPluginScreenShots extends GhidraScreenShotGenerator { @Test public void testFullWindow() { - showProvider(FunctionTagsComponentProvider.class); + showProvider(FunctionTagProvider.class); waitForSwing(); addTableData(); - captureIsolatedProvider(FunctionTagsComponentProvider.class, 950, 400); + captureIsolatedProvider(FunctionTagProvider.class, 950, 400); } @Test public void testInputField() { - showProvider(FunctionTagsComponentProvider.class); + showProvider(FunctionTagProvider.class); waitForSwing(); - FunctionTagsComponentProvider provider = getProvider(FunctionTagsComponentProvider.class); - final JPanel inputPanel = (JPanel) getInstanceField("inputPanel", provider); + FunctionTagProvider provider = getProvider(FunctionTagProvider.class); + final JPanel inputPanel = provider.getInputPanel(); captureComponent(inputPanel); } - @Test - public void testFilterField() { - showProvider(FunctionTagsComponentProvider.class); - waitForSwing(); - FunctionTagsComponentProvider provider = getProvider(FunctionTagsComponentProvider.class); - final JPanel filterPanel = (JPanel) getInstanceField("filterPanel", provider); - captureComponent(filterPanel); - } - /** * For this test the item in row 1 of the available tags table must be an editable * item. If not, edit the function_tags.xml file to remove all items and re-run this. */ @Test public void testEditTag() { - showProvider(FunctionTagsComponentProvider.class); + showProvider(FunctionTagProvider.class); waitForSwing(); addTableData(); - FunctionTagsComponentProvider provider = getProvider(FunctionTagsComponentProvider.class); - SourceTagsPanel sourcePanel = (SourceTagsPanel) getInstanceField("sourcePanel", provider); - FunctionTagTable table = (FunctionTagTable) getInstanceField("table", sourcePanel); + FunctionTagProvider provider = getProvider(FunctionTagProvider.class); + SourceTagsPanel sourcePanel = provider.getSourcePanel(); + FunctionTagTable table = sourcePanel.getTable(); Rectangle bounds = table.getCellRect(7, 0, false); // Cell 7 is an editable item doubleClick(table, bounds.x, bounds.y); - InputDialog warningDialog = waitForDialogComponent(InputDialog.class); - - captureDialog(warningDialog); + InputDialog editDialog = waitForDialogComponent(InputDialog.class); + captureDialog(editDialog); } - /** + /* * Captures the warning dialog when trying to delete a tag. Note that this assumes the * tag in row 1 is NOT read-only. If that's the not the case, modify the function_tags.xml * file to remove any tags that may be interfering with this. @@ -91,12 +80,12 @@ public class FunctionTagPluginScreenShots extends GhidraScreenShotGenerator { * @throws UsrException */ @Test - public void testDeleteWarning() throws UsrException { - showProvider(FunctionTagsComponentProvider.class); + public void testDeleteWarning() { + showProvider(FunctionTagProvider.class); waitForSwing(); addTableData(); - FunctionTagsComponentProvider provider = getProvider(FunctionTagsComponentProvider.class); + FunctionTagProvider provider = getProvider(FunctionTagProvider.class); SourceTagsPanel sourcePanel = (SourceTagsPanel) getInstanceField("sourcePanel", provider); FunctionTagTable table = (FunctionTagTable) getInstanceField("table", sourcePanel); table.setRowSelectionInterval(7, 7); @@ -107,20 +96,20 @@ public class FunctionTagPluginScreenShots extends GhidraScreenShotGenerator { captureDialog(warningDialog); } - /** + /* * Captures the read-only warning when trying to edit a tag * * @throws UsrException */ @Test - public void testEditNotAllowedWarning() throws UsrException { - showProvider(FunctionTagsComponentProvider.class); + public void testEditNotAllowedWarning() { + showProvider(FunctionTagProvider.class); waitForSwing(); addTableData(); - FunctionTagsComponentProvider provider = getProvider(FunctionTagsComponentProvider.class); - SourceTagsPanel sourcePanel = (SourceTagsPanel) getInstanceField("sourcePanel", provider); - FunctionTagTable table = (FunctionTagTable) getInstanceField("table", sourcePanel); + FunctionTagProvider provider = getProvider(FunctionTagProvider.class); + SourceTagsPanel sourcePanel = provider.getSourcePanel(); + FunctionTagTable table = sourcePanel.getTable(); doubleClickItem(table, "LIBRARY"); // pick a known read-only tag OptionDialog warningDialog = waitForDialogComponent(OptionDialog.class); @@ -151,7 +140,7 @@ public class FunctionTagPluginScreenShots extends GhidraScreenShotGenerator { private void addTableData() { - FunctionTagsComponentProvider provider = getProvider(FunctionTagsComponentProvider.class); + FunctionTagProvider provider = getProvider(FunctionTagProvider.class); Swing.runNow(() -> { provider.programActivated(program); @@ -171,7 +160,7 @@ public class FunctionTagPluginScreenShots extends GhidraScreenShotGenerator { * * @param provider the component provider */ - private void navigateToFunction(FunctionTagsComponentProvider provider) { + private void navigateToFunction(FunctionTagProvider provider) { FunctionIterator iter = program.getFunctionManager().getFunctions(true); while (iter.hasNext()) { Function func = iter.next();