diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/actions/AbstractFindReferencesDataTypeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/actions/AbstractFindReferencesDataTypeAction.java index 9c3a550ca7..3c28dfd29b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/actions/AbstractFindReferencesDataTypeAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/actions/AbstractFindReferencesDataTypeAction.java @@ -25,6 +25,7 @@ import docking.DockingUtils; import docking.action.*; import ghidra.app.plugin.core.navigation.FindAppliedDataTypesService; import ghidra.app.plugin.core.navigation.locationreferences.ReferenceUtils; +import ghidra.app.util.HelpTopics; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.data.Composite; import ghidra.program.model.data.DataType; @@ -32,6 +33,7 @@ import ghidra.util.*; public abstract class AbstractFindReferencesDataTypeAction extends DockingAction { + private static final String HELP_TOPIC = HelpTopics.FIND_REFERENCES; public static final String NAME = "Find References To"; public static final KeyStroke DEFAULT_KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_F, DockingUtils.CONTROL_KEY_MODIFIER_MASK | InputEvent.SHIFT_DOWN_MASK); @@ -46,7 +48,7 @@ public abstract class AbstractFindReferencesDataTypeAction extends DockingAction super(name, owner, KeyBindingType.SHARED); this.tool = tool; - setHelpLocation(new HelpLocation("LocationReferencesPlugin", "Data_Types")); + setHelpLocation(new HelpLocation(HELP_TOPIC, "Data_Types")); setDescription("Shows all uses of the selected data type"); initKeyStroke(defaultKeyStroke); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/actions/AbstractFindReferencesToAddressAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/actions/AbstractFindReferencesToAddressAction.java new file mode 100644 index 0000000000..57c50b4ee8 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/actions/AbstractFindReferencesToAddressAction.java @@ -0,0 +1,106 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.actions; + +import docking.action.KeyBindingType; +import ghidra.app.context.NavigatableActionContext; +import ghidra.app.context.NavigatableContextAction; +import ghidra.app.plugin.core.navigation.locationreferences.LocationReferencesService; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.*; +import ghidra.program.util.AddressFieldLocation; +import ghidra.program.util.ProgramLocation; +import ghidra.util.HelpLocation; +import ghidra.util.Msg; + +/** + * Only shows addresses to the code unit at the address for the current context. This differs + * from the normal 'find references' action in that it will find references by inspecting + * context for more information, potentially searching for more than just direct references to + * the code unit at the current address. + */ +public abstract class AbstractFindReferencesToAddressAction extends NavigatableContextAction { + + public static final String NAME = "Show References To Address"; + private static final String HELP_TOPIC = "LocationReferencesPlugin"; + + private PluginTool tool; + + protected AbstractFindReferencesToAddressAction(PluginTool tool, String owner) { + super(NAME, owner, KeyBindingType.SHARED); + this.tool = tool; + + setDescription("Shows references to the current Instruction or Data"); + setHelpLocation(new HelpLocation(HELP_TOPIC, "Show_Refs_To_Code_Unit")); + } + + @Override + public void actionPerformed(NavigatableActionContext context) { + + LocationReferencesService service = tool.getService(LocationReferencesService.class); + if (service == null) { + Msg.showError(this, null, "Missing Plugin", + "The " + LocationReferencesService.class.getSimpleName() + " is not installed.\n" + + "Please add the plugin implementing this service."); + return; + } + + Program program = context.getProgram(); + ProgramLocation location = getLocation(context); + Address address = location.getAddress(); + Listing listing = program.getListing(); + CodeUnit cu = listing.getCodeUnitContaining(address); + + int[] path = location.getComponentPath(); + if (cu instanceof Data) { + Data outerData = (Data) cu; + Data data = outerData.getComponent(location.getComponentPath()); + address = data.getMinAddress(); + } + + AddressFieldLocation addressLocation = + new AddressFieldLocation(program, address, path, address.toString(), 0); + service.showReferencesToLocation(addressLocation, context.getNavigatable()); + } + + @Override + protected boolean isEnabledForContext(NavigatableActionContext context) { + + Program program = context.getProgram(); + ProgramLocation location = getLocation(context); + if (location == null) { + return false; + } + + Address address = location.getAddress(); + if (address == null) { + return false; + } + + Listing listing = program.getListing(); + CodeUnit cu = listing.getCodeUnitContaining(address); + if (cu == null) { + return false; + } + + return true; + } + + protected ProgramLocation getLocation(NavigatableActionContext context) { + return context.getLocation(); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/context/NavigatableContextAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/context/NavigatableContextAction.java index a1402b2ce2..1334810ad1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/context/NavigatableContextAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/context/NavigatableContextAction.java @@ -19,6 +19,7 @@ import java.util.Set; import docking.ActionContext; import docking.action.DockingAction; +import docking.action.KeyBindingType; public abstract class NavigatableContextAction extends DockingAction { @@ -26,6 +27,10 @@ public abstract class NavigatableContextAction extends DockingAction { super(name, owner); } + public NavigatableContextAction(String name, String owner, KeyBindingType type) { + super(name, owner, type); + } + @Override public boolean isEnabledForContext(ActionContext context) { if (!(context instanceof NavigatableActionContext)) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorActionManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorActionManager.java index c58a4cc462..23dbf18328 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorActionManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CompositeEditorActionManager.java @@ -15,15 +15,14 @@ */ package ghidra.app.plugin.core.compositeeditor; -import ghidra.app.services.DataTypeManagerService; -import ghidra.framework.options.Options; -import ghidra.program.model.data.*; - import java.util.*; import javax.swing.KeyStroke; import docking.action.KeyBindingData; +import ghidra.app.services.DataTypeManagerService; +import ghidra.framework.options.Options; +import ghidra.program.model.data.*; /** * A CompositeEditorActionManager manages the actions for a single composite editor. @@ -32,7 +31,8 @@ import docking.action.KeyBindingData; */ public class CompositeEditorActionManager { private CompositeEditorProvider provider; - private ArrayList editorActions = new ArrayList(); + private ArrayList editorActions = + new ArrayList(); private ArrayList favoritesActions = new ArrayList(); private ArrayList cycleGroupActions = new ArrayList(); @@ -46,9 +46,7 @@ public class CompositeEditorActionManager { * Constructor *
NOTE: After constructing a manager, you must call setEditorModel() * and setParentComponent() for the actions to work. - * @param plugin the plugin that owns this composite editor action manager - * @param program the associated program for obtaining data types. - * @param dataTypeMgrService the data type manager service for the + * @param provider the provider that owns this composite editor action manager * favorites and cycle groups. */ public CompositeEditorActionManager(CompositeEditorProvider provider) { @@ -56,7 +54,8 @@ public class CompositeEditorActionManager { this.dataTypeMgrService = provider.dtmService; adapter = new DataTypeManagerChangeListenerAdapter() { @Override - public void favoritesChanged(DataTypeManager dtm, DataTypePath path, boolean isFavorite) { + public void favoritesChanged(DataTypeManager dtm, DataTypePath path, + boolean isFavorite) { setFavoritesActions(dataTypeMgrService.getFavorites()); } }; @@ -192,8 +191,8 @@ public class CompositeEditorActionManager { */ public void setEditorActions(CompositeEditorTableAction[] actions) { editorActions.clear(); - for (int i = 0; i < actions.length; i++) { - editorActions.add(actions[i]); + for (CompositeEditorTableAction action : actions) { + editorActions.add(action); } } @@ -226,20 +225,24 @@ public class CompositeEditorActionManager { } private void notifyActionsAdded(ArrayList actions) { - if (actions.size() <= 0) + if (actions.size() <= 0) { return; + } int length = listeners.size(); - CompositeEditorTableAction[] cea = actions.toArray(new CompositeEditorTableAction[actions.size()]); + CompositeEditorTableAction[] cea = + actions.toArray(new CompositeEditorTableAction[actions.size()]); for (int i = 0; i < length; i++) { listeners.get(i).actionsAdded(cea); } } private void notifyActionsRemoved(ArrayList actions) { - if (actions.size() <= 0) + if (actions.size() <= 0) { return; + } int length = listeners.size(); - CompositeEditorTableAction[] cea = actions.toArray(new CompositeEditorTableAction[actions.size()]); + CompositeEditorTableAction[] cea = + actions.toArray(new CompositeEditorTableAction[actions.size()]); for (int i = 0; i < length; i++) { listeners.get(i).actionsRemoved(cea); } @@ -252,14 +255,14 @@ public class CompositeEditorActionManager { // Update the editor actions here. // The favorites and cycle groups get handled by stateChanged() and cyclegroupChanged(). CompositeEditorTableAction[] actions = getEditorActions(); - for (int i = 0; i < actions.length; i++) { - String actionName = actions[i].getFullName(); + for (CompositeEditorTableAction action : actions) { + String actionName = action.getFullName(); if (actionName.equals(name)) { - KeyStroke actionKs = actions[i].getKeyBinding(); + KeyStroke actionKs = action.getKeyBinding(); KeyStroke oldKs = (KeyStroke) oldValue; KeyStroke newKs = (KeyStroke) newValue; if (actionKs == oldKs) { - actions[i].setUnvalidatedKeyBindingData(new KeyBindingData(newKs)); + action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs)); } break; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CycleGroupAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CycleGroupAction.java index 3e66294914..468f726098 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CycleGroupAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/CycleGroupAction.java @@ -27,7 +27,7 @@ import ghidra.program.model.data.CycleGroup; */ public class CycleGroupAction extends CompositeEditorTableAction { - private final static String GROUP_NAME = CYCLE_ACTION_GROUP; + private final static String GROUP_NAME = DATA_ACTION_GROUP; private CycleGroup cycleGroup; public CycleGroupAction(CompositeEditorProvider provider, CycleGroup cycleGroup) { @@ -35,7 +35,7 @@ public class CycleGroupAction extends CompositeEditorTableAction { new String[] { "Cycle", cycleGroup.getName() }, new String[] { "Cycle", cycleGroup.getName() }, null, KeyBindingType.SHARED); this.cycleGroup = cycleGroup; - + getPopupMenuData().setParentMenuGroup(GROUP_NAME); initKeyStroke(cycleGroup.getDefaultKeyStroke()); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditComponentAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditComponentAction.java index 072cdb10c1..878770de4d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditComponentAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditComponentAction.java @@ -15,12 +15,11 @@ */ package ghidra.app.plugin.core.compositeeditor; +import docking.ActionContext; import ghidra.app.services.DataTypeManagerService; import ghidra.program.model.data.*; import ghidra.program.model.data.Enum; -import docking.ActionContext; - /** * Action for use in the composite data type editor. * This action has help associated with it. @@ -34,16 +33,6 @@ public class EditComponentAction extends CompositeEditorTableAction { private static String[] menuPath = new String[] { ACTION_NAME + "..." }; private DataTypeManagerService dtmService; - /** - * @param id - * @param group - * @param owner - * @param popupPath - * @param menuPath - * @param icon - * @param useToolbar - * @param checkBox - */ public EditComponentAction(CompositeEditorProvider provider) { super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, popupPath, menuPath, null); this.dtmService = provider.dtmService; @@ -51,9 +40,6 @@ public class EditComponentAction extends CompositeEditorTableAction { adjustEnablement(); } - /* (non-Javadoc) - * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) - */ @Override public void actionPerformed(ActionContext context) { int row = model.getRow(); @@ -80,9 +66,6 @@ public class EditComponentAction extends CompositeEditorTableAction { requestTableFocus(); } - /* (non-Javadoc) - * @see ghidra.app.plugin.datamanager.editor.CompositeEditorAction#adjustEnablement() - */ @Override public void adjustEnablement() { setEnabled(model.isEditComponentAllowed()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditorAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditorAction.java index c8b8fb9ec9..f1d0e26536 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditorAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/EditorAction.java @@ -18,10 +18,9 @@ package ghidra.app.plugin.core.compositeeditor; public interface EditorAction extends CompositeEditorModelListener { static final String BASIC_ACTION_GROUP = "1_BASIC_EDITOR_ACTION"; - static final String FAVORITES_ACTION_GROUP = "2_FAVORITE_DT_EDITOR_ACTION"; - static final String CYCLE_ACTION_GROUP = "3_CYCLE_DT_EDITOR_ACTION"; - static final String COMPONENT_ACTION_GROUP = "4_COMPONENT_EDITOR_ACTION"; - static final String BITFIELD_ACTION_GROUP = "5_COMPONENT_EDITOR_ACTION"; + static final String DATA_ACTION_GROUP = "2_DATA_EDITOR_ACTION"; + static final String COMPONENT_ACTION_GROUP = "3_COMPONENT_EDITOR_ACTION"; + static final String BITFIELD_ACTION_GROUP = "4_COMPONENT_EDITOR_ACTION"; /** * Method to set the action's enablement based on the associated editor diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/FavoritesAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/FavoritesAction.java index 1a87be7c85..a4d4f24c7b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/FavoritesAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/FavoritesAction.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,9 +15,9 @@ */ package ghidra.app.plugin.core.compositeeditor; +import docking.ActionContext; import ghidra.program.model.data.DataType; import ghidra.util.exception.UsrException; -import docking.ActionContext; /** * Action to apply a favorite data type. @@ -27,56 +26,45 @@ import docking.ActionContext; */ public class FavoritesAction extends CompositeEditorTableAction { - private final static String GROUP_NAME = FAVORITES_ACTION_GROUP; - private DataType dataType; + private final static String GROUP_NAME = DATA_ACTION_GROUP; + private DataType dataType; + + /** + * Creates an action for applying a favorite data type. + * @param provider the provider that owns this action + * @param dt the favorite data type + */ + public FavoritesAction(CompositeEditorProvider provider, DataType dt) { + super(provider, dt.getDisplayName(), GROUP_NAME, + new String[] { "Favorite", dt.getDisplayName() }, + new String[] { "Favorite", dt.getDisplayName() }, null); + this.dataType = dt; + getPopupMenuData().setParentMenuGroup(GROUP_NAME); + adjustEnablement(); + } - /** - * Creates an action for applying a favorite data type. - * @param owner the plugin that owns this action - * @param dt the favorite data type - */ - public FavoritesAction(CompositeEditorProvider provider, DataType dt) { - super(provider, dt.getDisplayName(), GROUP_NAME, - new String[] { "Favorite", dt.getDisplayName() }, - new String[] { "Favorite", dt.getDisplayName() }, - null); - this.dataType = dt; - adjustEnablement(); - } - - /** - * Gets the favorite data type for this action. - */ public DataType getDataType() { return dataType; } - - /* (non-Javadoc) - * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) - */ - @Override - public void actionPerformed(ActionContext context) { - try { - model.add(dataType); - } catch (UsrException e1) { + + @Override + public void actionPerformed(ActionContext context) { + try { + model.add(dataType); + } + catch (UsrException e1) { model.setStatus(e1.getMessage()); } requestTableFocus(); - } - - /* (non-Javadoc) - * @see ghidra.app.plugin.compositeeditor.CompositeEditorAction#adjustEnablement() - */ - @Override - public void adjustEnablement() { - // Do nothing since we always want it enabled so the user gets a "doesn't fit" message. - } - - /* (non-Javadoc) - * @see ghidra.app.plugin.compositeeditor.CompositeEditorAction#getHelpName() - */ + } + @Override - public String getHelpName() { + public void adjustEnablement() { + // Do nothing since we always want it enabled so the user gets a "doesn't fit" message. + } + + @Override + public String getHelpName() { return "Favorite"; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/FindReferencesToField.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/FindReferencesToField.java new file mode 100644 index 0000000000..b830d1e819 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/FindReferencesToField.java @@ -0,0 +1,104 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.compositeeditor; + +import javax.swing.SwingUtilities; + +import docking.ActionContext; +import docking.action.MenuData; +import ghidra.app.plugin.core.navigation.FindAppliedDataTypesService; +import ghidra.app.util.HelpTopics; +import ghidra.program.model.data.Composite; +import ghidra.program.model.data.DataTypeComponent; +import ghidra.util.HelpLocation; +import ghidra.util.Msg; + +/** + * An action to show references to the field in the currently selected editor row + */ +public class FindReferencesToField extends CompositeEditorTableAction { + + public final static String ACTION_NAME = "Find Uses of"; + private final static String GROUP_NAME = BASIC_ACTION_GROUP; + private final static String DESCRIPTION = "Find uses of field in the selected row"; + private static String[] popupPath = new String[] { ACTION_NAME }; + + public FindReferencesToField(CompositeEditorProvider provider) { + super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, popupPath, null, null); + setDescription(DESCRIPTION); + adjustEnablement(); + setHelpLocation(new HelpLocation(HelpTopics.FIND_REFERENCES, "Data_Types")); + } + + @Override + public void actionPerformed(ActionContext context) { + + FindAppliedDataTypesService service = tool.getService(FindAppliedDataTypesService.class); + if (service == null) { + Msg.showError(this, null, "Missing Plugin", + "The FindAppliedDataTypesService is not installed.\n" + + "Please add the plugin implementing this service."); + return; + } + + String fieldName = getFieldName(); + Composite composite = model.getOriginalComposite(); + SwingUtilities.invokeLater( + () -> service.findAndDisplayAppliedDataTypeAddresses(composite, fieldName)); + } + + private String getFieldName() { + int[] rows = model.getSelectedComponentRows(); + if (rows.length == 0) { + return null; + } + + int row = rows[0]; + DataTypeComponent dtComponet = model.getComponent(row); + String fieldName = dtComponet.getFieldName(); + return fieldName; + } + + @Override + public void adjustEnablement() { + setEnabled(false); + if (model.getSelectedComponentRows().length != 1) { + return; + } + + Composite composite = model.getOriginalComposite(); + if (composite == null) { + return; // not sure if this can happen + } + + String fieldName = getFieldName(); + if (fieldName == null) { + return; + } + + setEnabled(true); + updateMenuName(fieldName); + } + + private void updateMenuName(String name) { + + String menuName = ACTION_NAME + ' ' + name; + MenuData data = getPopupMenuData().cloneData(); + data.setMenuPath(new String[] { menuName }); + setPopupMenuData(data); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/HexNumbersAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/HexNumbersAction.java index 09a391f35a..10d5223bc0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/HexNumbersAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/HexNumbersAction.java @@ -29,21 +29,11 @@ import docking.menu.DockingCheckboxMenuItemUI; public class HexNumbersAction extends CompositeEditorTableAction implements ToggleDockingActionIf { private final static String ACTION_NAME = "Show Numbers In Hex"; - private final static String GROUP_NAME = BASIC_ACTION_GROUP; + private final static String GROUP_NAME = DATA_ACTION_GROUP; private final static String defaultDescription = "Show Numbers in Hexadecimal"; private static String[] defaultPath = new String[] { defaultDescription }; private boolean isSelected; - /** - * @param name - * @param group - * @param owner - * @param popupPath - * @param menuPath - * @param icon - * @param useToolbar - * @param checkBox - */ public HexNumbersAction(CompositeEditorProvider provider) { super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, defaultPath, defaultPath, null); @@ -52,17 +42,11 @@ public class HexNumbersAction extends CompositeEditorTableAction implements Togg setSelected(model.isShowingNumbersInHex()); } - /* (non-Javadoc) - * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) - */ @Override public void actionPerformed(ActionContext context) { model.displayNumbersInHex(!model.isShowingNumbersInHex()); } - /* (non-Javadoc) - * @see ghidra.app.plugin.core.compositeeditor.CompositeEditorAction#adjustEnablement() - */ @Override public void adjustEnablement() { // Always enabled. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/StructureEditorProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/StructureEditorProvider.java index 8da6fb58a1..a3eafc0c3f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/StructureEditorProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/compositeeditor/StructureEditorProvider.java @@ -64,12 +64,13 @@ public class StructureEditorProvider extends CompositeEditorProvider { new DeleteAction(this), new PointerAction(this), new ArrayAction(this), - new ShowComponentPathAction(this), + new FindReferencesToField(this), new UnpackageAction(this), new EditComponentAction(this), new EditFieldAction(this), new HexNumbersAction(this), new CreateInternalStructureAction(this), + new ShowComponentPathAction(this), new AddBitFieldAction(this), new EditBitFieldAction(this), // new ViewBitFieldAction(this) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/FindReferencesToAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/FindReferencesToAction.java index 909b0b5e0b..9b4cb342b3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/FindReferencesToAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/FindReferencesToAction.java @@ -118,8 +118,9 @@ public class FindReferencesToAction extends ListingContextAction { menuName += itemName; } - setPopupMenuData(new MenuData(new String[] { "References", menuName }, null, - "ShowReferencesTo", MenuData.NO_MNEMONIC, Integer.toString(subGroupPosition))); + setPopupMenuData( + new MenuData(new String[] { LocationReferencesService.MENU_GROUP, menuName }, null, + "ShowReferencesTo", MenuData.NO_MNEMONIC, Integer.toString(subGroupPosition))); } private String getMenuPrefix(LocationDescriptor descriptor) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/FindReferencesToAddressAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/FindReferencesToAddressAction.java index dfc7655991..11001dfb5c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/FindReferencesToAddressAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/FindReferencesToAddressAction.java @@ -15,15 +15,10 @@ */ package ghidra.app.plugin.core.navigation.locationreferences; -import docking.action.KeyBindingType; import docking.action.MenuData; +import ghidra.app.actions.AbstractFindReferencesToAddressAction; import ghidra.app.context.ListingActionContext; -import ghidra.app.context.ListingContextAction; -import ghidra.program.model.address.Address; -import ghidra.program.model.listing.*; -import ghidra.program.util.AddressFieldLocation; -import ghidra.program.util.ProgramLocation; -import ghidra.util.HelpLocation; +import ghidra.app.context.NavigatableActionContext; /** * Only shows addresses to the code unit at the address for the current context. This differs @@ -31,58 +26,22 @@ import ghidra.util.HelpLocation; * context for more information, potentially searching for more than just direct references to * the code unit at the current address. */ -public class FindReferencesToAddressAction extends ListingContextAction { - - private LocationReferencesPlugin plugin; +public class FindReferencesToAddressAction extends AbstractFindReferencesToAddressAction { public FindReferencesToAddressAction(LocationReferencesPlugin plugin, int subGroupPosition) { - super("Show References to Address", plugin.getName(), KeyBindingType.SHARED); + super(plugin.getTool(), plugin.getName()); - this.plugin = plugin; - - setPopupMenuData(new MenuData(new String[] { "References", "Show References to Address" }, + setPopupMenuData(new MenuData(new String[] { LocationReferencesService.MENU_GROUP, NAME }, null, "ShowReferencesTo", MenuData.NO_MNEMONIC, Integer.toString(subGroupPosition))); - - setDescription("Shows references to the current Instruction or Data"); - setHelpLocation(new HelpLocation(plugin.getName(), "Show_Refs_To_Code_Unit")); } @Override - public void actionPerformed(ListingActionContext context) { - - Program program = context.getProgram(); - ProgramLocation location = context.getLocation(); - Address address = location.getAddress(); - Listing listing = program.getListing(); - CodeUnit cu = listing.getCodeUnitContaining(address); - - int[] path = location.getComponentPath(); - if (cu instanceof Data) { - Data outerData = (Data) cu; - Data data = outerData.getComponent(location.getComponentPath()); - address = data.getMinAddress(); - } - - AddressFieldLocation addressLocation = - new AddressFieldLocation(program, address, path, address.toString(), 0); - plugin.showReferencesToLocation(addressLocation, context.getNavigatable()); - } - - @Override - protected boolean isEnabledForContext(ListingActionContext context) { - Program program = context.getProgram(); - ProgramLocation location = context.getLocation(); - Address address = location.getAddress(); - if (address == null) { + public boolean isEnabledForContext(NavigatableActionContext context) { + if (!(context instanceof ListingActionContext)) { + // Restrict this action to the Listing. We have guilty knowledge that there are + // other sibling classes to this one for other contexts. return false; } - - Listing listing = program.getListing(); - CodeUnit cu = listing.getCodeUnitContaining(address); - if (cu == null) { - return false; - } - - return true; + return super.isEnabledForContext(context); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/LocationReferencesService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/LocationReferencesService.java index d319347cee..afee60d8c9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/LocationReferencesService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/navigation/locationreferences/LocationReferencesService.java @@ -16,7 +16,6 @@ package ghidra.app.plugin.core.navigation.locationreferences; import ghidra.app.nav.Navigatable; -import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramLocation; import ghidra.util.HelpLocation; @@ -26,6 +25,8 @@ import ghidra.util.HelpLocation; */ public interface LocationReferencesService { + public static final String MENU_GROUP = "References"; + /** * Returns the help location for help content that describes this service. * @return the help location for help content that describes this service. @@ -37,8 +38,6 @@ public interface LocationReferencesService { * location. * @param location The location for which to show references. * @param navigatable The navigatable in which the references should be shown - * @throws IllegalArgumentException if a call to {@link #canProcessLocation(Program, ProgramLocation)} - * returns false. * @throws NullPointerException if location is null. */ public void showReferencesToLocation(ProgramLocation location, Navigatable navigatable); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/HelpTopics.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/HelpTopics.java index daa5f17c56..efd41356ab 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/HelpTopics.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/HelpTopics.java @@ -86,6 +86,11 @@ public interface HelpTopics { */ public final static String EXPORTER = "ExporterPlugin"; + /** + * Help Topic for references searching + */ + public final static String FIND_REFERENCES = "LocationReferencesPlugin"; + /** * Name of options for the help topic for the front end (Ghidra * Project Window). diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java index aee8eb938b..329cc6d3cb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/AbstractGhidraHeadlessIntegrationTest.java @@ -15,7 +15,7 @@ */ package ghidra.test; -import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.*; import java.io.File; import java.io.IOException; @@ -32,6 +32,7 @@ import ghidra.framework.cmd.Command; import ghidra.framework.model.UndoableDomainObject; import ghidra.framework.plugintool.Plugin; import ghidra.framework.plugintool.PluginTool; +import ghidra.framework.plugintool.mgr.ServiceManager; import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramDB; import ghidra.program.model.address.*; @@ -525,26 +526,33 @@ public abstract class AbstractGhidraHeadlessIntegrationTest extends AbstractDock /** * Replaces the given implementations of the provided service class with the given class. - * + * + * @param tool the tool whose services to update (optional) * @param service the service to override * @param replacement the new version of the service + * @param the service type */ @SuppressWarnings("unchecked") - public static void replaceService(Class service, - Class replacement) { + public static void replaceService(PluginTool tool, Class service, + T replacement) { + + ServiceManager serviceManager = (ServiceManager) getInstanceField("serviceMgr", tool); Set> extentions = (Set>) getInstanceField("extensionPoints", ClassSearcher.class); - HashSet> set = new HashSet<>(extentions); + Set> set = new HashSet<>(extentions); Iterator> iterator = set.iterator(); while (iterator.hasNext()) { Class c = iterator.next(); if (service.isAssignableFrom(c)) { iterator.remove(); + T instance = tool.getService(service); + serviceManager.removeService(service, instance); } } - set.add(replacement); + set.add(replacement.getClass()); + serviceManager.addService(service, replacement); Set> newExtensionPoints = new HashSet<>(set); setInstanceField("extensionPoints", ClassSearcher.class, newExtensionPoints); diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java index 0ac1dd40b7..4272360911 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java @@ -704,7 +704,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field if (token == null) { return null; } - Address address = DecompilerUtils.getClosestAddress(token); + Address address = DecompilerUtils.getClosestAddress(getProgram(), token); if (address == null) { address = DecompilerUtils.findAddressBefore(layoutMgr.getFields(), token); } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerUtils.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerUtils.java index 9a08c05bb1..9d72f86a93 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerUtils.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerUtils.java @@ -216,7 +216,7 @@ public class DecompilerUtils { * Returns the function represented by the given token. This will be either the * decompiled function or a function referenced within the decompiled function. * - * @param program the progam + * @param program the program * @param token the token * @return the function */ @@ -336,7 +336,14 @@ public class DecompilerUtils { return addressSet.intersects(minAddress, maxAddress); } - public static Address getClosestAddress(ClangToken token) { + public static Address getClosestAddress(Program program, ClangToken token) { + + if (token instanceof ClangFuncNameToken) { + // special case: we know that name tokens do not have addresses + Function function = getFunction(program, (ClangFuncNameToken) token); + return function.getEntryPoint(); + } + Address address = token.getMinAddress(); if (address != null) { return address; diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerActionContext.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerActionContext.java index 56c94d7c08..4ffcc89045 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerActionContext.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerActionContext.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. @@ -18,10 +17,14 @@ package ghidra.app.plugin.core.decompile; import ghidra.app.context.NavigatableActionContext; import ghidra.app.context.RestrictedAddressSetContext; +import ghidra.app.decompiler.ClangToken; +import ghidra.app.decompiler.component.DecompilerPanel; +import ghidra.app.decompiler.component.DecompilerUtils; import ghidra.program.model.address.Address; +import ghidra.program.util.ProgramLocation; -public class DecompilerActionContext extends NavigatableActionContext implements - RestrictedAddressSetContext { +public class DecompilerActionContext extends NavigatableActionContext + implements RestrictedAddressSetContext { private final Address functionEntryPoint; private final boolean isDecompiling; @@ -40,4 +43,34 @@ public class DecompilerActionContext extends NavigatableActionContext implements return isDecompiling; } + @Override + public DecompilerProvider getComponentProvider() { + return (DecompilerProvider) super.getComponentProvider(); + } + + public DecompilerPanel getDecompilerPanel() { + return getComponentProvider().getDecompilerPanel(); + } + + @Override + public ProgramLocation getLocation() { + + // prefer the selection over the current location + DecompilerPanel decompilerPanel = getDecompilerPanel(); + ClangToken token = decompilerPanel.getSelectedToken(); + if (token == null) { + token = decompilerPanel.getTokenAtCursor(); + } + + if (token == null) { + return null; + } + + Address address = DecompilerUtils.getClosestAddress(program, token); + if (address == null) { + return null; + } + + return new ProgramLocation(program, address); + } } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java index fd835a3856..1f2950e919 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java @@ -44,7 +44,6 @@ import ghidra.program.model.address.*; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; import ghidra.program.model.symbol.*; -import ghidra.program.model.mem.MemoryBlock; import ghidra.program.util.*; import ghidra.util.HelpLocation; import ghidra.util.Swing; @@ -780,14 +779,35 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter // // Search // + String searchGroup = "comment2 - Search Group"; subGroupPosition = 0; // reset for the next group findAction = new FindAction(tool, controller, owner); setGroupInfo(findAction, searchGroup, subGroupPosition++); + // + // References + // + + // note: set the menu group so that the 'References' group is with the 'Find' action + String referencesParentGroup = searchGroup; + findReferencesAction = new FindReferencesToDataTypeAction(owner, tool, controller); setGroupInfo(findReferencesAction, searchGroup, subGroupPosition++); + findReferencesAction.getPopupMenuData().setParentMenuGroup(referencesParentGroup); + + FindReferencesToSymbolAction findReferencesToSymbolAction = + new FindReferencesToSymbolAction(tool, owner); + setGroupInfo(findReferencesToSymbolAction, searchGroup, subGroupPosition++); + findReferencesToSymbolAction.getPopupMenuData().setParentMenuGroup(referencesParentGroup); + addLocalAction(findReferencesToSymbolAction); + + FindReferencesToAddressAction findReferencesToAdressAction = + new FindReferencesToAddressAction(tool, owner); + setGroupInfo(findReferencesToAdressAction, searchGroup, subGroupPosition++); + findReferencesToAdressAction.getPopupMenuData().setParentMenuGroup(referencesParentGroup); + addLocalAction(findReferencesToAdressAction); // // Options @@ -839,7 +859,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter private void setGroupInfo(DockingAction action, String group, int subGroupPosition) { MenuData popupMenuData = action.getPopupMenuData(); popupMenuData.setMenuGroup(group); - popupMenuData.setMenuSubGroup(Integer.toString(subGroupPosition++)); + popupMenuData.setMenuSubGroup(Integer.toString(subGroupPosition)); } private void graphServiceRemoved() { diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FindReferencesToAddressAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FindReferencesToAddressAction.java new file mode 100644 index 0000000000..0155268223 --- /dev/null +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FindReferencesToAddressAction.java @@ -0,0 +1,44 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.decompile.actions; + +import docking.action.MenuData; +import ghidra.app.actions.AbstractFindReferencesToAddressAction; +import ghidra.app.context.NavigatableActionContext; +import ghidra.app.plugin.core.decompile.DecompilerActionContext; +import ghidra.app.plugin.core.navigation.locationreferences.LocationReferencesService; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.util.ProgramLocation; + +/** + * An action to show all references to the given address + */ +public class FindReferencesToAddressAction extends AbstractFindReferencesToAddressAction { + + public FindReferencesToAddressAction(PluginTool tool, String owner) { + super(tool, owner); + + setPopupMenuData(new MenuData(new String[] { LocationReferencesService.MENU_GROUP, NAME })); + } + + @Override + protected ProgramLocation getLocation(NavigatableActionContext context) { + if (!(context instanceof DecompilerActionContext)) { + return null; + } + return context.getLocation(); + } +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FindReferencesToDataTypeAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FindReferencesToDataTypeAction.java index b6970de1d1..e49dc3ae2b 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FindReferencesToDataTypeAction.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FindReferencesToDataTypeAction.java @@ -21,6 +21,7 @@ import ghidra.app.actions.AbstractFindReferencesDataTypeAction; import ghidra.app.decompiler.*; import ghidra.app.decompiler.component.*; import ghidra.app.plugin.core.decompile.DecompilerActionContext; +import ghidra.app.plugin.core.navigation.locationreferences.LocationReferencesService; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.data.DataType; import ghidra.program.model.pcode.HighVariable; @@ -36,7 +37,8 @@ public class FindReferencesToDataTypeAction extends AbstractFindReferencesDataTy super(tool, NAME, owner, DEFAULT_KEY_STROKE); this.controller = controller; - setPopupMenuData(new MenuData(new String[] { "Find Uses of " })); + setPopupMenuData( + new MenuData(new String[] { LocationReferencesService.MENU_GROUP, "Find Uses of " })); } @Override @@ -136,7 +138,7 @@ public class FindReferencesToDataTypeAction extends AbstractFindReferencesDataTy } MenuData data = getPopupMenuData().cloneData(); - data.setMenuPath(new String[] { menuName }); + data.setMenuPath(new String[] { LocationReferencesService.MENU_GROUP, menuName }); setPopupMenuData(data); } } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FindReferencesToSymbolAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FindReferencesToSymbolAction.java new file mode 100644 index 0000000000..4eaf7aeabb --- /dev/null +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FindReferencesToSymbolAction.java @@ -0,0 +1,131 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.decompile.actions; + +import docking.ActionContext; +import docking.action.DockingAction; +import docking.action.MenuData; +import ghidra.app.plugin.core.decompile.DecompilerActionContext; +import ghidra.app.plugin.core.decompile.DecompilerProvider; +import ghidra.app.plugin.core.navigation.locationreferences.LocationReferencesService; +import ghidra.app.util.HelpTopics; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.Symbol; +import ghidra.program.model.symbol.SymbolTable; +import ghidra.program.util.LabelFieldLocation; +import ghidra.program.util.ProgramLocation; +import ghidra.util.HelpLocation; +import ghidra.util.Msg; + +/** + * An action to show all references to the symbol under the cursor in the Decompiler + */ +public class FindReferencesToSymbolAction extends DockingAction { + + private static final String MENU_ITEM_TEXT = "Find References to"; + public static final String NAME = "Find References to Symbol"; + private PluginTool tool; + + public FindReferencesToSymbolAction(PluginTool tool, String owner) { + super(NAME, owner); + this.tool = tool; + + setPopupMenuData( + new MenuData(new String[] { LocationReferencesService.MENU_GROUP, MENU_ITEM_TEXT })); + setHelpLocation(new HelpLocation(HelpTopics.FIND_REFERENCES, HelpTopics.FIND_REFERENCES)); + } + + @Override + public void actionPerformed(ActionContext context) { + // Note: we intentionally do this check here and not in isEnabledForContext() so + // that global events do not get triggered. + DecompilerActionContext decompilerContext = (DecompilerActionContext) context; + if (decompilerContext.isDecompiling()) { + Msg.showInfo(getClass(), context.getComponentProvider().getComponent(), + "Decompiler Action Blocked", + "You cannot perform Decompiler actions while the Decompiler is busy"); + return; + } + + LocationReferencesService service = tool.getService(LocationReferencesService.class); + if (service == null) { + Msg.showError(this, null, "Missing Plugin", + "The " + LocationReferencesService.class.getSimpleName() + " is not installed.\n" + + "Please add the plugin implementing this service."); + return; + } + + Symbol symbol = getSymbol(decompilerContext); + LabelFieldLocation location = new LabelFieldLocation(symbol); + DecompilerProvider provider = decompilerContext.getComponentProvider(); + service.showReferencesToLocation(location, provider); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + if (!(context instanceof DecompilerActionContext)) { + return false; + } + + DecompilerActionContext decompilerActionContext = (DecompilerActionContext) context; + if (decompilerActionContext.isDecompiling()) { + // Let this through here and handle it in actionPerformed(). This lets us alert + // the user that they have to wait until the decompile is finished. If we are not + // enabled at this point, then the keybinding will be propagated to the global + // actions, which is not what we want. + return true; + } + + Symbol symbol = getSymbol((DecompilerActionContext) context); + if (symbol == null) { + return false; + } + + updateMenuName(symbol); + + return true; + } + + private Symbol getSymbol(DecompilerActionContext context) { + + ProgramLocation location = context.getLocation(); + if (location == null) { + return null; + } + + Address address = location.getAddress(); + Program program = context.getProgram(); + SymbolTable symbolTable = program.getSymbolTable(); + Symbol symbol = symbolTable.getPrimarySymbol(address); + return symbol; + } + + private void updateMenuName(Symbol symbol) { + + if (symbol == null) { + return; // not sure if this can happen + } + + String symbolName = symbol.getName(false); + String menuName = MENU_ITEM_TEXT + ' ' + symbolName; + + MenuData data = getPopupMenuData().cloneData(); + data.setMenuPath(new String[] { LocationReferencesService.MENU_GROUP, menuName }); + setPopupMenuData(data); + } +} diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/AbstractDecompilerFindReferencesActionTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/AbstractDecompilerFindReferencesActionTest.java index 950467569b..40a376dfd7 100644 --- a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/AbstractDecompilerFindReferencesActionTest.java +++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/AbstractDecompilerFindReferencesActionTest.java @@ -26,21 +26,28 @@ import docking.ActionContext; import docking.action.DockingActionIf; import docking.widgets.table.threaded.ThreadedTableModel; import ghidra.app.actions.AbstractFindReferencesDataTypeAction; +import ghidra.app.actions.AbstractFindReferencesToAddressAction; +import ghidra.app.nav.Navigatable; +import ghidra.app.plugin.core.decompile.actions.FindReferencesToSymbolAction; import ghidra.app.plugin.core.navigation.locationreferences.LocationReferencesProvider; +import ghidra.app.plugin.core.navigation.locationreferences.LocationReferencesService; import ghidra.app.services.DataTypeReference; import ghidra.app.services.DataTypeReferenceFinder; import ghidra.program.model.data.Composite; import ghidra.program.model.data.DataType; import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; import ghidra.util.task.TaskMonitor; -import mockit.Mock; -import mockit.MockUp; +import mockit.*; public abstract class AbstractDecompilerFindReferencesActionTest extends AbstractDecompilerTest { protected DockingActionIf findReferencesAction; + protected DockingActionIf findReferencesToSymbolAction; + protected DockingActionIf findReferencesToAddressAction; protected SpyDataTypeReferenceFinder spyReferenceFinder; + protected SpyLocationReferencesService spyLocationReferenceService; @Override @Before @@ -49,6 +56,9 @@ public abstract class AbstractDecompilerFindReferencesActionTest extends Abstrac super.setUp(); findReferencesAction = getAction(decompiler, AbstractFindReferencesDataTypeAction.NAME); + findReferencesToSymbolAction = getAction(decompiler, FindReferencesToSymbolAction.NAME); + findReferencesToAddressAction = + getAction(decompiler, AbstractFindReferencesToAddressAction.NAME); installSpyDataTypeReferenceFinder(); } @@ -56,7 +66,9 @@ public abstract class AbstractDecompilerFindReferencesActionTest extends Abstrac private void installSpyDataTypeReferenceFinder() { spyReferenceFinder = new SpyDataTypeReferenceFinder<>(); - replaceService(DataTypeReferenceFinder.class, StubDataTypeReferenceFinder.class); + replaceService(tool, DataTypeReferenceFinder.class, new StubDataTypeReferenceFinder()); + + spyLocationReferenceService = new SpyLocationReferencesService<>(); } protected void assertFindAllReferencesToCompositeFieldWasCalled() { @@ -69,7 +81,15 @@ public abstract class AbstractDecompilerFindReferencesActionTest extends Abstrac assertEquals(1, spyReferenceFinder.getFindDataTypeReferencesCallCount()); } - protected ThreadedTableModel perfomFindDataTypes() { + protected void assertFindAllReferencesToSymbolWasCalled() { + assertEquals(1, spyLocationReferenceService.getShowReferencesCallCount()); + } + + protected void assertFindAllReferencesToAddressWasCalled() { + assertEquals(1, spyLocationReferenceService.getShowReferencesCallCount()); + } + + protected ThreadedTableModel performFindDataTypes() { // tricky business - the 'finder' is being run in a thread pool, so we must wait for that // model to finish loading @@ -80,11 +100,34 @@ public abstract class AbstractDecompilerFindReferencesActionTest extends Abstrac return model; } + protected ThreadedTableModel performFindReferencesToAddress() { + // tricky business - the 'finder' is being run in a thread pool, so we must wait for that + // model to finish loading + + DecompilerActionContext context = new DecompilerActionContext(provider, addr(0x0), false); + performAction(findReferencesToAddressAction, context, true); + + ThreadedTableModel model = waitForSearchProvider(); + return model; + } + + protected ThreadedTableModel performFindReferencesToSymbol() { + // tricky business - the 'finder' is being run in a thread pool, so we must wait for that + // model to finish loading + + DecompilerActionContext context = new DecompilerActionContext(provider, addr(0x0), false); + performAction(findReferencesToSymbolAction, context, true); + + ThreadedTableModel model = waitForSearchProvider(); + return model; + } + protected ThreadedTableModel waitForSearchProvider() { LocationReferencesProvider searchProvider = (LocationReferencesProvider) tool.getComponentProvider(LocationReferencesProvider.NAME); + assertNotNull("Could not find the Location References Provider", searchProvider); ThreadedTableModel model = getTableModel(searchProvider); waitForTableModel(model); @@ -138,4 +181,21 @@ public abstract class AbstractDecompilerFindReferencesActionTest extends Abstrac return compositeFieldReferencesCallCount.get(); } } + + public class SpyLocationReferencesService + extends MockUp { + + private AtomicInteger showReferencesCallCount = new AtomicInteger(); + + @Mock + public void showReferencesToLocation(Invocation invocation, ProgramLocation location, + Navigatable navigatable) { + showReferencesCallCount.incrementAndGet(); + invocation.proceed(location, navigatable); + } + + public int getShowReferencesCallCount() { + return showReferencesCallCount.get(); + } + } } diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerFindReferencesToActionTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerFindReferencesToActionTest.java index 6c16fb40e5..9c9e905522 100644 --- a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerFindReferencesToActionTest.java +++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerFindReferencesToActionTest.java @@ -122,7 +122,7 @@ public class DecompilerFindReferencesToActionTest int line = 2; int charPosition = 17; setDecompilerLocation(line, charPosition); - perfomFindDataTypes(); + performFindDataTypes(); assertFindAllReferencesToDataTypeWasCalled(); } @@ -153,7 +153,7 @@ public class DecompilerFindReferencesToActionTest int line = 5; int charPosition = 2; setDecompilerLocation(line, charPosition); - perfomFindDataTypes(); + performFindDataTypes(); assertFindAllReferencesToDataTypeWasCalled(); } @@ -184,11 +184,67 @@ public class DecompilerFindReferencesToActionTest int line = 5; int charPosition = 7; setDecompilerLocation(line, charPosition); - perfomFindDataTypes(); + performFindDataTypes(); assertFindAllReferencesToCompositeFieldWasCalled(); } + @Test + public void testFindDataTypeReferences_ToCastSymbol() throws Exception { + + // void lzw_decompress(mytable *table,char *intstream,int len,char *output) + // output = (char *)output_string(output,&local_2c); + + decompile(0x0804873f); // lzw_decompress() + + int line = 18; + int charPosition = 11; + setDecompilerLocation(line, charPosition); + performFindDataTypes(); + + assertFindAllReferencesToDataTypeWasCalled(); + } + + @Test + public void testFindDataTypeReferences_ToCurrentAddress() throws Exception { + + /* + + Decomp of 'init_string': + + 1| + 2| void init_string(mystring *ptr) + 3| + 4| { + 5| ptr->alloc = 0; + 6| return; + 7| } + 8| + */ + + decompile(INIT_STRING_ADDR); + + int line = 5; + int charPosition = 7; + setDecompilerLocation(line, charPosition); + performFindReferencesToAddress(); + + assertFindAllReferencesToAddressWasCalled(); + } + + @Test + public void testFindDataTypeReferences_ToCurrentFunction() throws Exception { + + decompile(INIT_STRING_ADDR); + + int line = 2; + int charPosition = 10; // function name + setDecompilerLocation(line, charPosition); + performFindReferencesToSymbol(); + + assertFindAllReferencesToSymbolWasCalled(); + } + //================================================================================================== // Private Methods //================================================================================================== diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerFindReferencesToNestedStructureActionTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerFindReferencesToNestedStructureActionTest.java index f4a074d5d5..a0b6af182d 100644 --- a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerFindReferencesToNestedStructureActionTest.java +++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerFindReferencesToNestedStructureActionTest.java @@ -120,7 +120,7 @@ public class DecompilerFindReferencesToNestedStructureActionTest int line = 9; int charPosition = 44; setDecompilerLocation(line, charPosition); - perfomFindDataTypes(); + performFindDataTypes(); assertFindAllReferencesToCompositeFieldWasCalled(); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/DockingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/action/DockingAction.java index 71979c79b2..ffbbd2d299 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/DockingAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/DockingAction.java @@ -395,6 +395,13 @@ public abstract class DockingAction implements DockingActionIf { buffer.append('\n'); buffer.append(" MENU GROUP: ").append(menuBarData.getMenuGroup()); buffer.append('\n'); + + String parentGroup = popupMenuData.getParentMenuGroup(); + if (parentGroup != null) { + buffer.append(" PARENT GROUP: ").append(parentGroup); + buffer.append('\n'); + } + Icon icon = menuBarData.getMenuIcon(); if (icon != null && icon instanceof ImageIconWrapper) { ImageIconWrapper wrapper = (ImageIconWrapper) icon; @@ -412,6 +419,12 @@ public abstract class DockingAction implements DockingActionIf { buffer.append(" POPUP GROUP: ").append(popupMenuData.getMenuGroup()); buffer.append('\n'); + String parentGroup = popupMenuData.getParentMenuGroup(); + if (parentGroup != null) { + buffer.append(" PARENT GROUP: ").append(parentGroup); + buffer.append('\n'); + } + String menuSubGroup = popupMenuData.getMenuSubGroup(); if (menuSubGroup != MenuData.NO_SUBGROUP) { buffer.append(" POPUP SUB-GROUP: ").append(menuSubGroup); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/action/MenuData.java b/Ghidra/Framework/Docking/src/main/java/docking/action/MenuData.java index ed80b7ad0b..3f46e3d514 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/action/MenuData.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/action/MenuData.java @@ -31,6 +31,7 @@ public class MenuData { private Icon icon; private int mnemonic = NO_MNEMONIC; private String menuGroup; + private String parentMenuGroup; /** * The subgroup string. This string is used to sort items within a @@ -73,11 +74,14 @@ public class MenuData { this.icon = menuData.icon; this.menuGroup = menuData.menuGroup; this.menuSubGroup = menuData.menuSubGroup; + this.parentMenuGroup = menuData.parentMenuGroup; this.mnemonic = menuData.mnemonic; } public MenuData cloneData() { - return new MenuData(menuPath, icon, menuGroup, mnemonic, menuSubGroup); + MenuData newData = new MenuData(menuPath, icon, menuGroup, mnemonic, menuSubGroup); + newData.parentMenuGroup = parentMenuGroup; + return newData; } protected void firePropertyChanged(MenuData oldData) { @@ -113,11 +117,20 @@ public class MenuData { /** * Returns the icon assigned to this action's menu. Null indicates that this action does not * have a menu icon + * @return the icon */ public Icon getMenuIcon() { return icon; } + /** + * Returns the group for the menu item created by this data. This value determines which + * section inside of the tool's popup menu the menu item will be placed. If you need to + * control the ordering within a section, then provide a value for + * {@link #setMenuSubGroup(String)}. + * + * @return the group + */ public String getMenuGroup() { return menuGroup; } @@ -126,11 +139,24 @@ public class MenuData { * Returns the subgroup string. This string is used to sort items within a * {@link #getMenuGroup() toolbar group}. This value is not required. If not specified, * then the value will effectively place this item at the end of its specified group. + * @return the sub-group */ public String getMenuSubGroup() { return menuSubGroup; } + /** + * Returns the group for the parent menu of the menu item created by this data. That is, + * this value is effectively the same as {@link #getMenuGroup()}, but for the parent menu + * item of this data's item. Setting this value is only valid if the {@link #getMenuPath()} + * has a length greater than 1. + * + * @return the parent group + */ + public String getParentMenuGroup() { + return parentMenuGroup; + } + public void setIcon(Icon newIcon) { if (icon == newIcon) { return; @@ -154,11 +180,38 @@ public class MenuData { if (SystemUtilities.isEqual(menuSubGroup, newSubGroup)) { return; } + + if (newSubGroup == null) { + newSubGroup = NO_SUBGROUP; + } + MenuData oldData = cloneData(); menuSubGroup = newSubGroup; firePropertyChanged(oldData); } + /** + * See the description in {@link #getParentMenuGroup()} + * + * @param newParentMenuGroup the parent group + */ + public void setParentMenuGroup(String newParentMenuGroup) { + if (menuPath.length <= 1) { + throw new IllegalStateException( + "Cannot set the parent menu group for a menu item " + "that has no parent"); + } + + if (SystemUtilities.isEqual(parentMenuGroup, newParentMenuGroup)) { + return; + } + + MenuData oldData = cloneData(); + parentMenuGroup = newParentMenuGroup; + firePropertyChanged(oldData); + + this.parentMenuGroup = newParentMenuGroup; + } + public void setMenuPath(String[] newPath) { if (newPath == null || newPath.length == 0) { throw new IllegalArgumentException("Menu path cannot be null or empty"); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuManager.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuManager.java index e99d55a0d1..8bab73843e 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuManager.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/MenuManager.java @@ -102,28 +102,8 @@ public class MenuManager implements ManagedMenuItem { checkForSwingThread(); resetMenus(); MenuData menuData = usePopupPath ? action.getPopupMenuData() : action.getMenuBarData(); - String[] actionMenuPath = menuData.getMenuPath(); - if (actionMenuPath.length > level + 1) { - String subMenuName = actionMenuPath[level]; - String cleanSubMenuName = stripMnemonicAmp(subMenuName); - MenuManager mgr = subMenus.get(cleanSubMenuName); - - if (mgr == null) { - char mnemonic = getMnemonicKey(subMenuName); - - int submenuLevel = level + 1; - String[] submenuPath = new String[submenuLevel]; - System.arraycopy(actionMenuPath, 0, submenuPath, 0, submenuLevel); - String submenuGroup = menuGroupMap.getMenuGroup(submenuPath); - if (submenuGroup == null) { - submenuGroup = subMenuName; - } - - mgr = new MenuManager(cleanSubMenuName, submenuPath, mnemonic, submenuLevel, - submenuGroup, usePopupPath, menuHandler, menuGroupMap); - subMenus.put(cleanSubMenuName, mgr); - managedMenuItems.add(mgr); - } + if (isSubMenu(menuData)) { + MenuManager mgr = getSubMenu(menuData); mgr.addAction(action); } else { @@ -131,6 +111,70 @@ public class MenuManager implements ManagedMenuItem { } } + private boolean isSubMenu(MenuData menuData) { + String[] actionMenuPath = menuData.getMenuPath(); + return actionMenuPath.length > level + 1; + } + + private MenuManager getSubMenu(MenuData menuData) { + + String[] fullPath = menuData.getMenuPath(); + String displayName = fullPath[level]; + char mnemonic = getMnemonicKey(displayName); + String realName = stripMnemonicAmp(displayName); + MenuManager subMenu = subMenus.get(realName); + if (subMenu != null) { + return subMenu; + } + + int subMenuLevel = level + 1; + String[] subMenuPath = new String[subMenuLevel]; + System.arraycopy(fullPath, 0, subMenuPath, 0, subMenuLevel); + + String subMenuGroup = getSubMenuGroup(menuData, realName, subMenuPath); + subMenu = new MenuManager(realName, subMenuPath, mnemonic, subMenuLevel, subMenuGroup, + usePopupPath, menuHandler, menuGroupMap); + subMenus.put(realName, subMenu); + managedMenuItems.add(subMenu); + + return subMenu; + } + + private String getSubMenuGroup(MenuData menuData, String menuName, String[] subMenuPath) { + + // prefer the group defined in the menu data, if any + String pullRightGroup = getPullRightMenuGroup(menuData); + if (pullRightGroup != null) { + return pullRightGroup; + } + + // check the global registry + pullRightGroup = menuGroupMap.getMenuGroup(subMenuPath); + if (pullRightGroup != null) { + return pullRightGroup; + } + + // default to the menu name + return menuName; + } + + private String getPullRightMenuGroup(MenuData menuData) { + + // note: currently, the client can specify the group for the pull-right menu only for + // the immediate parent of the menu item. We can change this later if we find + // we have a need for a multi-level cascaded menu that needs to specify groups for + // each pull-right in the menu path + + String[] actionMenuPath = menuData.getMenuPath(); + int leafLevel = actionMenuPath.length - 1; + boolean isParentOfLeaf = level == (leafLevel - 1); + if (!isParentOfLeaf) { + return null; + } + + return menuData.getParentMenuGroup(); + } + public DockingActionIf getAction(String actionName) { for (ManagedMenuItem item : managedMenuItems) { if (item instanceof MenuItemManager) { @@ -158,17 +202,18 @@ public class MenuManager implements ManagedMenuItem { } /*** - * Removes the Mnemonic indicator character (&) from the text. - * @param str the text to strip. + * Removes the Mnemonic indicator character (&) from the text + * @param text the text to strip + * @return the stripped mnemonic */ - public static String stripMnemonicAmp(String str) { - int ampLoc = str.indexOf('&'); + public static String stripMnemonicAmp(String text) { + int ampLoc = text.indexOf('&'); if (ampLoc < 0) { - return str; + return text; } - String s = str.substring(0, ampLoc); - if (ampLoc < (str.length() - 1)) { - s += str.substring(++ampLoc); + String s = text.substring(0, ampLoc); + if (ampLoc < (text.length() - 1)) { + s += text.substring(++ampLoc); } return s; } @@ -182,7 +227,8 @@ public class MenuManager implements ManagedMenuItem { } /** - * Returns a Menu hierarchy of all the actions. + * Returns a Menu hierarchy of all the actions + * @return the menu */ public JMenu getMenu() { if (menu == null) { @@ -236,9 +282,6 @@ public class MenuManager implements ManagedMenuItem { return menuSubGroup; } - /** - * @see docking.menu.ManagedMenuItem#dispose() - */ @Override public void dispose() { for (ManagedMenuItem item : managedMenuItems) { @@ -249,7 +292,8 @@ public class MenuManager implements ManagedMenuItem { } /** - * Returns a JPopupMenu for the action hierarchy. + * Returns a JPopupMenu for the action hierarchy + * @return the popup menu */ public JPopupMenu getPopupMenu() { if (popupMenu == null) { diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/mgr/ServiceManager.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/mgr/ServiceManager.java index f7edd6af34..aa222fcabb 100644 --- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/mgr/ServiceManager.java +++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/plugintool/mgr/ServiceManager.java @@ -101,7 +101,7 @@ public class ServiceManager { * * @see #setServiceAddedNotificationsOn(boolean) */ - public synchronized void addService(Class interfaceClass, T service) { + public synchronized void addService(Class interfaceClass, T service) { List list = servicesByInterface.computeIfAbsent(interfaceClass, (k) -> new ArrayList<>()); if (list.contains(service)) {