GP-5326 - Decompiler - Added a quick field edit action to the Decompiler

This commit is contained in:
dragonmacher 2025-04-04 15:56:16 -04:00
parent b7a23cacd4
commit e9fb18faee
11 changed files with 729 additions and 154 deletions

View file

@ -972,19 +972,21 @@
<OL> <OL>
<LI>Right mouse click on a structure member in the Listing</LI> <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= action to bring the up the <A href=
"#Edit_Field_Dialog">Edit Field Dialog</A> </LI> "#Edit_Field_Dialog">Edit Field Dialog</A> </LI>
</OL> </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> <OL>
<LI>Place the cursor on the first line of the structure</LI> <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= <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> <LI>Edit the field name for the structure member</LI>
</OL> </OL>
@ -1369,7 +1371,7 @@
bringing up the entire structure or union editor. To edit a field, click anywhere on the line 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 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> 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> <P align="center"><IMG src="images/EditFieldDialog.png" alt=""> &nbsp;</P>
<UL> <UL>
<LI><B>Field Name</B>: The name of the structure or union field can be changed here.</LI> <LI><B>Field Name</B>: The name of the structure or union field can be changed here.</LI>

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

@ -25,17 +25,15 @@ import javax.swing.*;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import docking.DialogComponentProvider; 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.services.DataTypeManagerService;
import ghidra.app.util.datatype.DataTypeSelectionEditor;
import ghidra.framework.cmd.Command; import ghidra.framework.cmd.Command;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.util.*; import ghidra.util.*;
import ghidra.util.data.DataTypeParser.AllowedDataTypes;
import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.DuplicateNameException;
import ghidra.util.layout.PairLayout; import ghidra.util.layout.PairLayout;
@ -44,39 +42,53 @@ import ghidra.util.layout.PairLayout;
*/ */
public class EditDataFieldDialog extends DialogComponentProvider { 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 nameField;
private JTextField commentField; private JTextField commentField;
private JTextField dataTypeTextField; private DataTypeSelectionEditor dataTypeEditor;
private DataTypeComponent component;
private PluginTool tool;
private DataType newDataType;
private ProgramLocation programLocation;
private DataTypeManagerService dtmService;
private JCheckBox addressCheckBox; private JCheckBox addressCheckBox;
private JCheckBox dateCheckBox; private JCheckBox dateCheckBox;
private PluginTool tool;
private DataType newDataType;
private DataTypeManagerService dtmService;
private Composite composite;
private Address address;
private int ordinal;
private Program program;
/** /**
* Constructor * Constructor
* @param tool The tool hosting this dialog * @param tool The tool hosting this dialog
* @param dtmService the DataTypeManagerService used for choosing datatypes * @param dtmService the DataTypeManagerService used for choosing datatypes
* @param location the location of the field being edited * @param composite the composite being edited
* @param dataTypeComponent the component of the field being edited * @param program the program
* @param addDate selects the addDate checkbox * @param address the address of the data type component
* @param addAddress selects the addDate checkbox * @param ordinal the ordinal of the data type component inside of the composite
*/ */
public EditDataFieldDialog(PluginTool tool, DataTypeManagerService dtmService, public EditDataFieldDialog(PluginTool tool, DataTypeManagerService dtmService,
ProgramLocation location, DataTypeComponent dataTypeComponent, boolean addAddress, Composite composite, Program program, Address address, int ordinal) {
boolean addDate) {
super("Edit Field Dialog", true, true, true, false); super("Edit Field Dialog", true, true, true, false);
this.tool = tool; this.tool = tool;
this.dtmService = dtmService; this.dtmService = dtmService;
this.programLocation = location; this.composite = composite;
this.component = dataTypeComponent; this.program = program;
this.address = address;
this.ordinal = ordinal;
setTitle(generateTitle()); setTitle(generateTitle());
addWorkPanel(buildMainPanel()); addWorkPanel(buildMainPanel());
initializeFields(addAddress, addDate); initializeFields();
setFocusComponent(nameField); setFocusComponent(nameField);
setHelpLocation(new HelpLocation("DataPlugin", "Edit_Field_Dialog")); setHelpLocation(new HelpLocation("DataPlugin", "Edit_Field_Dialog"));
@ -87,9 +99,8 @@ public class EditDataFieldDialog extends DialogComponentProvider {
@Override @Override
public void dispose() { public void dispose() {
super.dispose(); super.dispose();
programLocation = null;
component = null;
tool = null; tool = null;
program = null;
} }
/** /**
@ -118,7 +129,7 @@ public class EditDataFieldDialog extends DialogComponentProvider {
/** /**
* Returns the text currently in the text field for the field comment. * 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() { public String getCommentText() {
return commentField.getText(); return commentField.getText();
@ -142,34 +153,16 @@ public class EditDataFieldDialog extends DialogComponentProvider {
updateDataTypeTextField(); updateDataTypeTextField();
} }
/** private void initializeFields() {
* Returns true if the Add Address checkbox is selected.
* @return true if the Add Address checkbox is selected
*/
public boolean isAddAddressSelected() {
return addressCheckBox.isSelected();
}
/** String name = getFieldName();
* Returns true if the Add Date checkbox is selected.
* @return true if the Add Address checkbox is selected
*/
public boolean isAddDateSelected() {
return dateCheckBox.isSelected();
}
private void initializeFields(boolean addAddress, boolean addDate) {
String name = component.getFieldName();
if (StringUtils.isBlank(name)) {
name = "";
}
nameField.setText(name); nameField.setText(name);
String comment = component.getComment();
if (comment == null) { String comment = getComment();
comment = "";
}
commentField.setText(comment); commentField.setText(comment);
dataTypeTextField.setText(component.getDataType().getDisplayName());
DataType dt = getComponentDataType();
dataTypeEditor.setCellEditorValue(dt);
if (addAddress) { if (addAddress) {
addressCheckBox.setSelected(true); addressCheckBox.setSelected(true);
@ -181,11 +174,40 @@ public class EditDataFieldDialog extends DialogComponentProvider {
} }
} }
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 @Override
protected void okCallback() { protected void okCallback() {
if (updateComponent()) { if (updateComponent()) {
close(); close();
programLocation = null;
} }
} }
@ -194,7 +216,7 @@ public class EditDataFieldDialog extends DialogComponentProvider {
return true; return true;
} }
Command<Program> cmd = new UpdateDataComponentCommand(); Command<Program> cmd = new UpdateDataComponentCommand();
if (!tool.execute(cmd, programLocation.getProgram())) { if (!tool.execute(cmd, program)) {
setStatusText(cmd.getStatusMsg(), MessageType.ERROR); setStatusText(cmd.getStatusMsg(), MessageType.ERROR);
return false; return false;
} }
@ -206,23 +228,31 @@ public class EditDataFieldDialog extends DialogComponentProvider {
} }
private boolean hasCommentChange() { private boolean hasCommentChange() {
String oldComment = getComment();
String newComment = getNewFieldComment(); String newComment = getNewFieldComment();
if (StringUtils.isBlank(newComment) && StringUtils.isBlank(component.getComment())) { if (StringUtils.isBlank(newComment) && StringUtils.isBlank(oldComment)) {
return false; 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() { boolean hasDataTypeChange() {
return newDataType != null && !newDataType.equals(component.getDataType()); DataType oldDt = getComponentDataType();
return newDataType != null && !newDataType.equals(oldDt);
} }
boolean hasNameChange() { boolean hasNameChange() {
String newName = getNewFieldName(); String newName = getNewFieldName();
String currentName = component.getFieldName(); String currentName = getFieldName();
if (currentName == null) {
currentName = component.getDefaultFieldName();
}
if (newName.equals(currentName)) { if (newName.equals(currentName)) {
return false; return false;
} }
@ -282,40 +312,34 @@ public class EditDataFieldDialog extends DialogComponentProvider {
private JPanel buildDataTypeChooserPanel() { private JPanel buildDataTypeChooserPanel() {
JPanel panel = new JPanel(new BorderLayout(10, 0)); JPanel panel = new JPanel(new BorderLayout(10, 0));
dataTypeTextField = new JTextField(); DataTypeManager dtm = composite.getDataTypeManager();
dataTypeTextField.setEditable(false); dataTypeEditor = new DataTypeSelectionEditor(dtm, dtmService, AllowedDataTypes.ALL);
BrowseButton browseButton = new BrowseButton();
browseButton.setToolTipText("Browse the Data Manager"); JComponent editorComponent = dataTypeEditor.getEditorComponent();
browseButton.addActionListener(e -> showDataTypeBrowser()); panel.add(editorComponent, BorderLayout.CENTER);
panel.add(dataTypeTextField, BorderLayout.CENTER);
panel.add(browseButton, BorderLayout.EAST);
return panel; return panel;
} }
private void showDataTypeBrowser() {
newDataType = dtmService.getDataType("");
updateDataTypeTextField();
}
private void updateDataTypeTextField() { private void updateDataTypeTextField() {
if (newDataType != null) { if (newDataType != null) {
dataTypeTextField.setText(newDataType.getDisplayName()); dataTypeEditor.setCellEditorValue(newDataType);
} }
else { else {
dataTypeTextField.setText(component.getDataType().getDisplayName()); DataType dt = getComponentDataType();
dataTypeEditor.setCellEditorValue(dt);
} }
} }
private String generateTitle() { private String generateTitle() {
DataType parent = component.getParent(); String compositeName = composite.getName();
String compositeName = parent.getName(); return "Edit " + compositeName + ", Field " + ordinal;
return "Edit " + compositeName + ", Field " + component.getOrdinal();
} }
private void dateCheckBoxChanged(ActionEvent e) { private void dateCheckBoxChanged(ActionEvent e) {
String today = getTodaysDate(); String today = getTodaysDate();
if (dateCheckBox.isSelected()) { addDate = dateCheckBox.isSelected();
if (addDate) {
addTextToComment(today); addTextToComment(today);
} }
else { else {
@ -324,12 +348,13 @@ public class EditDataFieldDialog extends DialogComponentProvider {
} }
private void addressCheckBoxChanged(ActionEvent e) { private void addressCheckBoxChanged(ActionEvent e) {
String address = getCurrentAddressString(); String addressString = getCurrentAddressString();
if (addressCheckBox.isSelected()) { addAddress = addressCheckBox.isSelected();
addTextToComment(address); if (addAddress) {
addTextToComment(addressString);
} }
else { else {
removeTextFromComment(address); removeTextFromComment(addressString);
} }
} }
@ -350,7 +375,7 @@ public class EditDataFieldDialog extends DialogComponentProvider {
} }
private String getCurrentAddressString() { private String getCurrentAddressString() {
return programLocation.getAddress().toString(); return address.toString();
} }
private void addTextToComment(String text) { private void addTextToComment(String text) {
@ -366,40 +391,127 @@ public class EditDataFieldDialog extends DialogComponentProvider {
} }
public String getDataTypeText() { public String getDataTypeText() {
return dataTypeTextField.getText(); return dataTypeEditor.getCellEditorValueAsText();
} }
private class UpdateDataComponentCommand implements Command<Program> { private class UpdateDataComponentCommand implements Command<Program> {
private String statusMessage = null; private String statusMessage = null;
@Override @Override
public boolean applyTo(Program program) { public boolean applyTo(Program p) {
if (component.isUndefined() || hasDataTypeChange()) {
DataType dt = getNewDataType(); maybeAdjustStructure();
Address address = programLocation.getAddress();
int[] path = programLocation.getComponentPath(); if (!updateDataType()) {
Command<Program> cmd = new CreateDataInStructureCmd(address, path, dt, false); return false;
if (!cmd.applyTo(program)) {
statusMessage = cmd.getStatusMsg();
return false;
}
component = DataTypeUtils.getDataTypeComponent(program, address, path);
} }
if (hasNameChange()) { if (!updateName()) {
try { return false;
component.setFieldName(getNewFieldName());
}
catch (DuplicateNameException e) {
statusMessage = "Duplicate field name";
return false;
}
} }
if (hasCommentChange()) { if (!updateComment()) {
component.setComment(getNewFieldComment()); return false;
} }
return true; 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 {
dtc.setFieldName(getNewFieldName());
return true;
}
catch (DuplicateNameException e) {
statusMessage = "Duplicate field name";
return false;
}
}
private boolean updateComment() {
DataTypeComponent dtc = composite.getComponent(ordinal);
if (hasCommentChange()) {
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 @Override
public String getStatusMsg() { public String getStatusMsg() {
return statusMessage; return statusMessage;

View file

@ -53,7 +53,7 @@ public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest {
program = buildProgram(); program = buildProgram();
env.open(program); env.open(program);
env.showTool(); env.showTool();
editFieldAction = getAction(plugin, "Edit Field"); editFieldAction = getAction(plugin, "Quick Edit Field");
Data dataAt = program.getListing().getDataAt(addr(0x100)); Data dataAt = program.getListing().getDataAt(addr(0x100));
structure = (Structure) dataAt.getDataType(); structure = (Structure) dataAt.getDataType();
codeBrowser.toggleOpen(dataAt); codeBrowser.toggleOpen(dataAt);
@ -121,6 +121,29 @@ public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest {
assertEquals("char", structure.getComponent(4).getDataType().getDisplayName()); 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 @Test
public void testEditUndefinedFieldName() { public void testEditUndefinedFieldName() {
goTo(0x101); goTo(0x101);
@ -228,7 +251,7 @@ public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest {
} }
private void pressOk() { private void pressOk() {
runSwing(() -> dialog.okCallback()); pressButtonByText(dialog, "OK");
} }
private String getNameText() { private String getNameText() {
@ -254,8 +277,4 @@ public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest {
private void setDataType(DataType dataType) { private void setDataType(DataType dataType) {
runSwing(() -> dialog.setDataType(dataType)); runSwing(() -> dialog.setDataType(dataType));
} }
private String getDialogStatusText() {
return runSwing(() -> dialog.getStatusText());
}
} }

View file

@ -961,6 +961,10 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
EditDataTypeAction editDataTypeAction = new EditDataTypeAction(); EditDataTypeAction editDataTypeAction = new EditDataTypeAction();
setGroupInfo(editDataTypeAction, variableGroup, subGroupPosition++); 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 // Listing action for Creating Structure on a Variable
// //
@ -1151,6 +1155,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
addLocalAction(decompilerCreateStructureAction); addLocalAction(decompilerCreateStructureAction);
tool.addAction(listingCreateStructureAction); tool.addAction(listingCreateStructureAction);
addLocalAction(editDataTypeAction); addLocalAction(editDataTypeAction);
addLocalAction(editFieldAction);
addLocalAction(specifyCProtoAction); addLocalAction(specifyCProtoAction);
addLocalAction(overrideSigAction); addLocalAction(overrideSigAction);
addLocalAction(editOverrideSigAction); 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

@ -4,9 +4,9 @@
* 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.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -29,7 +29,7 @@ import ghidra.program.model.listing.*;
import ghidra.program.model.pcode.*; import ghidra.program.model.pcode.*;
import ghidra.program.model.symbol.*; import ghidra.program.model.symbol.*;
public class EquateTest extends AbstractDecompilerTest { public class DecompilerEquateTest extends AbstractDecompilerTest {
private static class EquateNameForce extends SetEquateAction { private static class EquateNameForce extends SetEquateAction {

View file

@ -4,9 +4,9 @@
* 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.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -40,7 +40,7 @@ import ghidra.program.model.pcode.*;
import ghidra.program.model.symbol.SourceType; import ghidra.program.model.symbol.SourceType;
import ghidra.program.model.symbol.Symbol; import ghidra.program.model.symbol.Symbol;
public class HighSymbolTest extends AbstractDecompilerTest { public class DecompilerHighSymbolTest extends AbstractDecompilerTest {
@Override @Override
protected String getProgramName() { protected String getProgramName() {
return "Winmine__XP.exe.gzf"; return "Winmine__XP.exe.gzf";

View file

@ -4,9 +4,9 @@
* 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.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -40,7 +40,7 @@ import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
import ghidra.xml.XmlParseException; import ghidra.xml.XmlParseException;
public class SpecExtensionTest extends AbstractDecompilerTest { public class DecompilerSpecExtensionTest extends AbstractDecompilerTest {
@Override @Override
protected String getProgramName() { protected String getProgramName() {
return "Winmine__XP.exe.gzf"; 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; return fieldDt;
} }