From b7a23cacd4b1145a37bf47f3e2e454864d615a71 Mon Sep 17 00:00:00 2001
From: ghidragon <106987263+ghidragon@users.noreply.github.com>
Date: Fri, 21 Mar 2025 14:41:52 -0400
Subject: [PATCH 1/2] GP-5326 adding "Add Date" and "Add Address" to edit data
field dialog
---
.../main/help/help/topics/DataPlugin/Data.htm | 4 +
.../DataPlugin/images/EditFieldDialog.png | Bin 7673 -> 8352 bytes
.../app/plugin/core/data/DataPlugin.java | 6 +-
.../plugin/core/data/EditDataFieldDialog.java | 125 ++++++++++++++++--
.../plugin/core/data/EditFieldDialogTest.java | 46 +++++++
.../src/main/java/ghidra/util/DateUtils.java | 17 ++-
.../screenshot/DataPluginScreenShots.java | 37 +++++-
7 files changed, 222 insertions(+), 13 deletions(-)
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/Data.htm b/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/Data.htm
index 5d9100e00c..240fd2e5e5 100644
--- a/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/Data.htm
+++ b/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/Data.htm
@@ -1376,6 +1376,10 @@
Comment: The comment for the field can be entered or changed here.
DataType: 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.
+ Add Current Address: If selected, the current address where this field is edited
+ will be added to the datatype's field comment if not already there.
+ Add Today's Date: If selected, the current date will be added to the datatype's
+ field comment if not already there.
If a default field (a field with an undefined
datatype (??)) is named or given a comment, the datatype will be set to undefined1 if
no specific datatype is set. This is because undefined fields are not stored and therefore
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/images/EditFieldDialog.png b/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/images/EditFieldDialog.png
index 47f90f76f20d5b283a2ae536e9aa53ce2324af6b..e0123004a4416e286f63d46b336014163f28e4c4 100644
GIT binary patch
literal 8352
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 dialog.isVisible());
}
diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/DateUtils.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/DateUtils.java
index 942979a615..997a4fd906 100644
--- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/DateUtils.java
+++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/DateUtils.java
@@ -4,9 +4,9 @@
* 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.
@@ -31,6 +31,7 @@ public class DateUtils {
/** Example: Oct 31, 2019 03:24 PM */
private static final String DATE_TIME_FORMAT_STRING = "MMM dd, yyyy hh:mm a";
private static final String DATE_FORMAT_STRING = "MM/dd/yyyy";
+ private static final String COMPACT_DATE_FORMAT_STRING = "MM/dd/yy";
private static final String TIME_FORMAT_STRING = "h:mm";
private static final DateTimeFormatter DATE_TIME_FORMATTER =
@@ -39,6 +40,8 @@ public class DateUtils {
DateTimeFormatter.ofPattern(DATE_FORMAT_STRING);
private static final DateTimeFormatter TIME_FORMATTER =
DateTimeFormatter.ofPattern(TIME_FORMAT_STRING);
+ private static final DateTimeFormatter COMPACT_DATE_FORMATTER =
+ DateTimeFormatter.ofPattern(COMPACT_DATE_FORMAT_STRING);
public static final long MS_PER_SEC = 1000;
public static final long MS_PER_MIN = MS_PER_SEC * 60;
@@ -227,6 +230,16 @@ public class DateUtils {
return DATE_FORMATTER.format(toLocalDate(date));
}
+ /**
+ * Formats the given date into a compact date string (mm/dd/yy).
+ *
+ * @param date the date to format
+ * @return the date string
+ */
+ public static String formatCompactDate(Date date) {
+ return COMPACT_DATE_FORMATTER.format(toLocalDate(date));
+ }
+
/**
* Formats the given date into a string that contains the date and time. This is in
* contrast to {@link #formatDate(Date)}, which only returns a date string.
diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataPluginScreenShots.java
index 65d0427666..aa733c0e8c 100644
--- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataPluginScreenShots.java
+++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataPluginScreenShots.java
@@ -15,11 +15,16 @@
*/
package help.screenshot;
+import java.util.Set;
+import java.util.stream.Collectors;
+
import javax.swing.JRadioButton;
import org.junit.Test;
-import docking.DialogComponentProvider;
+import docking.*;
+import docking.action.DockingActionIf;
+import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
import ghidra.util.table.GhidraTable;
public class DataPluginScreenShots extends GhidraScreenShotGenerator {
@@ -75,7 +80,10 @@ public class DataPluginScreenShots extends GhidraScreenShotGenerator {
@Test
public void testDefaultSettings() {
positionListingTop(0x40d3a4);
- performAction("Default Data Settings", "DataPlugin", false);
+ ComponentProvider componentProvider = getProvider(CodeViewerProvider.class);
+ ActionContext actionContext = componentProvider.getActionContext(null);
+ DockingActionIf action = getAction("Default Settings", actionContext);
+ performAction(action, actionContext, false);
captureDialog();
}
@@ -86,4 +94,29 @@ public class DataPluginScreenShots extends GhidraScreenShotGenerator {
captureDialog();
}
+ private DockingActionIf getAction(String name, ActionContext context) {
+ Set 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 getDataPluginActions(ActionContext context) {
+ Set 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());
+ }
}
From e9fb18faee0afbe83a48c2e65b3ce3ce6342247b Mon Sep 17 00:00:00 2001
From: dragonmacher <48328597+dragonmacher@users.noreply.github.com>
Date: Fri, 4 Apr 2025 15:56:16 -0400
Subject: [PATCH 2/2] GP-5326 - Decompiler - Added a quick field edit action to
the Decompiler
---
.../main/help/help/topics/DataPlugin/Data.htm | 10 +-
.../app/plugin/core/data/DataPlugin.java | 66 ++--
.../plugin/core/data/EditDataFieldDialog.java | 318 ++++++++++++------
.../plugin/core/data/EditFieldDialogTest.java | 31 +-
.../core/decompile/DecompilerProvider.java | 5 +
.../decompile/actions/EditFieldAction.java | 131 ++++++++
.../DecompilerEditDataFieldTest.java | 292 ++++++++++++++++
...ateTest.java => DecompilerEquateTest.java} | 6 +-
...est.java => DecompilerHighSymbolTest.java} | 6 +-
....java => DecompilerSpecExtensionTest.java} | 6 +-
.../datatype/finder/DecompilerReference.java | 12 +
11 files changed, 729 insertions(+), 154 deletions(-)
create mode 100644 Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/EditFieldAction.java
create mode 100644 Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerEditDataFieldTest.java
rename Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/{EquateTest.java => DecompilerEquateTest.java} (99%)
rename Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/{HighSymbolTest.java => DecompilerHighSymbolTest.java} (99%)
rename Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/{SpecExtensionTest.java => DecompilerSpecExtensionTest.java} (99%)
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/Data.htm b/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/Data.htm
index 240fd2e5e5..43377f3973 100644
--- a/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/Data.htm
+++ b/Ghidra/Features/Base/src/main/help/help/topics/DataPlugin/Data.htm
@@ -972,19 +972,21 @@
- Right mouse click on a structure member in the Listing
- - Choose the Data
Edit Field
+ - Choose the Data
Quick Edit Field
action to bring the up the Edit Field Dialog
- The second way is more useful for changing the names of multiple members:
+ The second way is more useful for changing the names of multiple members. This method
+ will show the full Structure Editor:
+
- Place the cursor on the first line of the structure
- Press mouse-right over the structure and choose Data
Edit Data Type...
+ "help/shared/arrow.gif"> Edit Data Type
- Edit the field name for the structure member
@@ -1369,7 +1371,7 @@
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 Data
Edit Field from the popup context menu.
- Edit Field Dialog
+ Edit Field Dialog
- Field Name: The name of the structure or union field can be changed here.
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataPlugin.java
index aa577f5fb9..fd5037874b 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataPlugin.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/DataPlugin.java
@@ -85,7 +85,7 @@ public class DataPlugin extends Plugin implements DataService {
private static final String BASIC_DATA_GROUP = "BasicData";
private static final String DATA_MENU_POPUP_PATH = "Data";
private static final String[] EDIT_DATA_TYPE_POPUP_PATH =
- { DATA_MENU_POPUP_PATH, "Edit Data Type..." };
+ { DATA_MENU_POPUP_PATH, "Edit Data Type" };
private static final String[] DATA_SETTINGS_POPUP_PATH =
{ DATA_MENU_POPUP_PATH, "Settings..." };
private static final String[] DEFAULT_SETTINGS_POPUP_PATH =
@@ -95,22 +95,16 @@ public class DataPlugin extends Plugin implements DataService {
{ DATA_MENU_POPUP_PATH, "Choose Data Type..." };
private DataTypeManagerService dtmService;
+ private DataTypeManagerChangeListenerAdapter adapter;
private DataAction pointerAction;
private DataAction recentlyUsedAction;
private DockingAction editDataTypeAction;
private CreateStructureAction createStructureAction;
private CreateArrayAction createArrayAction;
-
- private List favoriteActions = new ArrayList<>();
-
private ChooseDataTypeAction chooseDataTypeAction;
-
- private DataTypeManagerChangeListenerAdapter adapter;
-
+ private List favoriteActions = new ArrayList<>();
private SwingUpdateManager favoritesUpdateManager;
- private boolean addAddress;
- private boolean addDate;
public DataPlugin(PluginTool tool) {
super(tool);
@@ -152,16 +146,20 @@ public class DataPlugin extends Plugin implements DataService {
pointerAction = new PointerDataAction(this);
tool.addAction(pointerAction);
- new ActionBuilder("Edit Field", getName())
- .popupMenuPath("Data", "Edit Field")
+ new ActionBuilder("Quick Edit Field", getName())
+ .helpLocation(new HelpLocation("DataPlugin", "Quick_Edit_Field"))
+ .popupMenuPath("Data", "Quick Edit Field...")
+ .popupMenuGroup("BasicData")
.keyBinding("ctrl shift E")
+ .sharedKeyBinding()
.withContext(ListingActionContext.class)
.enabledWhen(this::canEditField)
.onAction(this::editField)
.buildAndInstall(tool);
// Data instance settings action based upon data selection in listing
- new ActionBuilder("Data Settings", getName()).sharedKeyBinding()
+ new ActionBuilder("Data Settings", getName())
+ .sharedKeyBinding()
.popupMenuPath(DATA_SETTINGS_POPUP_PATH)
.popupMenuGroup("Settings")
.withContext(ListingActionContext.class)
@@ -170,7 +168,8 @@ public class DataPlugin extends Plugin implements DataService {
.buildAndInstall(tool);
// Default settings action based upon data selection in listing
- new ActionBuilder("Default Settings", getName()).sharedKeyBinding()
+ new ActionBuilder("Default Settings", getName())
+ .sharedKeyBinding()
.popupMenuPath(DEFAULT_SETTINGS_POPUP_PATH)
.popupMenuGroup("Settings")
.withContext(ListingActionContext.class)
@@ -179,7 +178,8 @@ public class DataPlugin extends Plugin implements DataService {
.buildAndInstall(tool);
// Default settings action for selected datatypes from datatype manager
- new ActionBuilder("Default Settings", getName()).sharedKeyBinding()
+ new ActionBuilder("Default Settings", getName())
+ .sharedKeyBinding()
.popupMenuPath(DATATYPE_SETTINGS_POPUP_PATH)
.popupMenuGroup("Settings")
.withContext(DataTypesActionContext.class)
@@ -197,7 +197,8 @@ public class DataPlugin extends Plugin implements DataService {
.buildAndInstall(tool);
// Default settings action for composite editor components (stand-alone archive)
- new ActionBuilder("Default Settings", getName()).sharedKeyBinding()
+ new ActionBuilder("Default Settings", getName())
+ .sharedKeyBinding()
.popupMenuPath(DATATYPE_SETTINGS_POPUP_PATH)
.popupMenuGroup("Settings")
.withContext(ComponentStandAloneActionContext.class)
@@ -206,14 +207,15 @@ public class DataPlugin extends Plugin implements DataService {
.buildAndInstall(tool);
editDataTypeAction =
- new ActionBuilder("Edit Data Type", getName()).popupMenuPath(EDIT_DATA_TYPE_POPUP_PATH)
+ new ActionBuilder("Edit Data Type", getName())
+ .popupMenuPath(EDIT_DATA_TYPE_POPUP_PATH)
.popupMenuGroup("BasicData")
.withContext(ListingActionContext.class)
.enabledWhen(c -> {
DataType editableDt = getEditableDataTypeFromContext(c);
if (editableDt != null) {
- editDataTypeAction
- .setHelpLocation(dtmService.getEditorHelpLocation(editableDt));
+ HelpLocation helps = dtmService.getEditorHelpLocation(editableDt);
+ editDataTypeAction.setHelpLocation(helps);
return true;
}
return false;
@@ -295,7 +297,6 @@ public class DataPlugin extends Plugin implements DataService {
@Override
public boolean createData(DataType dt, ListingActionContext context, boolean stackPointers,
boolean enableConflictHandling) {
-// TODO: conflict handler (i.e., removal of other conflicting data not yet supported)
ProgramLocation location = context.getLocation();
if (!(location instanceof CodeUnitLocation)) {
return false;
@@ -329,7 +330,7 @@ public class DataPlugin extends Plugin implements DataService {
ProgramLocation location) {
Address start = location.getAddress();
int[] startPath = location.getComponentPath();
- Command cmd;
+ Command cmd;
if (startPath != null && startPath.length != 0) {
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,
ProgramSelection selection) {
- BackgroundCommand cmd;
+ BackgroundCommand cmd;
Address start = selection.getMinAddress();
InteriorSelection interSel = selection.getInteriorSelection();
if (interSel != null) {
@@ -839,10 +840,6 @@ public class DataPlugin extends Plugin implements DataService {
return true;
}
- public DataType pickDataType() {
- return dtmService.getDataType("");
- }
-
private boolean canEditField(ListingActionContext context) {
ProgramLocation location = context.getLocation();
int[] componentPath = location.getComponentPath();
@@ -850,18 +847,23 @@ public class DataPlugin extends Plugin implements DataService {
}
private void editField(ListingActionContext context) {
+
Program program = context.getProgram();
ProgramLocation location = context.getLocation();
Address address = location.getAddress();
int[] path = location.getComponentPath();
- DataTypeComponent component = DataTypeUtils.getDataTypeComponent(program, address, path);
- if (component != null) {
- EditDataFieldDialog dialog =
- new EditDataFieldDialog(tool, dtmService, location, component, addAddress, addDate);
- tool.showDialog(dialog);
- addAddress = dialog.isAddAddressSelected();
- addDate = dialog.isAddDateSelected();
+
+ DataTypeComponent dtc = DataTypeUtils.getDataTypeComponent(program, address, path);
+ if (dtc == null) {
+ return;
}
+
+ DataType parent = dtc.getParent();
+ Composite composite = (Composite) parent;
+ int ordinal = dtc.getOrdinal();
+ EditDataFieldDialog dialog =
+ new EditDataFieldDialog(tool, dtmService, composite, program, address, ordinal);
+ tool.showDialog(dialog);
}
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/EditDataFieldDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/EditDataFieldDialog.java
index 9de0f6046e..ef1a96bd9a 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/EditDataFieldDialog.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/data/EditDataFieldDialog.java
@@ -25,17 +25,15 @@ import javax.swing.*;
import org.apache.commons.lang3.StringUtils;
import docking.DialogComponentProvider;
-import docking.widgets.button.BrowseButton;
-import ghidra.app.cmd.data.CreateDataInStructureCmd;
-import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
import ghidra.app.services.DataTypeManagerService;
+import ghidra.app.util.datatype.DataTypeSelectionEditor;
import ghidra.framework.cmd.Command;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Program;
-import ghidra.program.util.ProgramLocation;
import ghidra.util.*;
+import ghidra.util.data.DataTypeParser.AllowedDataTypes;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.layout.PairLayout;
@@ -44,39 +42,53 @@ import ghidra.util.layout.PairLayout;
*/
public class EditDataFieldDialog extends DialogComponentProvider {
+ // These two fields are static so that the user's last choice is remembered across dialog uses.
+ // The preferred way to do this would be to have a plugin manage this state and have that plugin
+ // make the dialog available as a service. At the time of writing, this solution seemed good
+ // enough. The downside of this is that these values are not saved across uses of Ghidra.
+ private static boolean addAddress;
+ private static boolean addDate;
+
private JTextField nameField;
private JTextField commentField;
- private JTextField dataTypeTextField;
-
- private DataTypeComponent component;
- private PluginTool tool;
- private DataType newDataType;
- private ProgramLocation programLocation;
- private DataTypeManagerService dtmService;
+ private DataTypeSelectionEditor dataTypeEditor;
private JCheckBox addressCheckBox;
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 dtmService the DataTypeManagerService used for choosing datatypes
- * @param location the location of the field being edited
- * @param dataTypeComponent the component of the field being edited
- * @param addDate selects the addDate checkbox
- * @param addAddress selects the addDate checkbox
+ * @param composite the composite being edited
+ * @param program the program
+ * @param address the address of the data type component
+ * @param ordinal the ordinal of the data type component inside of the composite
*/
public EditDataFieldDialog(PluginTool tool, DataTypeManagerService dtmService,
- ProgramLocation location, DataTypeComponent dataTypeComponent, boolean addAddress,
- boolean addDate) {
+ Composite composite, Program program, Address address, int ordinal) {
+
super("Edit Field Dialog", true, true, true, false);
+
this.tool = tool;
this.dtmService = dtmService;
- this.programLocation = location;
- this.component = dataTypeComponent;
+ this.composite = composite;
+ this.program = program;
+ this.address = address;
+ this.ordinal = ordinal;
+
setTitle(generateTitle());
addWorkPanel(buildMainPanel());
- initializeFields(addAddress, addDate);
+ initializeFields();
setFocusComponent(nameField);
setHelpLocation(new HelpLocation("DataPlugin", "Edit_Field_Dialog"));
@@ -87,9 +99,8 @@ public class EditDataFieldDialog extends DialogComponentProvider {
@Override
public void dispose() {
super.dispose();
- programLocation = null;
- component = 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.
- * @return the text currently in the text field for the field commment
+ * @return the text currently in the text field for the field comment
*/
public String getCommentText() {
return commentField.getText();
@@ -142,34 +153,16 @@ public class EditDataFieldDialog extends DialogComponentProvider {
updateDataTypeTextField();
}
- /**
- * Returns true if the Add Address checkbox is selected.
- * @return true if the Add Address checkbox is selected
- */
- public boolean isAddAddressSelected() {
- return addressCheckBox.isSelected();
- }
+ private void initializeFields() {
- /**
- * 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 = "";
- }
+ String name = getFieldName();
nameField.setText(name);
- String comment = component.getComment();
- if (comment == null) {
- comment = "";
- }
+
+ String comment = getComment();
commentField.setText(comment);
- dataTypeTextField.setText(component.getDataType().getDisplayName());
+
+ DataType dt = getComponentDataType();
+ dataTypeEditor.setCellEditorValue(dt);
if (addAddress) {
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
protected void okCallback() {
if (updateComponent()) {
close();
- programLocation = null;
}
}
@@ -194,7 +216,7 @@ public class EditDataFieldDialog extends DialogComponentProvider {
return true;
}
Command cmd = new UpdateDataComponentCommand();
- if (!tool.execute(cmd, programLocation.getProgram())) {
+ if (!tool.execute(cmd, program)) {
setStatusText(cmd.getStatusMsg(), MessageType.ERROR);
return false;
}
@@ -206,23 +228,31 @@ public class EditDataFieldDialog extends DialogComponentProvider {
}
private boolean hasCommentChange() {
+ String oldComment = getComment();
String newComment = getNewFieldComment();
- if (StringUtils.isBlank(newComment) && StringUtils.isBlank(component.getComment())) {
+ if (StringUtils.isBlank(newComment) && StringUtils.isBlank(oldComment)) {
return false;
}
- return !newComment.equals(component.getComment());
+ return !newComment.equals(oldComment);
+ }
+
+ private DataType getComponentDataType() {
+ if (hasNoDataTypeComponent()) {
+ return DataType.DEFAULT;
+ }
+
+ DataTypeComponent dtc = composite.getComponent(ordinal);
+ return dtc.getDataType();
}
boolean hasDataTypeChange() {
- return newDataType != null && !newDataType.equals(component.getDataType());
+ DataType oldDt = getComponentDataType();
+ return newDataType != null && !newDataType.equals(oldDt);
}
boolean hasNameChange() {
String newName = getNewFieldName();
- String currentName = component.getFieldName();
- if (currentName == null) {
- currentName = component.getDefaultFieldName();
- }
+ String currentName = getFieldName();
if (newName.equals(currentName)) {
return false;
}
@@ -282,40 +312,34 @@ public class EditDataFieldDialog extends DialogComponentProvider {
private JPanel buildDataTypeChooserPanel() {
JPanel panel = new JPanel(new BorderLayout(10, 0));
- dataTypeTextField = new JTextField();
- dataTypeTextField.setEditable(false);
- BrowseButton browseButton = new BrowseButton();
- browseButton.setToolTipText("Browse the Data Manager");
- browseButton.addActionListener(e -> showDataTypeBrowser());
+ DataTypeManager dtm = composite.getDataTypeManager();
+ dataTypeEditor = new DataTypeSelectionEditor(dtm, dtmService, AllowedDataTypes.ALL);
+
+ JComponent editorComponent = dataTypeEditor.getEditorComponent();
+ panel.add(editorComponent, BorderLayout.CENTER);
- panel.add(dataTypeTextField, BorderLayout.CENTER);
- panel.add(browseButton, BorderLayout.EAST);
return panel;
}
- private void showDataTypeBrowser() {
- newDataType = dtmService.getDataType("");
- updateDataTypeTextField();
- }
-
private void updateDataTypeTextField() {
if (newDataType != null) {
- dataTypeTextField.setText(newDataType.getDisplayName());
+ dataTypeEditor.setCellEditorValue(newDataType);
}
else {
- dataTypeTextField.setText(component.getDataType().getDisplayName());
+ DataType dt = getComponentDataType();
+ dataTypeEditor.setCellEditorValue(dt);
}
}
private String generateTitle() {
- DataType parent = component.getParent();
- String compositeName = parent.getName();
- return "Edit " + compositeName + ", Field " + component.getOrdinal();
+ String compositeName = composite.getName();
+ return "Edit " + compositeName + ", Field " + ordinal;
}
private void dateCheckBoxChanged(ActionEvent e) {
String today = getTodaysDate();
- if (dateCheckBox.isSelected()) {
+ addDate = dateCheckBox.isSelected();
+ if (addDate) {
addTextToComment(today);
}
else {
@@ -324,12 +348,13 @@ public class EditDataFieldDialog extends DialogComponentProvider {
}
private void addressCheckBoxChanged(ActionEvent e) {
- String address = getCurrentAddressString();
- if (addressCheckBox.isSelected()) {
- addTextToComment(address);
+ String addressString = getCurrentAddressString();
+ addAddress = addressCheckBox.isSelected();
+ if (addAddress) {
+ addTextToComment(addressString);
}
else {
- removeTextFromComment(address);
+ removeTextFromComment(addressString);
}
}
@@ -350,7 +375,7 @@ public class EditDataFieldDialog extends DialogComponentProvider {
}
private String getCurrentAddressString() {
- return programLocation.getAddress().toString();
+ return address.toString();
}
private void addTextToComment(String text) {
@@ -366,40 +391,127 @@ public class EditDataFieldDialog extends DialogComponentProvider {
}
public String getDataTypeText() {
- return dataTypeTextField.getText();
+ return dataTypeEditor.getCellEditorValueAsText();
}
private class UpdateDataComponentCommand implements Command {
private String statusMessage = null;
@Override
- public boolean applyTo(Program program) {
- if (component.isUndefined() || hasDataTypeChange()) {
- DataType dt = getNewDataType();
- Address address = programLocation.getAddress();
- int[] path = programLocation.getComponentPath();
- Command cmd = new CreateDataInStructureCmd(address, path, dt, false);
- if (!cmd.applyTo(program)) {
- statusMessage = cmd.getStatusMsg();
- return false;
- }
- component = DataTypeUtils.getDataTypeComponent(program, address, path);
+ public boolean applyTo(Program p) {
+
+ maybeAdjustStructure();
+
+ if (!updateDataType()) {
+ return false;
}
- if (hasNameChange()) {
- try {
- component.setFieldName(getNewFieldName());
- }
- catch (DuplicateNameException e) {
- statusMessage = "Duplicate field name";
- return false;
- }
+ if (!updateName()) {
+ return false;
}
- if (hasCommentChange()) {
- component.setComment(getNewFieldComment());
+ if (!updateComment()) {
+ return false;
}
return true;
}
+ private void maybeAdjustStructure() {
+
+ if (!(composite instanceof Structure struct)) {
+ return;
+ }
+
+ int n = composite.getNumComponents();
+ if (ordinal >= n) {
+ int amount = ordinal - n;
+ struct.growStructure(amount);
+ }
+
+ DataTypeComponent dtc = composite.getComponent(ordinal);
+ if (dtc.getDataType() == DataType.DEFAULT) { // remove placeholder type
+ DataType newtype = new Undefined1DataType();
+ struct.replaceAtOffset(dtc.getOffset(), newtype, 1, "tempName",
+ "Created by Edit Data Field action");
+ }
+ }
+
+ private boolean updateName() {
+ if (!hasNameChange()) {
+ return true;
+ }
+
+ DataTypeComponent dtc = composite.getComponent(ordinal);
+ try {
+ 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
public String getStatusMsg() {
return statusMessage;
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/EditFieldDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/EditFieldDialogTest.java
index a450111d7a..3722388c42 100644
--- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/EditFieldDialogTest.java
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/data/EditFieldDialogTest.java
@@ -53,7 +53,7 @@ public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest {
program = buildProgram();
env.open(program);
env.showTool();
- editFieldAction = getAction(plugin, "Edit Field");
+ editFieldAction = getAction(plugin, "Quick Edit Field");
Data dataAt = program.getListing().getDataAt(addr(0x100));
structure = (Structure) dataAt.getDataType();
codeBrowser.toggleOpen(dataAt);
@@ -121,6 +121,29 @@ public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest {
assertEquals("char", structure.getComponent(4).getDataType().getDisplayName());
}
+ @Test
+ public void testEditDefinedFieldDataTypeAndNameAndComment() {
+ goTo(0x104);
+ showFieldEditDialog();
+ DataTypeComponent dtc = structure.getComponent(4);
+ assertEquals("word", dtc.getDataType().getDisplayName());
+ assertEquals("word", getDataTypeText());
+
+ setDataType(new CharDataType());
+ setNameText("TestName");
+ setCommentText("Flux capacitor relay");
+
+ pressOk();
+
+ waitForTasks();
+ assertFalse(isDialogVisible());
+
+ dtc = structure.getComponent(4);
+ assertEquals("char", dtc.getDataType().getDisplayName());
+ assertEquals("TestName", dtc.getFieldName());
+ assertEquals("Flux capacitor relay", dtc.getComment());
+ }
+
@Test
public void testEditUndefinedFieldName() {
goTo(0x101);
@@ -228,7 +251,7 @@ public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest {
}
private void pressOk() {
- runSwing(() -> dialog.okCallback());
+ pressButtonByText(dialog, "OK");
}
private String getNameText() {
@@ -254,8 +277,4 @@ public class EditFieldDialogTest extends AbstractGhidraHeadedIntegrationTest {
private void setDataType(DataType dataType) {
runSwing(() -> dialog.setDataType(dataType));
}
-
- private String getDialogStatusText() {
- return runSwing(() -> dialog.getStatusText());
- }
}
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java
index a65db5a018..6a892e41ff 100644
--- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/DecompilerProvider.java
@@ -961,6 +961,10 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
EditDataTypeAction editDataTypeAction = new EditDataTypeAction();
setGroupInfo(editDataTypeAction, variableGroup, subGroupPosition++);
+ // shows the quick editor dialog
+ EditFieldAction editFieldAction = new EditFieldAction();
+ setGroupInfo(editFieldAction, variableGroup, subGroupPosition++);
+
//
// Listing action for Creating Structure on a Variable
//
@@ -1151,6 +1155,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
addLocalAction(decompilerCreateStructureAction);
tool.addAction(listingCreateStructureAction);
addLocalAction(editDataTypeAction);
+ addLocalAction(editFieldAction);
addLocalAction(specifyCProtoAction);
addLocalAction(overrideSigAction);
addLocalAction(editOverrideSigAction);
diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/EditFieldAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/EditFieldAction.java
new file mode 100644
index 0000000000..a59aeca48b
--- /dev/null
+++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/EditFieldAction.java
@@ -0,0 +1,131 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.plugin.core.decompile.actions;
+
+import docking.ActionContext;
+import docking.action.*;
+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);
+ }
+
+}
diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerEditDataFieldTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerEditDataFieldTest.java
new file mode 100644
index 0000000000..050ba0c1a1
--- /dev/null
+++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerEditDataFieldTest.java
@@ -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));
+ }
+
+}
diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/EquateTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerEquateTest.java
similarity index 99%
rename from Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/EquateTest.java
rename to Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerEquateTest.java
index 2667f363a3..3ea8d60fbb 100644
--- a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/EquateTest.java
+++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerEquateTest.java
@@ -4,9 +4,9 @@
* 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.
@@ -29,7 +29,7 @@ import ghidra.program.model.listing.*;
import ghidra.program.model.pcode.*;
import ghidra.program.model.symbol.*;
-public class EquateTest extends AbstractDecompilerTest {
+public class DecompilerEquateTest extends AbstractDecompilerTest {
private static class EquateNameForce extends SetEquateAction {
diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/HighSymbolTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerHighSymbolTest.java
similarity index 99%
rename from Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/HighSymbolTest.java
rename to Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerHighSymbolTest.java
index 85fe9bb86b..21a0bd474e 100644
--- a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/HighSymbolTest.java
+++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerHighSymbolTest.java
@@ -4,9 +4,9 @@
* 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.
@@ -40,7 +40,7 @@ import ghidra.program.model.pcode.*;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.model.symbol.Symbol;
-public class HighSymbolTest extends AbstractDecompilerTest {
+public class DecompilerHighSymbolTest extends AbstractDecompilerTest {
@Override
protected String getProgramName() {
return "Winmine__XP.exe.gzf";
diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/SpecExtensionTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerSpecExtensionTest.java
similarity index 99%
rename from Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/SpecExtensionTest.java
rename to Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerSpecExtensionTest.java
index 9abcabfe0d..40a5b1c0ee 100644
--- a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/SpecExtensionTest.java
+++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerSpecExtensionTest.java
@@ -4,9 +4,9 @@
* 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.
@@ -40,7 +40,7 @@ import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
import ghidra.xml.XmlParseException;
-public class SpecExtensionTest extends AbstractDecompilerTest {
+public class DecompilerSpecExtensionTest extends AbstractDecompilerTest {
@Override
protected String getProgramName() {
return "Winmine__XP.exe.gzf";
diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/extension/datatype/finder/DecompilerReference.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/extension/datatype/finder/DecompilerReference.java
index 21649c4d7d..fc383c6f8f 100644
--- a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/extension/datatype/finder/DecompilerReference.java
+++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/extension/datatype/finder/DecompilerReference.java
@@ -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;
}