Merge remote-tracking branch

'origin/GP-5326_ghidragon_adding_address_and_data_options'
(Closes #7407)
This commit is contained in:
Ryan Kurtz 2025-04-07 14:52:56 -04:00
commit f1a135d8bb
14 changed files with 909 additions and 125 deletions

View file

@ -972,19 +972,21 @@
<OL>
<LI>Right mouse click on a structure member in the Listing</LI>
<LI>Choose the <B>Data</B><IMG src="help/shared/arrow.gif"> <B>Edit Field</B>
<LI>Choose the <B>Data</B><IMG src="help/shared/arrow.gif"> <B>Quick Edit Field</B>
action to bring the up the <A href=
"#Edit_Field_Dialog">Edit Field Dialog</A> </LI>
</OL>
<P>The second way is more useful for changing the names of multiple members:</P>
<P>The second way is more useful for changing the names of multiple members. This method
will show the full Structure Editor:
</P>
<OL>
<LI>Place the cursor on the first line of the structure</LI>
<LI>Press mouse-right over the structure and choose <B>Data</B><IMG src=
"help/shared/arrow.gif"> <B>Edit Data Type...</B></LI>
"help/shared/arrow.gif"> <B>Edit Data Type</B></LI>
<LI>Edit the field name for the structure member</LI>
</OL>
@ -1369,13 +1371,17 @@
bringing up the entire structure or union editor. To edit a field, click anywhere on the line
displaying that field in the listing and then right click and select <B>Data</B><IMG
src="help/shared/arrow.gif"> <B>Edit Field</B> from the popup context menu.</P>
<H3><A name="Edit_Field_Dialog"></A>Edit Field Dialog</H3>
<H3><A name="Edit_Field_Dialog"></A><A name="Quick_Edit_Field"></A>Edit Field Dialog</H3>
<P align="center"><IMG src="images/EditFieldDialog.png" alt=""> &nbsp;</P>
<UL>
<LI><B>Field Name</B>: The name of the structure or union field can be changed here.</LI>
<LI><B>Comment</B>: The comment for the field can be entered or changed here.</LI>
<LI><B>DataType</B>: The data can be changed here. The text field is read only so you must
press the ... button to bring up the datatype chooser to change the datatype.</LI>
<LI><B>Add Current Address</B>: If selected, the current address where this field is edited
will be added to the datatype's field comment if not already there.</LI>
<LI><B>Add Today's Date</B>: If selected, the current date will be added to the datatype's
field comment if not already there.</LI>
<P><IMG src="help/shared/note.png"> If a default field (a field with an undefined
datatype (??)) is named or given a comment, the datatype will be set to <B>undefined1</B> if
no specific datatype is set. This is because undefined fields are not stored and therefore

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Before After
Before After

View file

@ -85,7 +85,7 @@ public class DataPlugin extends Plugin implements DataService {
private static final String BASIC_DATA_GROUP = "BasicData";
private static final String DATA_MENU_POPUP_PATH = "Data";
private static final String[] EDIT_DATA_TYPE_POPUP_PATH =
{ DATA_MENU_POPUP_PATH, "Edit Data Type..." };
{ DATA_MENU_POPUP_PATH, "Edit Data Type" };
private static final String[] DATA_SETTINGS_POPUP_PATH =
{ DATA_MENU_POPUP_PATH, "Settings..." };
private static final String[] DEFAULT_SETTINGS_POPUP_PATH =
@ -95,19 +95,15 @@ public class DataPlugin extends Plugin implements DataService {
{ DATA_MENU_POPUP_PATH, "Choose Data Type..." };
private DataTypeManagerService dtmService;
private DataTypeManagerChangeListenerAdapter adapter;
private DataAction pointerAction;
private DataAction recentlyUsedAction;
private DockingAction editDataTypeAction;
private CreateStructureAction createStructureAction;
private CreateArrayAction createArrayAction;
private List<DataAction> favoriteActions = new ArrayList<>();
private ChooseDataTypeAction chooseDataTypeAction;
private DataTypeManagerChangeListenerAdapter adapter;
private List<DataAction> favoriteActions = new ArrayList<>();
private SwingUpdateManager favoritesUpdateManager;
public DataPlugin(PluginTool tool) {
@ -150,16 +146,20 @@ public class DataPlugin extends Plugin implements DataService {
pointerAction = new PointerDataAction(this);
tool.addAction(pointerAction);
new ActionBuilder("Edit Field", getName())
.popupMenuPath("Data", "Edit Field")
new ActionBuilder("Quick Edit Field", getName())
.helpLocation(new HelpLocation("DataPlugin", "Quick_Edit_Field"))
.popupMenuPath("Data", "Quick Edit Field...")
.popupMenuGroup("BasicData")
.keyBinding("ctrl shift E")
.sharedKeyBinding()
.withContext(ListingActionContext.class)
.enabledWhen(this::canEditField)
.onAction(this::editField)
.buildAndInstall(tool);
// Data instance settings action based upon data selection in listing
new ActionBuilder("Data Settings", getName()).sharedKeyBinding()
new ActionBuilder("Data Settings", getName())
.sharedKeyBinding()
.popupMenuPath(DATA_SETTINGS_POPUP_PATH)
.popupMenuGroup("Settings")
.withContext(ListingActionContext.class)
@ -168,7 +168,8 @@ public class DataPlugin extends Plugin implements DataService {
.buildAndInstall(tool);
// Default settings action based upon data selection in listing
new ActionBuilder("Default Settings", getName()).sharedKeyBinding()
new ActionBuilder("Default Settings", getName())
.sharedKeyBinding()
.popupMenuPath(DEFAULT_SETTINGS_POPUP_PATH)
.popupMenuGroup("Settings")
.withContext(ListingActionContext.class)
@ -177,7 +178,8 @@ public class DataPlugin extends Plugin implements DataService {
.buildAndInstall(tool);
// Default settings action for selected datatypes from datatype manager
new ActionBuilder("Default Settings", getName()).sharedKeyBinding()
new ActionBuilder("Default Settings", getName())
.sharedKeyBinding()
.popupMenuPath(DATATYPE_SETTINGS_POPUP_PATH)
.popupMenuGroup("Settings")
.withContext(DataTypesActionContext.class)
@ -195,7 +197,8 @@ public class DataPlugin extends Plugin implements DataService {
.buildAndInstall(tool);
// Default settings action for composite editor components (stand-alone archive)
new ActionBuilder("Default Settings", getName()).sharedKeyBinding()
new ActionBuilder("Default Settings", getName())
.sharedKeyBinding()
.popupMenuPath(DATATYPE_SETTINGS_POPUP_PATH)
.popupMenuGroup("Settings")
.withContext(ComponentStandAloneActionContext.class)
@ -204,14 +207,15 @@ public class DataPlugin extends Plugin implements DataService {
.buildAndInstall(tool);
editDataTypeAction =
new ActionBuilder("Edit Data Type", getName()).popupMenuPath(EDIT_DATA_TYPE_POPUP_PATH)
new ActionBuilder("Edit Data Type", getName())
.popupMenuPath(EDIT_DATA_TYPE_POPUP_PATH)
.popupMenuGroup("BasicData")
.withContext(ListingActionContext.class)
.enabledWhen(c -> {
DataType editableDt = getEditableDataTypeFromContext(c);
if (editableDt != null) {
editDataTypeAction
.setHelpLocation(dtmService.getEditorHelpLocation(editableDt));
HelpLocation helps = dtmService.getEditorHelpLocation(editableDt);
editDataTypeAction.setHelpLocation(helps);
return true;
}
return false;
@ -293,7 +297,6 @@ public class DataPlugin extends Plugin implements DataService {
@Override
public boolean createData(DataType dt, ListingActionContext context, boolean stackPointers,
boolean enableConflictHandling) {
// TODO: conflict handler (i.e., removal of other conflicting data not yet supported)
ProgramLocation location = context.getLocation();
if (!(location instanceof CodeUnitLocation)) {
return false;
@ -327,7 +330,7 @@ public class DataPlugin extends Plugin implements DataService {
ProgramLocation location) {
Address start = location.getAddress();
int[] startPath = location.getComponentPath();
Command cmd;
Command<Program> cmd;
if (startPath != null && startPath.length != 0) {
cmd = new CreateDataInStructureCmd(start, startPath, dt, stackPointers);
}
@ -342,7 +345,7 @@ public class DataPlugin extends Plugin implements DataService {
private boolean createDataForSelection(Program program, DataType dt, boolean stackPointers,
ProgramSelection selection) {
BackgroundCommand cmd;
BackgroundCommand<Program> cmd;
Address start = selection.getMinAddress();
InteriorSelection interSel = selection.getInteriorSelection();
if (interSel != null) {
@ -837,10 +840,6 @@ public class DataPlugin extends Plugin implements DataService {
return true;
}
public DataType pickDataType() {
return dtmService.getDataType("");
}
private boolean canEditField(ListingActionContext context) {
ProgramLocation location = context.getLocation();
int[] componentPath = location.getComponentPath();
@ -848,16 +847,23 @@ public class DataPlugin extends Plugin implements DataService {
}
private void editField(ListingActionContext context) {
Program program = context.getProgram();
ProgramLocation location = context.getLocation();
Address address = location.getAddress();
int[] path = location.getComponentPath();
DataTypeComponent component = DataTypeUtils.getDataTypeComponent(program, address, path);
if (component != null) {
EditDataFieldDialog dialog =
new EditDataFieldDialog(tool, dtmService, location, component);
tool.showDialog(dialog);
DataTypeComponent dtc = DataTypeUtils.getDataTypeComponent(program, address, path);
if (dtc == null) {
return;
}
DataType parent = dtc.getParent();
Composite composite = (Composite) parent;
int ordinal = dtc.getOrdinal();
EditDataFieldDialog dialog =
new EditDataFieldDialog(tool, dtmService, composite, program, address, ordinal);
tool.showDialog(dialog);
}
}

View file

@ -16,24 +16,24 @@
package ghidra.app.plugin.core.data;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.util.Date;
import javax.swing.*;
import org.apache.commons.lang3.StringUtils;
import docking.DialogComponentProvider;
import docking.widgets.button.BrowseButton;
import ghidra.app.cmd.data.CreateDataInStructureCmd;
import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
import ghidra.app.services.DataTypeManagerService;
import ghidra.app.util.datatype.DataTypeSelectionEditor;
import ghidra.framework.cmd.Command;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.util.HelpLocation;
import ghidra.util.MessageType;
import ghidra.util.*;
import ghidra.util.data.DataTypeParser.AllowedDataTypes;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.layout.PairLayout;
@ -42,30 +42,49 @@ import ghidra.util.layout.PairLayout;
*/
public class EditDataFieldDialog extends DialogComponentProvider {
// These two fields are static so that the user's last choice is remembered across dialog uses.
// The preferred way to do this would be to have a plugin manage this state and have that plugin
// make the dialog available as a service. At the time of writing, this solution seemed good
// enough. The downside of this is that these values are not saved across uses of Ghidra.
private static boolean addAddress;
private static boolean addDate;
private JTextField nameField;
private JTextField commentField;
private JTextField dataTypeTextField;
private DataTypeSelectionEditor dataTypeEditor;
private JCheckBox addressCheckBox;
private JCheckBox dateCheckBox;
private DataTypeComponent component;
private PluginTool tool;
private DataType newDataType;
private ProgramLocation programLocation;
private DataTypeManagerService dtmService;
private Composite composite;
private Address address;
private int ordinal;
private Program program;
/**
* Constructor
* @param tool The tool hosting this dialog
* @param dtmService the DataTypeManagerService used for choosing datatypes
* @param location the location of the field being edited
* @param dataTypeComponent the component of the field being edited
* @param composite the composite being edited
* @param program the program
* @param address the address of the data type component
* @param ordinal the ordinal of the data type component inside of the composite
*/
public EditDataFieldDialog(PluginTool tool, DataTypeManagerService dtmService,
ProgramLocation location, DataTypeComponent dataTypeComponent) {
Composite composite, Program program, Address address, int ordinal) {
super("Edit Field Dialog", true, true, true, false);
this.tool = tool;
this.dtmService = dtmService;
this.programLocation = location;
this.component = dataTypeComponent;
this.composite = composite;
this.program = program;
this.address = address;
this.ordinal = ordinal;
setTitle(generateTitle());
addWorkPanel(buildMainPanel());
@ -80,9 +99,8 @@ public class EditDataFieldDialog extends DialogComponentProvider {
@Override
public void dispose() {
super.dispose();
programLocation = null;
component = null;
tool = null;
program = null;
}
/**
@ -111,7 +129,7 @@ public class EditDataFieldDialog extends DialogComponentProvider {
/**
* Returns the text currently in the text field for the field comment.
* @return the text currently in the text field for the field commment
* @return the text currently in the text field for the field comment
*/
public String getCommentText() {
return commentField.getText();
@ -136,20 +154,60 @@ public class EditDataFieldDialog extends DialogComponentProvider {
}
private void initializeFields() {
String name = component.getFieldName();
if (StringUtils.isBlank(name)) {
name = "";
}
String name = getFieldName();
nameField.setText(name);
commentField.setText(component.getComment());
dataTypeTextField.setText(component.getDataType().getDisplayName());
String comment = getComment();
commentField.setText(comment);
DataType dt = getComponentDataType();
dataTypeEditor.setCellEditorValue(dt);
if (addAddress) {
addressCheckBox.setSelected(true);
addTextToComment(getCurrentAddressString());
}
if (addDate) {
dateCheckBox.setSelected(true);
addTextToComment(getTodaysDate());
}
}
private String getComment() {
if (hasNoDataTypeComponent()) {
return "";
}
DataTypeComponent dtc = composite.getComponent(ordinal);
String comment = dtc.getComment();
if (StringUtils.isBlank(comment)) {
return "";
}
return comment;
}
private String getFieldName() {
if (hasNoDataTypeComponent()) {
return "";
}
DataTypeComponent dtc = composite.getComponent(ordinal);
String fieldName = dtc.getFieldName();
if (StringUtils.isBlank(fieldName)) {
return "";
}
return fieldName;
}
private boolean hasNoDataTypeComponent() {
return ordinal >= composite.getNumComponents();
}
@Override
protected void okCallback() {
if (updateComponent()) {
close();
programLocation = null;
}
}
@ -158,7 +216,7 @@ public class EditDataFieldDialog extends DialogComponentProvider {
return true;
}
Command<Program> cmd = new UpdateDataComponentCommand();
if (!tool.execute(cmd, programLocation.getProgram())) {
if (!tool.execute(cmd, program)) {
setStatusText(cmd.getStatusMsg(), MessageType.ERROR);
return false;
}
@ -170,23 +228,31 @@ public class EditDataFieldDialog extends DialogComponentProvider {
}
private boolean hasCommentChange() {
String oldComment = getComment();
String newComment = getNewFieldComment();
if (StringUtils.isBlank(newComment) && StringUtils.isBlank(component.getComment())) {
if (StringUtils.isBlank(newComment) && StringUtils.isBlank(oldComment)) {
return false;
}
return !newComment.equals(component.getComment());
return !newComment.equals(oldComment);
}
private DataType getComponentDataType() {
if (hasNoDataTypeComponent()) {
return DataType.DEFAULT;
}
DataTypeComponent dtc = composite.getComponent(ordinal);
return dtc.getDataType();
}
boolean hasDataTypeChange() {
return newDataType != null && !newDataType.equals(component.getDataType());
DataType oldDt = getComponentDataType();
return newDataType != null && !newDataType.equals(oldDt);
}
boolean hasNameChange() {
String newName = getNewFieldName();
String currentName = component.getFieldName();
if (currentName == null) {
currentName = component.getDefaultFieldName();
}
String currentName = getFieldName();
if (newName.equals(currentName)) {
return false;
}
@ -202,6 +268,27 @@ public class EditDataFieldDialog extends DialogComponentProvider {
}
private JPanel buildMainPanel() {
JPanel panel = new JPanel(new BorderLayout());
panel.add(buildNameValuePanel(), BorderLayout.NORTH);
panel.add(buildCheckboxPanel(), BorderLayout.SOUTH);
return panel;
}
private JPanel buildCheckboxPanel() {
JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 30, 0));
addressCheckBox = new JCheckBox("Add Current Address");
addressCheckBox.addActionListener(this::addressCheckBoxChanged);
dateCheckBox = new JCheckBox("Add Today's Date");
dateCheckBox.addActionListener(this::dateCheckBoxChanged);
panel.add(addressCheckBox);
panel.add(dateCheckBox);
return panel;
}
private JPanel buildNameValuePanel() {
JPanel panel = new JPanel(new PairLayout(10, 10));
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
@ -214,10 +301,10 @@ public class EditDataFieldDialog extends DialogComponentProvider {
panel.add(new JLabel("Field Name:", SwingConstants.LEFT));
panel.add(nameField);
panel.add(new JLabel("Comment:", SwingConstants.LEFT));
panel.add(commentField);
panel.add(new JLabel("Datatype:", SwingConstants.LEFT));
panel.add(buildDataTypeChooserPanel());
panel.add(new JLabel("Comment:", SwingConstants.LEFT));
panel.add(commentField);
return panel;
}
@ -225,72 +312,206 @@ public class EditDataFieldDialog extends DialogComponentProvider {
private JPanel buildDataTypeChooserPanel() {
JPanel panel = new JPanel(new BorderLayout(10, 0));
dataTypeTextField = new JTextField();
dataTypeTextField.setEditable(false);
BrowseButton browseButton = new BrowseButton();
browseButton.setToolTipText("Browse the Data Manager");
browseButton.addActionListener(e -> showDataTypeBrowser());
DataTypeManager dtm = composite.getDataTypeManager();
dataTypeEditor = new DataTypeSelectionEditor(dtm, dtmService, AllowedDataTypes.ALL);
JComponent editorComponent = dataTypeEditor.getEditorComponent();
panel.add(editorComponent, BorderLayout.CENTER);
panel.add(dataTypeTextField, BorderLayout.CENTER);
panel.add(browseButton, BorderLayout.EAST);
return panel;
}
private void showDataTypeBrowser() {
newDataType = dtmService.getDataType("");
updateDataTypeTextField();
}
private void updateDataTypeTextField() {
if (newDataType != null) {
dataTypeTextField.setText(newDataType.getDisplayName());
dataTypeEditor.setCellEditorValue(newDataType);
}
else {
dataTypeTextField.setText(component.getDataType().getDisplayName());
DataType dt = getComponentDataType();
dataTypeEditor.setCellEditorValue(dt);
}
}
private String generateTitle() {
DataType parent = component.getParent();
String compositeName = parent.getName();
return "Edit " + compositeName + ", Field " + component.getOrdinal();
String compositeName = composite.getName();
return "Edit " + compositeName + ", Field " + ordinal;
}
private void dateCheckBoxChanged(ActionEvent e) {
String today = getTodaysDate();
addDate = dateCheckBox.isSelected();
if (addDate) {
addTextToComment(today);
}
else {
removeTextFromComment(today);
}
}
private void addressCheckBoxChanged(ActionEvent e) {
String addressString = getCurrentAddressString();
addAddress = addressCheckBox.isSelected();
if (addAddress) {
addTextToComment(addressString);
}
else {
removeTextFromComment(addressString);
}
}
private void removeTextFromComment(String text) {
String comment = commentField.getText().trim();
int index = comment.indexOf(text);
if (index < 0) {
return;
}
// remove the given text and any spaces that follow it.
comment = comment.replaceAll(text + "\\s*", "");
commentField.setText(comment.trim());
}
private String getTodaysDate() {
return DateUtils.formatCompactDate(new Date());
}
private String getCurrentAddressString() {
return address.toString();
}
private void addTextToComment(String text) {
String comment = commentField.getText().trim();
if (comment.contains(text)) {
return;
}
if (!comment.isBlank()) {
comment += " ";
}
comment += text;
commentField.setText(comment.trim());
}
public String getDataTypeText() {
return dataTypeTextField.getText();
return dataTypeEditor.getCellEditorValueAsText();
}
private class UpdateDataComponentCommand implements Command<Program> {
private String statusMessage = null;
@Override
public boolean applyTo(Program program) {
if (component.isUndefined() || hasDataTypeChange()) {
DataType dt = getNewDataType();
Address address = programLocation.getAddress();
int[] path = programLocation.getComponentPath();
Command<Program> cmd = new CreateDataInStructureCmd(address, path, dt, false);
if (!cmd.applyTo(program)) {
statusMessage = cmd.getStatusMsg();
public boolean applyTo(Program p) {
maybeAdjustStructure();
if (!updateDataType()) {
return false;
}
component = DataTypeUtils.getDataTypeComponent(program, address, path);
if (!updateName()) {
return false;
}
if (hasNameChange()) {
if (!updateComment()) {
return false;
}
return true;
}
private void maybeAdjustStructure() {
if (!(composite instanceof Structure struct)) {
return;
}
int n = composite.getNumComponents();
if (ordinal >= n) {
int amount = ordinal - n;
struct.growStructure(amount);
}
DataTypeComponent dtc = composite.getComponent(ordinal);
if (dtc.getDataType() == DataType.DEFAULT) { // remove placeholder type
DataType newtype = new Undefined1DataType();
struct.replaceAtOffset(dtc.getOffset(), newtype, 1, "tempName",
"Created by Edit Data Field action");
}
}
private boolean updateName() {
if (!hasNameChange()) {
return true;
}
DataTypeComponent dtc = composite.getComponent(ordinal);
try {
component.setFieldName(getNewFieldName());
dtc.setFieldName(getNewFieldName());
return true;
}
catch (DuplicateNameException e) {
statusMessage = "Duplicate field name";
return false;
}
}
private boolean updateComment() {
DataTypeComponent dtc = composite.getComponent(ordinal);
if (hasCommentChange()) {
component.setComment(getNewFieldComment());
dtc.setComment(getNewFieldComment());
}
return true;
}
private boolean updateDataType() {
if (!hasDataTypeChange()) {
return true;
}
try {
if (composite instanceof Structure struct) {
updateStructure(struct);
}
else if (composite instanceof Union union) {
updateUnion(union);
}
return true;
}
catch (DuplicateNameException e) {
statusMessage = "Duplicate field name";
return false;
}
catch (Exception e) {
statusMessage = e.getMessage();
return false;
}
}
private void updateStructure(Structure struct) {
DataTypeComponent dtc = composite.getComponent(ordinal);
DataType resolvedDt = program.getDataTypeManager().resolve(newDataType, null);
if (resolvedDt == DataType.DEFAULT) {
struct.clearComponent(ordinal);
return;
}
DataTypeInstance dti =
DataTypeInstance.getDataTypeInstance(resolvedDt, -1, false);
DataType dataType = dti.getDataType();
int length = dti.getLength();
String fieldName = dtc.getFieldName();
String comment = dtc.getComment();
dtc = struct.replace(ordinal, dataType, length, fieldName, comment);
}
private void updateUnion(Union union) throws DuplicateNameException {
DataTypeComponent dtc = composite.getComponent(ordinal);
DataType resolvedDt = program.getDataTypeManager().resolve(newDataType, null);
String comment = dtc.getComment();
String fieldName = dtc.getFieldName();
union.insert(ordinal, resolvedDt);
union.delete(ordinal + 1);
dtc = union.getComponent(ordinal);
dtc.setComment(comment);
dtc.setFieldName(fieldName);
}
@Override
public String getStatusMsg() {
return statusMessage;

View file

@ -17,6 +17,8 @@ package ghidra.app.plugin.core.data;
import static org.junit.Assert.*;
import java.util.Date;
import org.junit.*;
import docking.action.DockingActionIf;
@ -27,6 +29,7 @@ import ghidra.program.model.data.*;
import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Program;
import ghidra.test.*;
import ghidra.util.DateUtils;
public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env;
@ -50,7 +53,7 @@ public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest {
program = buildProgram();
env.open(program);
env.showTool();
editFieldAction = getAction(plugin, "Edit Field");
editFieldAction = getAction(plugin, "Quick Edit Field");
Data dataAt = program.getListing().getDataAt(addr(0x100));
structure = (Structure) dataAt.getDataType();
codeBrowser.toggleOpen(dataAt);
@ -118,6 +121,29 @@ public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest {
assertEquals("char", structure.getComponent(4).getDataType().getDisplayName());
}
@Test
public void testEditDefinedFieldDataTypeAndNameAndComment() {
goTo(0x104);
showFieldEditDialog();
DataTypeComponent dtc = structure.getComponent(4);
assertEquals("word", dtc.getDataType().getDisplayName());
assertEquals("word", getDataTypeText());
setDataType(new CharDataType());
setNameText("TestName");
setCommentText("Flux capacitor relay");
pressOk();
waitForTasks();
assertFalse(isDialogVisible());
dtc = structure.getComponent(4);
assertEquals("char", dtc.getDataType().getDisplayName());
assertEquals("TestName", dtc.getFieldName());
assertEquals("Flux capacitor relay", dtc.getComment());
}
@Test
public void testEditUndefinedFieldName() {
goTo(0x101);
@ -162,6 +188,49 @@ public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest {
assertEquals("byte", structure.getComponent(1).getDataType().getDisplayName());
}
@Test
public void testAddAddressCheckbox() {
goTo(0x101);
showFieldEditDialog();
assertEquals("", getCommentText());
pressButtonByText(dialog.getComponent(), "Add Current Address");
assertEquals("00000101", getCommentText());
pressOk();
waitForTasks();
assertEquals("00000101", structure.getComponent(1).getComment());
showFieldEditDialog();
assertEquals("00000101", getCommentText());
pressButtonByText(dialog.getComponent(), "Add Current Address");
assertEquals("", getCommentText());
pressOk();
assertNull(structure.getComponent(1).getComment());
}
@Test
public void testAddDateCheckbox() {
String today = DateUtils.formatCompactDate(new Date());
goTo(0x101);
showFieldEditDialog();
assertEquals("", getCommentText());
pressButtonByText(dialog.getComponent(), "Add Today's Date");
assertEquals(today, getCommentText());
pressOk();
waitForTasks();
assertEquals(today, structure.getComponent(1).getComment());
showFieldEditDialog();
assertEquals(today, getCommentText());
pressButtonByText(dialog.getComponent(), "Add Today's Date");
assertEquals("", getCommentText());
pressOk();
assertNull(structure.getComponent(1).getComment());
}
private boolean isDialogVisible() {
return runSwing(() -> dialog.isVisible());
}
@ -182,7 +251,7 @@ public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest {
}
private void pressOk() {
runSwing(() -> dialog.okCallback());
pressButtonByText(dialog, "OK");
}
private String getNameText() {
@ -208,8 +277,4 @@ public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest {
private void setDataType(DataType dataType) {
runSwing(() -> dialog.setDataType(dataType));
}
private String getDialogStatusText() {
return runSwing(() -> dialog.getStatusText());
}
}

View file

@ -960,6 +960,10 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
EditDataTypeAction editDataTypeAction = new EditDataTypeAction();
setGroupInfo(editDataTypeAction, variableGroup, subGroupPosition++);
// shows the quick editor dialog
EditFieldAction editFieldAction = new EditFieldAction();
setGroupInfo(editFieldAction, variableGroup, subGroupPosition++);
//
// Listing action for Creating Structure on a Variable
//
@ -1150,6 +1154,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
addLocalAction(decompilerCreateStructureAction);
tool.addAction(listingCreateStructureAction);
addLocalAction(editDataTypeAction);
addLocalAction(editFieldAction);
addLocalAction(specifyCProtoAction);
addLocalAction(overrideSigAction);
addLocalAction(editOverrideSigAction);

View file

@ -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.*;
import ghidra.app.decompiler.ClangFieldToken;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.plugin.core.data.EditDataFieldDialog;
import ghidra.app.plugin.core.decompile.DecompilerActionContext;
import ghidra.app.services.DataTypeManagerService;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.util.*;
/**
* Performs a quick edit of a given field using the {@link EditDataFieldDialog}. This action is
* similar to the same named action available in the Listing.
*/
public class EditFieldAction extends AbstractDecompilerAction {
public EditFieldAction() {
super("Quick Edit Field", KeyBindingType.SHARED);
setHelpLocation(new HelpLocation("DataPlugin", "Edit_Field_Dialog"));
setPopupMenuData(new MenuData(new String[] { "Quick Edit Field..." }, "Decompile"));
setKeyBindingData(new KeyBindingData("ctrl shift E"));
}
@Override
public boolean isValidContext(ActionContext context) {
return (context instanceof DecompilerActionContext);
}
@Override
protected boolean isEnabledForDecompilerContext(DecompilerActionContext context) {
Function function = context.getFunction();
if (function instanceof UndefinedFunction) {
return false;
}
Address address = context.getAddress();
if (address == null) {
return false;
}
ClangToken tokenAtCursor = context.getTokenAtCursor();
if (tokenAtCursor == null) {
return false;
}
if (!(tokenAtCursor instanceof ClangFieldToken)) {
return false;
}
Composite composite = getCompositeDataType(tokenAtCursor);
if (composite == null) {
return false;
}
int offset = ((ClangFieldToken) tokenAtCursor).getOffset();
if (offset < 0 || offset >= composite.getLength()) {
return false;
}
return true;
}
@Override
protected void decompilerActionPerformed(DecompilerActionContext context) {
ClangToken tokenAtCursor = context.getTokenAtCursor();
Composite composite = getCompositeDataType(tokenAtCursor);
ClangFieldToken token = (ClangFieldToken) tokenAtCursor;
DataTypeComponent dtc = null;
int offset = token.getOffset();
String fieldName = token.getText();
if (composite instanceof Structure structure) {
dtc = structure.getComponentContaining(offset);
}
else if (composite instanceof Union union) {
int n = union.getNumComponents();
for (int i = 0; i < n; i++) {
DataTypeComponent unionDtc = union.getComponent(i);
String dtcName = unionDtc.getFieldName();
if (fieldName.equals(dtcName)) {
dtc = unionDtc;
break;
}
}
}
if (dtc == null) {
Msg.debug(this,
"Unable to find field '%s' at offset %d in composite %s".formatted(fieldName,
offset, composite.getName()));
return;
}
Address address = context.getAddress();
Program program = context.getProgram();
int ordinal = dtc.getOrdinal();
PluginTool tool = context.getTool();
DataTypeManagerService service =
tool.getService(DataTypeManagerService.class);
EditDataFieldDialog dialog =
new EditDataFieldDialog(tool, service, composite, program, address, ordinal);
tool.showDialog(dialog);
}
}

View file

@ -0,0 +1,292 @@
/* ###
* 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;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import docking.ActionContext;
import docking.action.DockingActionIf;
import ghidra.app.plugin.core.data.EditDataFieldDialog;
import ghidra.program.model.data.*;
public class DecompilerEditDataFieldTest extends AbstractDecompilerTest {
private static final long INIT_STRING_ADDR = 0X080483c7;
private DockingActionIf editFieldAction;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
editFieldAction = getAction(decompiler, "Quick Edit Field");
}
@Override
protected String getProgramName() {
return "elf/CentOS/32bit/decomp.gzf";
}
@Test
public void testActionEnablement() 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);
//
// Action should not enabled unless on the data type
//
// Empty line
int line = 1;
int charPosition = 0;
setDecompilerLocation(line, charPosition);
assertActionNotInPopup();
// Signature - first param; a data type
line = 2;
charPosition = 17;
setDecompilerLocation(line, charPosition);
assertActionNotInPopup();
// Signature - first param name
line = 2;
charPosition = 26;
setDecompilerLocation(line, charPosition);
assertActionNotInPopup();
// Syntax - {
line = 4;
charPosition = 0;
setDecompilerLocation(line, charPosition);
assertActionNotInPopup();
// Data access - the data type itself
line = 5;
charPosition = 2;
setDecompilerLocation(line, charPosition);
assertActionNotInPopup();
// Data access - the data type field dereference
line = 5;
charPosition = 7;
setDecompilerLocation(line, charPosition);
assertActionInPopup();
}
@Test
public void testEditName() {
/*
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);
ProgramBasedDataTypeManager dtm = program.getDataTypeManager();
Structure structure = (Structure) dtm.getDataType(new DataTypePath("/", "mystring"));
// Data access - the data type field dereference
int line = 5;
int charPosition = 7;
setDecompilerLocation(line, charPosition);
assertToken("alloc", line, charPosition);
EditDataFieldDialog dialog = performEditField();
assertEquals("alloc", structure.getComponent(0).getFieldName());
assertEquals("alloc", getNameText(dialog));
setNameText(dialog, "weight");
pressOk(dialog);
waitForDecompiler();
setDecompilerLocation(line, charPosition);
assertToken("weight", line, charPosition);
assertEquals("weight", structure.getComponent(0).getFieldName());
}
@Test
public void testEditDataType() {
/*
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);
ProgramBasedDataTypeManager dtm = program.getDataTypeManager();
Structure structure = (Structure) dtm.getDataType(new DataTypePath("/", "mystring"));
// Data access - the data type field dereference
int line = 5;
int charPosition = 7;
setDecompilerLocation(line, charPosition);
assertToken("alloc", line, charPosition);
EditDataFieldDialog dialog = performEditField();
assertEquals("int", structure.getComponent(0).getDataType().getDisplayName());
assertEquals("int", getDataTypeText(dialog));
setDataType(dialog, new DWordDataType());
pressOk(dialog);
waitForDecompiler();
setDecompilerLocation(line, charPosition);
assertToken("alloc", line, charPosition);
assertEquals("dword", structure.getComponent(0).getDataType().getDisplayName());
}
@Test
public void testEditComment() {
/*
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);
ProgramBasedDataTypeManager dtm = program.getDataTypeManager();
Structure structure = (Structure) dtm.getDataType(new DataTypePath("/", "mystring"));
// Data access - the data type field dereference
int line = 5;
int charPosition = 7;
setDecompilerLocation(line, charPosition);
assertToken("alloc", line, charPosition);
EditDataFieldDialog dialog = performEditField();
assertEquals(null, structure.getComponent(0).getComment());
assertEquals("", getCommentText(dialog));
setCommentText(dialog, "comment");
pressOk(dialog);
waitForDecompiler();
setDecompilerLocation(line, charPosition);
assertToken("alloc", line, charPosition);
assertEquals("comment", structure.getComponent(0).getComment());
}
//=================================================================================================
// Private Methods
//=================================================================================================
private EditDataFieldDialog performEditField() {
DecompilerActionContext context =
new DecompilerActionContext(provider, addr(0x0), false);
performAction(editFieldAction, context, false);
return waitForDialogComponent(EditDataFieldDialog.class);
}
private void pressOk(EditDataFieldDialog dialog) {
pressButtonByText(dialog, "OK");
}
private String getNameText(EditDataFieldDialog dialog) {
return runSwing(() -> dialog.getNameText());
}
private void setNameText(EditDataFieldDialog dialog, String newName) {
runSwing(() -> dialog.setNameText(newName));
}
private String getCommentText(EditDataFieldDialog dialog) {
return runSwing(() -> dialog.getCommentText());
}
private void setCommentText(EditDataFieldDialog dialog, String newName) {
runSwing(() -> dialog.setCommentText(newName));
}
private String getDataTypeText(EditDataFieldDialog dialog) {
return runSwing(() -> dialog.getDataTypeText());
}
private void setDataType(EditDataFieldDialog dialog, DataType dataType) {
runSwing(() -> dialog.setDataType(dataType));
}
private void assertActionInPopup() {
ActionContext context = provider.getActionContext(null);
assertTrue("'Edit Field' action should be enabled; currently selected token: " +
provider.currentTokenToString(), editFieldAction.isAddToPopup(context));
}
private void assertActionNotInPopup() {
ActionContext context = provider.getActionContext(null);
assertFalse(
"'Edit Field' action should not be enabled; currently selected token: " +
provider.currentTokenToString(),
editFieldAction.isAddToPopup(context));
}
}

View file

@ -29,7 +29,7 @@ import ghidra.program.model.listing.*;
import ghidra.program.model.pcode.*;
import ghidra.program.model.symbol.*;
public class EquateTest extends AbstractDecompilerTest {
public class DecompilerEquateTest extends AbstractDecompilerTest {
private static class EquateNameForce extends SetEquateAction {

View file

@ -40,7 +40,7 @@ import ghidra.program.model.pcode.*;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.model.symbol.Symbol;
public class HighSymbolTest extends AbstractDecompilerTest {
public class DecompilerHighSymbolTest extends AbstractDecompilerTest {
@Override
protected String getProgramName() {
return "Winmine__XP.exe.gzf";

View file

@ -40,7 +40,7 @@ import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
import ghidra.xml.XmlParseException;
public class SpecExtensionTest extends AbstractDecompilerTest {
public class DecompilerSpecExtensionTest extends AbstractDecompilerTest {
@Override
protected String getProgramName() {
return "Winmine__XP.exe.gzf";

View file

@ -166,6 +166,18 @@ public abstract class DecompilerReference {
}
}
}
else if (fieldDt instanceof Union union) {
String fieldName = field.getText();
int n = union.getNumComponents();
for (int i = 0; i < n; i++) {
DataTypeComponent unionDtc = union.getComponent(i);
String dtcName = unionDtc.getFieldName();
if (fieldName.equals(dtcName)) {
return unionDtc.getDataType();
}
}
}
return fieldDt;
}

View file

@ -31,6 +31,7 @@ public class DateUtils {
/** Example: Oct 31, 2019 03:24 PM */
private static final String DATE_TIME_FORMAT_STRING = "MMM dd, yyyy hh:mm a";
private static final String DATE_FORMAT_STRING = "MM/dd/yyyy";
private static final String COMPACT_DATE_FORMAT_STRING = "MM/dd/yy";
private static final String TIME_FORMAT_STRING = "h:mm";
private static final DateTimeFormatter DATE_TIME_FORMATTER =
@ -39,6 +40,8 @@ public class DateUtils {
DateTimeFormatter.ofPattern(DATE_FORMAT_STRING);
private static final DateTimeFormatter TIME_FORMATTER =
DateTimeFormatter.ofPattern(TIME_FORMAT_STRING);
private static final DateTimeFormatter COMPACT_DATE_FORMATTER =
DateTimeFormatter.ofPattern(COMPACT_DATE_FORMAT_STRING);
public static final long MS_PER_SEC = 1000;
public static final long MS_PER_MIN = MS_PER_SEC * 60;
@ -227,6 +230,16 @@ public class DateUtils {
return DATE_FORMATTER.format(toLocalDate(date));
}
/**
* Formats the given date into a compact date string (mm/dd/yy).
*
* @param date the date to format
* @return the date string
*/
public static String formatCompactDate(Date date) {
return COMPACT_DATE_FORMATTER.format(toLocalDate(date));
}
/**
* Formats the given date into a string that contains the date and time. This is in
* contrast to {@link #formatDate(Date)}, which only returns a date string.

View file

@ -15,11 +15,16 @@
*/
package help.screenshot;
import java.util.Set;
import java.util.stream.Collectors;
import javax.swing.JRadioButton;
import org.junit.Test;
import docking.DialogComponentProvider;
import docking.*;
import docking.action.DockingActionIf;
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.util.table.GhidraTable;
public class DataPluginScreenShots extends GhidraScreenShotGenerator {
@ -75,7 +80,10 @@ public class DataPluginScreenShots extends GhidraScreenShotGenerator {
@Test
public void testDefaultSettings() {
positionListingTop(0x40d3a4);
performAction("Default Data Settings", "DataPlugin", false);
ComponentProvider componentProvider = getProvider(CodeViewerProvider.class);
ActionContext actionContext = componentProvider.getActionContext(null);
DockingActionIf action = getAction("Default Settings", actionContext);
performAction(action, actionContext, false);
captureDialog();
}
@ -86,4 +94,29 @@ public class DataPluginScreenShots extends GhidraScreenShotGenerator {
captureDialog();
}
private DockingActionIf getAction(String name, ActionContext context) {
Set<DockingActionIf> actions = getDataPluginActions(context);
for (DockingActionIf element : actions) {
String actionName = element.getName();
int pos = actionName.indexOf(" (");
if (pos > 0) {
actionName = actionName.substring(0, pos);
}
if (actionName.equals(name)) {
return element;
}
}
return null;
}
private Set<DockingActionIf> getDataPluginActions(ActionContext context) {
Set<DockingActionIf> actions = getActionsByOwner(tool, "DataPlugin");
if (context == null) {
return actions;
}
// assumes returned set may be modified
return actions.stream()
.filter(a -> a.isValidContext(context))
.collect(Collectors.toSet());
}
}