Merge remote-tracking branch 'origin/GT-3115-dragonmacer-decompiler-find-actions'

This commit is contained in:
Ryan Kurtz
2019-08-28 16:37:57 -04:00
30 changed files with 833 additions and 223 deletions
@@ -25,6 +25,7 @@ import docking.DockingUtils;
import docking.action.*; import docking.action.*;
import ghidra.app.plugin.core.navigation.FindAppliedDataTypesService; import ghidra.app.plugin.core.navigation.FindAppliedDataTypesService;
import ghidra.app.plugin.core.navigation.locationreferences.ReferenceUtils; import ghidra.app.plugin.core.navigation.locationreferences.ReferenceUtils;
import ghidra.app.util.HelpTopics;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.data.Composite; import ghidra.program.model.data.Composite;
import ghidra.program.model.data.DataType; import ghidra.program.model.data.DataType;
@@ -32,6 +33,7 @@ import ghidra.util.*;
public abstract class AbstractFindReferencesDataTypeAction extends DockingAction { 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 String NAME = "Find References To";
public static final KeyStroke DEFAULT_KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_F, public static final KeyStroke DEFAULT_KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_F,
DockingUtils.CONTROL_KEY_MODIFIER_MASK | InputEvent.SHIFT_DOWN_MASK); DockingUtils.CONTROL_KEY_MODIFIER_MASK | InputEvent.SHIFT_DOWN_MASK);
@@ -46,7 +48,7 @@ public abstract class AbstractFindReferencesDataTypeAction extends DockingAction
super(name, owner, KeyBindingType.SHARED); super(name, owner, KeyBindingType.SHARED);
this.tool = tool; 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"); setDescription("Shows all uses of the selected data type");
initKeyStroke(defaultKeyStroke); initKeyStroke(defaultKeyStroke);
@@ -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();
}
}
@@ -19,6 +19,7 @@ import java.util.Set;
import docking.ActionContext; import docking.ActionContext;
import docking.action.DockingAction; import docking.action.DockingAction;
import docking.action.KeyBindingType;
public abstract class NavigatableContextAction extends DockingAction { public abstract class NavigatableContextAction extends DockingAction {
@@ -26,6 +27,10 @@ public abstract class NavigatableContextAction extends DockingAction {
super(name, owner); super(name, owner);
} }
public NavigatableContextAction(String name, String owner, KeyBindingType type) {
super(name, owner, type);
}
@Override @Override
public boolean isEnabledForContext(ActionContext context) { public boolean isEnabledForContext(ActionContext context) {
if (!(context instanceof NavigatableActionContext)) { if (!(context instanceof NavigatableActionContext)) {
@@ -15,15 +15,14 @@
*/ */
package ghidra.app.plugin.core.compositeeditor; 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 java.util.*;
import javax.swing.KeyStroke; import javax.swing.KeyStroke;
import docking.action.KeyBindingData; 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. * A CompositeEditorActionManager manages the actions for a single composite editor.
@@ -32,7 +31,8 @@ import docking.action.KeyBindingData;
*/ */
public class CompositeEditorActionManager { public class CompositeEditorActionManager {
private CompositeEditorProvider provider; private CompositeEditorProvider provider;
private ArrayList<CompositeEditorTableAction> editorActions = new ArrayList<CompositeEditorTableAction>(); private ArrayList<CompositeEditorTableAction> editorActions =
new ArrayList<CompositeEditorTableAction>();
private ArrayList<CompositeEditorTableAction> favoritesActions = private ArrayList<CompositeEditorTableAction> favoritesActions =
new ArrayList<CompositeEditorTableAction>(); new ArrayList<CompositeEditorTableAction>();
private ArrayList<CycleGroupAction> cycleGroupActions = new ArrayList<CycleGroupAction>(); private ArrayList<CycleGroupAction> cycleGroupActions = new ArrayList<CycleGroupAction>();
@@ -46,9 +46,7 @@ public class CompositeEditorActionManager {
* Constructor * Constructor
* <BR> NOTE: After constructing a manager, you must call setEditorModel() * <BR> NOTE: After constructing a manager, you must call setEditorModel()
* and setParentComponent() for the actions to work. * and setParentComponent() for the actions to work.
* @param plugin the plugin that owns this composite editor action manager * @param provider the provider 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
* favorites and cycle groups. * favorites and cycle groups.
*/ */
public CompositeEditorActionManager(CompositeEditorProvider provider) { public CompositeEditorActionManager(CompositeEditorProvider provider) {
@@ -56,7 +54,8 @@ public class CompositeEditorActionManager {
this.dataTypeMgrService = provider.dtmService; this.dataTypeMgrService = provider.dtmService;
adapter = new DataTypeManagerChangeListenerAdapter() { adapter = new DataTypeManagerChangeListenerAdapter() {
@Override @Override
public void favoritesChanged(DataTypeManager dtm, DataTypePath path, boolean isFavorite) { public void favoritesChanged(DataTypeManager dtm, DataTypePath path,
boolean isFavorite) {
setFavoritesActions(dataTypeMgrService.getFavorites()); setFavoritesActions(dataTypeMgrService.getFavorites());
} }
}; };
@@ -192,8 +191,8 @@ public class CompositeEditorActionManager {
*/ */
public void setEditorActions(CompositeEditorTableAction[] actions) { public void setEditorActions(CompositeEditorTableAction[] actions) {
editorActions.clear(); editorActions.clear();
for (int i = 0; i < actions.length; i++) { for (CompositeEditorTableAction action : actions) {
editorActions.add(actions[i]); editorActions.add(action);
} }
} }
@@ -226,20 +225,24 @@ public class CompositeEditorActionManager {
} }
private void notifyActionsAdded(ArrayList<? extends CompositeEditorTableAction> actions) { private void notifyActionsAdded(ArrayList<? extends CompositeEditorTableAction> actions) {
if (actions.size() <= 0) if (actions.size() <= 0) {
return; return;
}
int length = listeners.size(); 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++) { for (int i = 0; i < length; i++) {
listeners.get(i).actionsAdded(cea); listeners.get(i).actionsAdded(cea);
} }
} }
private void notifyActionsRemoved(ArrayList<? extends CompositeEditorTableAction> actions) { private void notifyActionsRemoved(ArrayList<? extends CompositeEditorTableAction> actions) {
if (actions.size() <= 0) if (actions.size() <= 0) {
return; return;
}
int length = listeners.size(); 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++) { for (int i = 0; i < length; i++) {
listeners.get(i).actionsRemoved(cea); listeners.get(i).actionsRemoved(cea);
} }
@@ -252,14 +255,14 @@ public class CompositeEditorActionManager {
// Update the editor actions here. // Update the editor actions here.
// The favorites and cycle groups get handled by stateChanged() and cyclegroupChanged(). // The favorites and cycle groups get handled by stateChanged() and cyclegroupChanged().
CompositeEditorTableAction[] actions = getEditorActions(); CompositeEditorTableAction[] actions = getEditorActions();
for (int i = 0; i < actions.length; i++) { for (CompositeEditorTableAction action : actions) {
String actionName = actions[i].getFullName(); String actionName = action.getFullName();
if (actionName.equals(name)) { if (actionName.equals(name)) {
KeyStroke actionKs = actions[i].getKeyBinding(); KeyStroke actionKs = action.getKeyBinding();
KeyStroke oldKs = (KeyStroke) oldValue; KeyStroke oldKs = (KeyStroke) oldValue;
KeyStroke newKs = (KeyStroke) newValue; KeyStroke newKs = (KeyStroke) newValue;
if (actionKs == oldKs) { if (actionKs == oldKs) {
actions[i].setUnvalidatedKeyBindingData(new KeyBindingData(newKs)); action.setUnvalidatedKeyBindingData(new KeyBindingData(newKs));
} }
break; break;
} }
@@ -27,7 +27,7 @@ import ghidra.program.model.data.CycleGroup;
*/ */
public class CycleGroupAction extends CompositeEditorTableAction { 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; private CycleGroup cycleGroup;
public CycleGroupAction(CompositeEditorProvider provider, 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() },
new String[] { "Cycle", cycleGroup.getName() }, null, KeyBindingType.SHARED); new String[] { "Cycle", cycleGroup.getName() }, null, KeyBindingType.SHARED);
this.cycleGroup = cycleGroup; this.cycleGroup = cycleGroup;
getPopupMenuData().setParentMenuGroup(GROUP_NAME);
initKeyStroke(cycleGroup.getDefaultKeyStroke()); initKeyStroke(cycleGroup.getDefaultKeyStroke());
} }
@@ -15,12 +15,11 @@
*/ */
package ghidra.app.plugin.core.compositeeditor; package ghidra.app.plugin.core.compositeeditor;
import docking.ActionContext;
import ghidra.app.services.DataTypeManagerService; import ghidra.app.services.DataTypeManagerService;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.program.model.data.Enum; import ghidra.program.model.data.Enum;
import docking.ActionContext;
/** /**
* Action for use in the composite data type editor. * Action for use in the composite data type editor.
* This action has help associated with it. * 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 static String[] menuPath = new String[] { ACTION_NAME + "..." };
private DataTypeManagerService dtmService; private DataTypeManagerService dtmService;
/**
* @param id
* @param group
* @param owner
* @param popupPath
* @param menuPath
* @param icon
* @param useToolbar
* @param checkBox
*/
public EditComponentAction(CompositeEditorProvider provider) { public EditComponentAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, popupPath, menuPath, null); super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, popupPath, menuPath, null);
this.dtmService = provider.dtmService; this.dtmService = provider.dtmService;
@@ -51,9 +40,6 @@ public class EditComponentAction extends CompositeEditorTableAction {
adjustEnablement(); adjustEnablement();
} }
/* (non-Javadoc)
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
int row = model.getRow(); int row = model.getRow();
@@ -80,9 +66,6 @@ public class EditComponentAction extends CompositeEditorTableAction {
requestTableFocus(); requestTableFocus();
} }
/* (non-Javadoc)
* @see ghidra.app.plugin.datamanager.editor.CompositeEditorAction#adjustEnablement()
*/
@Override @Override
public void adjustEnablement() { public void adjustEnablement() {
setEnabled(model.isEditComponentAllowed()); setEnabled(model.isEditComponentAllowed());
@@ -18,10 +18,9 @@ package ghidra.app.plugin.core.compositeeditor;
public interface EditorAction extends CompositeEditorModelListener { public interface EditorAction extends CompositeEditorModelListener {
static final String BASIC_ACTION_GROUP = "1_BASIC_EDITOR_ACTION"; static final String BASIC_ACTION_GROUP = "1_BASIC_EDITOR_ACTION";
static final String FAVORITES_ACTION_GROUP = "2_FAVORITE_DT_EDITOR_ACTION"; static final String DATA_ACTION_GROUP = "2_DATA_EDITOR_ACTION";
static final String CYCLE_ACTION_GROUP = "3_CYCLE_DT_EDITOR_ACTION"; static final String COMPONENT_ACTION_GROUP = "3_COMPONENT_EDITOR_ACTION";
static final String COMPONENT_ACTION_GROUP = "4_COMPONENT_EDITOR_ACTION"; static final String BITFIELD_ACTION_GROUP = "4_COMPONENT_EDITOR_ACTION";
static final String BITFIELD_ACTION_GROUP = "5_COMPONENT_EDITOR_ACTION";
/** /**
* Method to set the action's enablement based on the associated editor * Method to set the action's enablement based on the associated editor
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -16,9 +15,9 @@
*/ */
package ghidra.app.plugin.core.compositeeditor; package ghidra.app.plugin.core.compositeeditor;
import docking.ActionContext;
import ghidra.program.model.data.DataType; import ghidra.program.model.data.DataType;
import ghidra.util.exception.UsrException; import ghidra.util.exception.UsrException;
import docking.ActionContext;
/** /**
* Action to apply a favorite data type. * Action to apply a favorite data type.
@@ -27,56 +26,45 @@ import docking.ActionContext;
*/ */
public class FavoritesAction extends CompositeEditorTableAction { public class FavoritesAction extends CompositeEditorTableAction {
private final static String GROUP_NAME = FAVORITES_ACTION_GROUP; private final static String GROUP_NAME = DATA_ACTION_GROUP;
private DataType dataType; 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() { public DataType getDataType() {
return dataType; return dataType;
} }
/* (non-Javadoc) @Override
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) public void actionPerformed(ActionContext context) {
*/ try {
@Override model.add(dataType);
public void actionPerformed(ActionContext context) { }
try { catch (UsrException e1) {
model.add(dataType);
} catch (UsrException e1) {
model.setStatus(e1.getMessage()); model.setStatus(e1.getMessage());
} }
requestTableFocus(); 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 @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"; return "Favorite";
} }
@@ -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);
}
}
@@ -29,21 +29,11 @@ import docking.menu.DockingCheckboxMenuItemUI;
public class HexNumbersAction extends CompositeEditorTableAction implements ToggleDockingActionIf { public class HexNumbersAction extends CompositeEditorTableAction implements ToggleDockingActionIf {
private final static String ACTION_NAME = "Show Numbers In Hex"; 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 final static String defaultDescription = "Show Numbers in Hexadecimal";
private static String[] defaultPath = new String[] { defaultDescription }; private static String[] defaultPath = new String[] { defaultDescription };
private boolean isSelected; private boolean isSelected;
/**
* @param name
* @param group
* @param owner
* @param popupPath
* @param menuPath
* @param icon
* @param useToolbar
* @param checkBox
*/
public HexNumbersAction(CompositeEditorProvider provider) { public HexNumbersAction(CompositeEditorProvider provider) {
super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, defaultPath, defaultPath, super(provider, EDIT_ACTION_PREFIX + ACTION_NAME, GROUP_NAME, defaultPath, defaultPath,
null); null);
@@ -52,17 +42,11 @@ public class HexNumbersAction extends CompositeEditorTableAction implements Togg
setSelected(model.isShowingNumbersInHex()); setSelected(model.isShowingNumbersInHex());
} }
/* (non-Javadoc)
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override @Override
public void actionPerformed(ActionContext context) { public void actionPerformed(ActionContext context) {
model.displayNumbersInHex(!model.isShowingNumbersInHex()); model.displayNumbersInHex(!model.isShowingNumbersInHex());
} }
/* (non-Javadoc)
* @see ghidra.app.plugin.core.compositeeditor.CompositeEditorAction#adjustEnablement()
*/
@Override @Override
public void adjustEnablement() { public void adjustEnablement() {
// Always enabled. // Always enabled.
@@ -64,12 +64,13 @@ public class StructureEditorProvider extends CompositeEditorProvider {
new DeleteAction(this), new DeleteAction(this),
new PointerAction(this), new PointerAction(this),
new ArrayAction(this), new ArrayAction(this),
new ShowComponentPathAction(this), new FindReferencesToField(this),
new UnpackageAction(this), new UnpackageAction(this),
new EditComponentAction(this), new EditComponentAction(this),
new EditFieldAction(this), new EditFieldAction(this),
new HexNumbersAction(this), new HexNumbersAction(this),
new CreateInternalStructureAction(this), new CreateInternalStructureAction(this),
new ShowComponentPathAction(this),
new AddBitFieldAction(this), new AddBitFieldAction(this),
new EditBitFieldAction(this), new EditBitFieldAction(this),
// new ViewBitFieldAction(this) // new ViewBitFieldAction(this)
@@ -118,8 +118,9 @@ public class FindReferencesToAction extends ListingContextAction {
menuName += itemName; menuName += itemName;
} }
setPopupMenuData(new MenuData(new String[] { "References", menuName }, null, setPopupMenuData(
"ShowReferencesTo", MenuData.NO_MNEMONIC, Integer.toString(subGroupPosition))); new MenuData(new String[] { LocationReferencesService.MENU_GROUP, menuName }, null,
"ShowReferencesTo", MenuData.NO_MNEMONIC, Integer.toString(subGroupPosition)));
} }
private String getMenuPrefix(LocationDescriptor descriptor) { private String getMenuPrefix(LocationDescriptor descriptor) {
@@ -15,15 +15,10 @@
*/ */
package ghidra.app.plugin.core.navigation.locationreferences; package ghidra.app.plugin.core.navigation.locationreferences;
import docking.action.KeyBindingType;
import docking.action.MenuData; import docking.action.MenuData;
import ghidra.app.actions.AbstractFindReferencesToAddressAction;
import ghidra.app.context.ListingActionContext; import ghidra.app.context.ListingActionContext;
import ghidra.app.context.ListingContextAction; import ghidra.app.context.NavigatableActionContext;
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;
/** /**
* Only shows addresses to the code unit at the address for the current context. This differs * 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 * context for more information, potentially searching for more than just direct references to
* the code unit at the current address. * the code unit at the current address.
*/ */
public class FindReferencesToAddressAction extends ListingContextAction { public class FindReferencesToAddressAction extends AbstractFindReferencesToAddressAction {
private LocationReferencesPlugin plugin;
public FindReferencesToAddressAction(LocationReferencesPlugin plugin, int subGroupPosition) { 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[] { LocationReferencesService.MENU_GROUP, NAME },
setPopupMenuData(new MenuData(new String[] { "References", "Show References to Address" },
null, "ShowReferencesTo", MenuData.NO_MNEMONIC, Integer.toString(subGroupPosition))); 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 @Override
public void actionPerformed(ListingActionContext context) { public boolean isEnabledForContext(NavigatableActionContext context) {
if (!(context instanceof ListingActionContext)) {
Program program = context.getProgram(); // Restrict this action to the Listing. We have guilty knowledge that there are
ProgramLocation location = context.getLocation(); // other sibling classes to this one for other contexts.
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) {
return false; return false;
} }
return super.isEnabledForContext(context);
Listing listing = program.getListing();
CodeUnit cu = listing.getCodeUnitContaining(address);
if (cu == null) {
return false;
}
return true;
} }
} }
@@ -16,7 +16,6 @@
package ghidra.app.plugin.core.navigation.locationreferences; package ghidra.app.plugin.core.navigation.locationreferences;
import ghidra.app.nav.Navigatable; import ghidra.app.nav.Navigatable;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
@@ -26,6 +25,8 @@ import ghidra.util.HelpLocation;
*/ */
public interface LocationReferencesService { public interface LocationReferencesService {
public static final String MENU_GROUP = "References";
/** /**
* Returns the help location for help content that describes this service. * Returns the help location for help content that describes this service.
* @return 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. * location.
* @param location The location for which to show references. * @param location The location for which to show references.
* @param navigatable The navigatable in which the references should be shown * @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 <tt>location</tt> is null. * @throws NullPointerException if <tt>location</tt> is null.
*/ */
public void showReferencesToLocation(ProgramLocation location, Navigatable navigatable); public void showReferencesToLocation(ProgramLocation location, Navigatable navigatable);
@@ -86,6 +86,11 @@ public interface HelpTopics {
*/ */
public final static String EXPORTER = "ExporterPlugin"; 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 * Name of options for the help topic for the front end (Ghidra
* Project Window). * Project Window).
@@ -15,7 +15,7 @@
*/ */
package ghidra.test; package ghidra.test;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.*;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -32,6 +32,7 @@ import ghidra.framework.cmd.Command;
import ghidra.framework.model.UndoableDomainObject; import ghidra.framework.model.UndoableDomainObject;
import ghidra.framework.plugintool.Plugin; import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.mgr.ServiceManager;
import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB; import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.*; 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. * 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 service the service to override
* @param replacement the new version of the service * @param replacement the new version of the service
* @param <T> the service type
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T> void replaceService(Class<? extends T> service, public static <T> void replaceService(PluginTool tool, Class<? extends T> service,
Class<? extends T> replacement) { T replacement) {
ServiceManager serviceManager = (ServiceManager) getInstanceField("serviceMgr", tool);
Set<Class<?>> extentions = Set<Class<?>> extentions =
(Set<Class<?>>) getInstanceField("extensionPoints", ClassSearcher.class); (Set<Class<?>>) getInstanceField("extensionPoints", ClassSearcher.class);
HashSet<Class<?>> set = new HashSet<>(extentions); Set<Class<?>> set = new HashSet<>(extentions);
Iterator<Class<?>> iterator = set.iterator(); Iterator<Class<?>> iterator = set.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
Class<?> c = iterator.next(); Class<?> c = iterator.next();
if (service.isAssignableFrom(c)) { if (service.isAssignableFrom(c)) {
iterator.remove(); iterator.remove();
T instance = tool.getService(service);
serviceManager.removeService(service, instance);
} }
} }
set.add(replacement); set.add(replacement.getClass());
serviceManager.addService(service, replacement);
Set<Class<?>> newExtensionPoints = new HashSet<>(set); Set<Class<?>> newExtensionPoints = new HashSet<>(set);
setInstanceField("extensionPoints", ClassSearcher.class, newExtensionPoints); setInstanceField("extensionPoints", ClassSearcher.class, newExtensionPoints);
@@ -704,7 +704,7 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field
if (token == null) { if (token == null) {
return null; return null;
} }
Address address = DecompilerUtils.getClosestAddress(token); Address address = DecompilerUtils.getClosestAddress(getProgram(), token);
if (address == null) { if (address == null) {
address = DecompilerUtils.findAddressBefore(layoutMgr.getFields(), token); address = DecompilerUtils.findAddressBefore(layoutMgr.getFields(), token);
} }
@@ -216,7 +216,7 @@ public class DecompilerUtils {
* Returns the function represented by the given token. This will be either the * Returns the function represented by the given token. This will be either the
* decompiled function or a function referenced within the decompiled function. * decompiled function or a function referenced within the decompiled function.
* *
* @param program the progam * @param program the program
* @param token the token * @param token the token
* @return the function * @return the function
*/ */
@@ -336,7 +336,14 @@ public class DecompilerUtils {
return addressSet.intersects(minAddress, maxAddress); 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(); Address address = token.getMinAddress();
if (address != null) { if (address != null) {
return address; return address;
@@ -1,6 +1,5 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -18,10 +17,14 @@ package ghidra.app.plugin.core.decompile;
import ghidra.app.context.NavigatableActionContext; import ghidra.app.context.NavigatableActionContext;
import ghidra.app.context.RestrictedAddressSetContext; 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.model.address.Address;
import ghidra.program.util.ProgramLocation;
public class DecompilerActionContext extends NavigatableActionContext implements public class DecompilerActionContext extends NavigatableActionContext
RestrictedAddressSetContext { implements RestrictedAddressSetContext {
private final Address functionEntryPoint; private final Address functionEntryPoint;
private final boolean isDecompiling; private final boolean isDecompiling;
@@ -40,4 +43,34 @@ public class DecompilerActionContext extends NavigatableActionContext implements
return isDecompiling; 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);
}
} }
@@ -44,7 +44,6 @@ import ghidra.program.model.address.*;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*; import ghidra.program.model.symbol.*;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.*; import ghidra.program.util.*;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.Swing; import ghidra.util.Swing;
@@ -780,14 +779,35 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
// //
// Search // Search
// //
String searchGroup = "comment2 - Search Group"; String searchGroup = "comment2 - Search Group";
subGroupPosition = 0; // reset for the next group subGroupPosition = 0; // reset for the next group
findAction = new FindAction(tool, controller, owner); findAction = new FindAction(tool, controller, owner);
setGroupInfo(findAction, searchGroup, subGroupPosition++); 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); findReferencesAction = new FindReferencesToDataTypeAction(owner, tool, controller);
setGroupInfo(findReferencesAction, searchGroup, subGroupPosition++); 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 // Options
@@ -839,7 +859,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
private void setGroupInfo(DockingAction action, String group, int subGroupPosition) { private void setGroupInfo(DockingAction action, String group, int subGroupPosition) {
MenuData popupMenuData = action.getPopupMenuData(); MenuData popupMenuData = action.getPopupMenuData();
popupMenuData.setMenuGroup(group); popupMenuData.setMenuGroup(group);
popupMenuData.setMenuSubGroup(Integer.toString(subGroupPosition++)); popupMenuData.setMenuSubGroup(Integer.toString(subGroupPosition));
} }
private void graphServiceRemoved() { private void graphServiceRemoved() {
@@ -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();
}
}
@@ -21,6 +21,7 @@ import ghidra.app.actions.AbstractFindReferencesDataTypeAction;
import ghidra.app.decompiler.*; import ghidra.app.decompiler.*;
import ghidra.app.decompiler.component.*; import ghidra.app.decompiler.component.*;
import ghidra.app.plugin.core.decompile.DecompilerActionContext; import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferencesService;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.data.DataType; import ghidra.program.model.data.DataType;
import ghidra.program.model.pcode.HighVariable; import ghidra.program.model.pcode.HighVariable;
@@ -36,7 +37,8 @@ public class FindReferencesToDataTypeAction extends AbstractFindReferencesDataTy
super(tool, NAME, owner, DEFAULT_KEY_STROKE); super(tool, NAME, owner, DEFAULT_KEY_STROKE);
this.controller = controller; this.controller = controller;
setPopupMenuData(new MenuData(new String[] { "Find Uses of " })); setPopupMenuData(
new MenuData(new String[] { LocationReferencesService.MENU_GROUP, "Find Uses of " }));
} }
@Override @Override
@@ -136,7 +138,7 @@ public class FindReferencesToDataTypeAction extends AbstractFindReferencesDataTy
} }
MenuData data = getPopupMenuData().cloneData(); MenuData data = getPopupMenuData().cloneData();
data.setMenuPath(new String[] { menuName }); data.setMenuPath(new String[] { LocationReferencesService.MENU_GROUP, menuName });
setPopupMenuData(data); setPopupMenuData(data);
} }
} }
@@ -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);
}
}
@@ -26,21 +26,28 @@ import docking.ActionContext;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.widgets.table.threaded.ThreadedTableModel; import docking.widgets.table.threaded.ThreadedTableModel;
import ghidra.app.actions.AbstractFindReferencesDataTypeAction; 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.LocationReferencesProvider;
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferencesService;
import ghidra.app.services.DataTypeReference; import ghidra.app.services.DataTypeReference;
import ghidra.app.services.DataTypeReferenceFinder; import ghidra.app.services.DataTypeReferenceFinder;
import ghidra.program.model.data.Composite; import ghidra.program.model.data.Composite;
import ghidra.program.model.data.DataType; import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
import mockit.Mock; import mockit.*;
import mockit.MockUp;
public abstract class AbstractDecompilerFindReferencesActionTest extends AbstractDecompilerTest { public abstract class AbstractDecompilerFindReferencesActionTest extends AbstractDecompilerTest {
protected DockingActionIf findReferencesAction; protected DockingActionIf findReferencesAction;
protected DockingActionIf findReferencesToSymbolAction;
protected DockingActionIf findReferencesToAddressAction;
protected SpyDataTypeReferenceFinder<DataTypeReferenceFinder> spyReferenceFinder; protected SpyDataTypeReferenceFinder<DataTypeReferenceFinder> spyReferenceFinder;
protected SpyLocationReferencesService<LocationReferencesService> spyLocationReferenceService;
@Override @Override
@Before @Before
@@ -49,6 +56,9 @@ public abstract class AbstractDecompilerFindReferencesActionTest extends Abstrac
super.setUp(); super.setUp();
findReferencesAction = getAction(decompiler, AbstractFindReferencesDataTypeAction.NAME); findReferencesAction = getAction(decompiler, AbstractFindReferencesDataTypeAction.NAME);
findReferencesToSymbolAction = getAction(decompiler, FindReferencesToSymbolAction.NAME);
findReferencesToAddressAction =
getAction(decompiler, AbstractFindReferencesToAddressAction.NAME);
installSpyDataTypeReferenceFinder(); installSpyDataTypeReferenceFinder();
} }
@@ -56,7 +66,9 @@ public abstract class AbstractDecompilerFindReferencesActionTest extends Abstrac
private void installSpyDataTypeReferenceFinder() { private void installSpyDataTypeReferenceFinder() {
spyReferenceFinder = new SpyDataTypeReferenceFinder<>(); spyReferenceFinder = new SpyDataTypeReferenceFinder<>();
replaceService(DataTypeReferenceFinder.class, StubDataTypeReferenceFinder.class); replaceService(tool, DataTypeReferenceFinder.class, new StubDataTypeReferenceFinder());
spyLocationReferenceService = new SpyLocationReferencesService<>();
} }
protected void assertFindAllReferencesToCompositeFieldWasCalled() { protected void assertFindAllReferencesToCompositeFieldWasCalled() {
@@ -69,7 +81,15 @@ public abstract class AbstractDecompilerFindReferencesActionTest extends Abstrac
assertEquals(1, spyReferenceFinder.getFindDataTypeReferencesCallCount()); 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 // tricky business - the 'finder' is being run in a thread pool, so we must wait for that
// model to finish loading // model to finish loading
@@ -80,11 +100,34 @@ public abstract class AbstractDecompilerFindReferencesActionTest extends Abstrac
return model; 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() { protected ThreadedTableModel<?, ?> waitForSearchProvider() {
LocationReferencesProvider searchProvider = LocationReferencesProvider searchProvider =
(LocationReferencesProvider) tool.getComponentProvider(LocationReferencesProvider.NAME); (LocationReferencesProvider) tool.getComponentProvider(LocationReferencesProvider.NAME);
assertNotNull("Could not find the Location References Provider", searchProvider);
ThreadedTableModel<?, ?> model = getTableModel(searchProvider); ThreadedTableModel<?, ?> model = getTableModel(searchProvider);
waitForTableModel(model); waitForTableModel(model);
@@ -138,4 +181,21 @@ public abstract class AbstractDecompilerFindReferencesActionTest extends Abstrac
return compositeFieldReferencesCallCount.get(); return compositeFieldReferencesCallCount.get();
} }
} }
public class SpyLocationReferencesService<T extends LocationReferencesService>
extends MockUp<T> {
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();
}
}
} }
@@ -122,7 +122,7 @@ public class DecompilerFindReferencesToActionTest
int line = 2; int line = 2;
int charPosition = 17; int charPosition = 17;
setDecompilerLocation(line, charPosition); setDecompilerLocation(line, charPosition);
perfomFindDataTypes(); performFindDataTypes();
assertFindAllReferencesToDataTypeWasCalled(); assertFindAllReferencesToDataTypeWasCalled();
} }
@@ -153,7 +153,7 @@ public class DecompilerFindReferencesToActionTest
int line = 5; int line = 5;
int charPosition = 2; int charPosition = 2;
setDecompilerLocation(line, charPosition); setDecompilerLocation(line, charPosition);
perfomFindDataTypes(); performFindDataTypes();
assertFindAllReferencesToDataTypeWasCalled(); assertFindAllReferencesToDataTypeWasCalled();
} }
@@ -184,11 +184,67 @@ public class DecompilerFindReferencesToActionTest
int line = 5; int line = 5;
int charPosition = 7; int charPosition = 7;
setDecompilerLocation(line, charPosition); setDecompilerLocation(line, charPosition);
perfomFindDataTypes(); performFindDataTypes();
assertFindAllReferencesToCompositeFieldWasCalled(); 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 // Private Methods
//================================================================================================== //==================================================================================================
@@ -120,7 +120,7 @@ public class DecompilerFindReferencesToNestedStructureActionTest
int line = 9; int line = 9;
int charPosition = 44; int charPosition = 44;
setDecompilerLocation(line, charPosition); setDecompilerLocation(line, charPosition);
perfomFindDataTypes(); performFindDataTypes();
assertFindAllReferencesToCompositeFieldWasCalled(); assertFindAllReferencesToCompositeFieldWasCalled();
} }
@@ -395,6 +395,13 @@ public abstract class DockingAction implements DockingActionIf {
buffer.append('\n'); buffer.append('\n');
buffer.append(" MENU GROUP: ").append(menuBarData.getMenuGroup()); buffer.append(" MENU GROUP: ").append(menuBarData.getMenuGroup());
buffer.append('\n'); buffer.append('\n');
String parentGroup = popupMenuData.getParentMenuGroup();
if (parentGroup != null) {
buffer.append(" PARENT GROUP: ").append(parentGroup);
buffer.append('\n');
}
Icon icon = menuBarData.getMenuIcon(); Icon icon = menuBarData.getMenuIcon();
if (icon != null && icon instanceof ImageIconWrapper) { if (icon != null && icon instanceof ImageIconWrapper) {
ImageIconWrapper wrapper = (ImageIconWrapper) icon; ImageIconWrapper wrapper = (ImageIconWrapper) icon;
@@ -412,6 +419,12 @@ public abstract class DockingAction implements DockingActionIf {
buffer.append(" POPUP GROUP: ").append(popupMenuData.getMenuGroup()); buffer.append(" POPUP GROUP: ").append(popupMenuData.getMenuGroup());
buffer.append('\n'); buffer.append('\n');
String parentGroup = popupMenuData.getParentMenuGroup();
if (parentGroup != null) {
buffer.append(" PARENT GROUP: ").append(parentGroup);
buffer.append('\n');
}
String menuSubGroup = popupMenuData.getMenuSubGroup(); String menuSubGroup = popupMenuData.getMenuSubGroup();
if (menuSubGroup != MenuData.NO_SUBGROUP) { if (menuSubGroup != MenuData.NO_SUBGROUP) {
buffer.append(" POPUP SUB-GROUP: ").append(menuSubGroup); buffer.append(" POPUP SUB-GROUP: ").append(menuSubGroup);
@@ -31,6 +31,7 @@ public class MenuData {
private Icon icon; private Icon icon;
private int mnemonic = NO_MNEMONIC; private int mnemonic = NO_MNEMONIC;
private String menuGroup; private String menuGroup;
private String parentMenuGroup;
/** /**
* The subgroup string. This string is used to sort items within a * The subgroup string. This string is used to sort items within a
@@ -73,11 +74,14 @@ public class MenuData {
this.icon = menuData.icon; this.icon = menuData.icon;
this.menuGroup = menuData.menuGroup; this.menuGroup = menuData.menuGroup;
this.menuSubGroup = menuData.menuSubGroup; this.menuSubGroup = menuData.menuSubGroup;
this.parentMenuGroup = menuData.parentMenuGroup;
this.mnemonic = menuData.mnemonic; this.mnemonic = menuData.mnemonic;
} }
public MenuData cloneData() { 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) { 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 * Returns the icon assigned to this action's menu. Null indicates that this action does not
* have a menu icon * have a menu icon
* @return the icon
*/ */
public Icon getMenuIcon() { public Icon getMenuIcon() {
return icon; 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 <b>within a section</b>, then provide a value for
* {@link #setMenuSubGroup(String)}.
*
* @return the group
*/
public String getMenuGroup() { public String getMenuGroup() {
return menuGroup; return menuGroup;
} }
@@ -126,11 +139,24 @@ public class MenuData {
* Returns the subgroup string. This string is used to sort items within a * 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, * {@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. * then the value will effectively place this item at the end of its specified group.
* @return the sub-group
*/ */
public String getMenuSubGroup() { public String getMenuSubGroup() {
return menuSubGroup; 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) { public void setIcon(Icon newIcon) {
if (icon == newIcon) { if (icon == newIcon) {
return; return;
@@ -154,11 +180,38 @@ public class MenuData {
if (SystemUtilities.isEqual(menuSubGroup, newSubGroup)) { if (SystemUtilities.isEqual(menuSubGroup, newSubGroup)) {
return; return;
} }
if (newSubGroup == null) {
newSubGroup = NO_SUBGROUP;
}
MenuData oldData = cloneData(); MenuData oldData = cloneData();
menuSubGroup = newSubGroup; menuSubGroup = newSubGroup;
firePropertyChanged(oldData); 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) { public void setMenuPath(String[] newPath) {
if (newPath == null || newPath.length == 0) { if (newPath == null || newPath.length == 0) {
throw new IllegalArgumentException("Menu path cannot be null or empty"); throw new IllegalArgumentException("Menu path cannot be null or empty");
@@ -102,28 +102,8 @@ public class MenuManager implements ManagedMenuItem {
checkForSwingThread(); checkForSwingThread();
resetMenus(); resetMenus();
MenuData menuData = usePopupPath ? action.getPopupMenuData() : action.getMenuBarData(); MenuData menuData = usePopupPath ? action.getPopupMenuData() : action.getMenuBarData();
String[] actionMenuPath = menuData.getMenuPath(); if (isSubMenu(menuData)) {
if (actionMenuPath.length > level + 1) { MenuManager mgr = getSubMenu(menuData);
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);
}
mgr.addAction(action); mgr.addAction(action);
} }
else { 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) { public DockingActionIf getAction(String actionName) {
for (ManagedMenuItem item : managedMenuItems) { for (ManagedMenuItem item : managedMenuItems) {
if (item instanceof MenuItemManager) { if (item instanceof MenuItemManager) {
@@ -158,17 +202,18 @@ public class MenuManager implements ManagedMenuItem {
} }
/*** /***
* Removes the Mnemonic indicator character (&) from the text. * Removes the Mnemonic indicator character (&) from the text
* @param str the text to strip. * @param text the text to strip
* @return the stripped mnemonic
*/ */
public static String stripMnemonicAmp(String str) { public static String stripMnemonicAmp(String text) {
int ampLoc = str.indexOf('&'); int ampLoc = text.indexOf('&');
if (ampLoc < 0) { if (ampLoc < 0) {
return str; return text;
} }
String s = str.substring(0, ampLoc); String s = text.substring(0, ampLoc);
if (ampLoc < (str.length() - 1)) { if (ampLoc < (text.length() - 1)) {
s += str.substring(++ampLoc); s += text.substring(++ampLoc);
} }
return s; 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() { public JMenu getMenu() {
if (menu == null) { if (menu == null) {
@@ -236,9 +282,6 @@ public class MenuManager implements ManagedMenuItem {
return menuSubGroup; return menuSubGroup;
} }
/**
* @see docking.menu.ManagedMenuItem#dispose()
*/
@Override @Override
public void dispose() { public void dispose() {
for (ManagedMenuItem item : managedMenuItems) { 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() { public JPopupMenu getPopupMenu() {
if (popupMenu == null) { if (popupMenu == null) {
@@ -101,7 +101,7 @@ public class ServiceManager {
* *
* @see #setServiceAddedNotificationsOn(boolean) * @see #setServiceAddedNotificationsOn(boolean)
*/ */
public synchronized <T> void addService(Class<T> interfaceClass, T service) { public synchronized <T> void addService(Class<? extends T> interfaceClass, T service) {
List<Object> list = List<Object> list =
servicesByInterface.computeIfAbsent(interfaceClass, (k) -> new ArrayList<>()); servicesByInterface.computeIfAbsent(interfaceClass, (k) -> new ArrayList<>());
if (list.contains(service)) { if (list.contains(service)) {