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> <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,13 +1371,17 @@
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>
<LI><B>Comment</B>: The comment for the field can be entered or 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 <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> 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 <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 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 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 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,19 +95,15 @@ 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;
public DataPlugin(PluginTool tool) { public DataPlugin(PluginTool tool) {
@ -150,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)
@ -168,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)
@ -177,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)
@ -195,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)
@ -204,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;
@ -293,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;
@ -327,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);
} }
@ -342,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) {
@ -837,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();
@ -848,16 +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); return;
tool.showDialog(dialog);
} }
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; package ghidra.app.plugin.core.data;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.util.Date;
import javax.swing.*; 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.HelpLocation; import ghidra.util.data.DataTypeParser.AllowedDataTypes;
import ghidra.util.MessageType;
import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.DuplicateNameException;
import ghidra.util.layout.PairLayout; import ghidra.util.layout.PairLayout;
@ -42,30 +42,49 @@ 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 JCheckBox addressCheckBox;
private JCheckBox dateCheckBox;
private DataTypeComponent component;
private PluginTool tool; private PluginTool tool;
private DataType newDataType; private DataType newDataType;
private ProgramLocation programLocation;
private DataTypeManagerService dtmService; 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 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, 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); 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());
@ -80,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;
} }
/** /**
@ -111,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();
@ -136,20 +154,60 @@ public class EditDataFieldDialog extends DialogComponentProvider {
} }
private void initializeFields() { private void initializeFields() {
String name = component.getFieldName();
if (StringUtils.isBlank(name)) { String name = getFieldName();
name = "";
}
nameField.setText(name); 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 @Override
protected void okCallback() { protected void okCallback() {
if (updateComponent()) { if (updateComponent()) {
close(); close();
programLocation = null;
} }
} }
@ -158,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;
} }
@ -170,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;
} }
@ -202,6 +268,27 @@ public class EditDataFieldDialog extends DialogComponentProvider {
} }
private JPanel buildMainPanel() { 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)); JPanel panel = new JPanel(new PairLayout(10, 10));
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); 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(new JLabel("Field Name:", SwingConstants.LEFT));
panel.add(nameField); panel.add(nameField);
panel.add(new JLabel("Comment:", SwingConstants.LEFT));
panel.add(commentField);
panel.add(new JLabel("Datatype:", SwingConstants.LEFT)); panel.add(new JLabel("Datatype:", SwingConstants.LEFT));
panel.add(buildDataTypeChooserPanel()); panel.add(buildDataTypeChooserPanel());
panel.add(new JLabel("Comment:", SwingConstants.LEFT));
panel.add(commentField);
return panel; return panel;
} }
@ -225,72 +312,206 @@ 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) {
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() { 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

@ -17,6 +17,8 @@ package ghidra.app.plugin.core.data;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.util.Date;
import org.junit.*; import org.junit.*;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
@ -27,6 +29,7 @@ import ghidra.program.model.data.*;
import ghidra.program.model.listing.Data; import ghidra.program.model.listing.Data;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.test.*; import ghidra.test.*;
import ghidra.util.DateUtils;
public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest { public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env; private TestEnv env;
@ -50,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);
@ -118,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);
@ -162,6 +188,49 @@ public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest {
assertEquals("byte", structure.getComponent(1).getDataType().getDisplayName()); 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() { private boolean isDialogVisible() {
return runSwing(() -> dialog.isVisible()); return runSwing(() -> dialog.isVisible());
} }
@ -182,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() {
@ -208,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

@ -960,6 +960,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
// //
@ -1150,6 +1154,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;
} }

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.
@ -31,6 +31,7 @@ public class DateUtils {
/** Example: Oct 31, 2019 03:24 PM */ /** 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_TIME_FORMAT_STRING = "MMM dd, yyyy hh:mm a";
private static final String DATE_FORMAT_STRING = "MM/dd/yyyy"; 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 String TIME_FORMAT_STRING = "h:mm";
private static final DateTimeFormatter DATE_TIME_FORMATTER = private static final DateTimeFormatter DATE_TIME_FORMATTER =
@ -39,6 +40,8 @@ public class DateUtils {
DateTimeFormatter.ofPattern(DATE_FORMAT_STRING); DateTimeFormatter.ofPattern(DATE_FORMAT_STRING);
private static final DateTimeFormatter TIME_FORMATTER = private static final DateTimeFormatter TIME_FORMATTER =
DateTimeFormatter.ofPattern(TIME_FORMAT_STRING); 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_SEC = 1000;
public static final long MS_PER_MIN = MS_PER_SEC * 60; public static final long MS_PER_MIN = MS_PER_SEC * 60;
@ -227,6 +230,16 @@ public class DateUtils {
return DATE_FORMATTER.format(toLocalDate(date)); 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 * 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. * contrast to {@link #formatDate(Date)}, which only returns a date string.

View file

@ -15,11 +15,16 @@
*/ */
package help.screenshot; package help.screenshot;
import java.util.Set;
import java.util.stream.Collectors;
import javax.swing.JRadioButton; import javax.swing.JRadioButton;
import org.junit.Test; 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; import ghidra.util.table.GhidraTable;
public class DataPluginScreenShots extends GhidraScreenShotGenerator { public class DataPluginScreenShots extends GhidraScreenShotGenerator {
@ -75,7 +80,10 @@ public class DataPluginScreenShots extends GhidraScreenShotGenerator {
@Test @Test
public void testDefaultSettings() { public void testDefaultSettings() {
positionListingTop(0x40d3a4); 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(); captureDialog();
} }
@ -86,4 +94,29 @@ public class DataPluginScreenShots extends GhidraScreenShotGenerator {
captureDialog(); 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());
}
} }