From eaf804fc330bf61d81eaa4efc4ad13778dac7167 Mon Sep 17 00:00:00 2001
From: ghidra007
Date: Fri, 24 Mar 2023 21:35:09 +0000
Subject: [PATCH] GP-3204 Updated CreateEnumFromSelection action to handle
duplicate named entries.
---
.../data_type_manager_description.htm | 6 +-
.../CreateEnumFromSelectionAction.java | 131 ++++++++++++++----
.../datamgr/CreateEnumFromSelectionTest.java | 114 +++++++++++++++
3 files changed, 223 insertions(+), 28 deletions(-)
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_description.htm b/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_description.htm
index fb37bdc85c..6ac79334c8 100644
--- a/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_description.htm
+++ b/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_description.htm
@@ -854,7 +854,11 @@
or you will be prompted to enter a unique name. The resulting enum will contain a
combination of all names and values from the selected enums. NOTE: If more than one of
the same value is contained in the enums, they will all be added to the new enum.
- However, only the first one entered will be applied when this enum is used.
+ However, only the first one entered will be applied when this enum is used. If more than one
+ entry with the same name is contained in the selected enums, any extras with the same value will
+ be ignored and any with different value will be given a new name consisting of original name appended
+ with as many underscores needed to make it unique. A comment will be added so users know which ones
+ had names modified to allow the addition of the entry.
Deleting a Data Type
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/CreateEnumFromSelectionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/CreateEnumFromSelectionAction.java
index 11da0db296..43cf2e9c03 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/CreateEnumFromSelectionAction.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/CreateEnumFromSelectionAction.java
@@ -15,6 +15,9 @@
*/
package ghidra.app.plugin.core.datamgr.actions;
+import java.util.Arrays;
+import java.util.List;
+
import javax.swing.SwingUtilities;
import javax.swing.tree.TreePath;
@@ -107,9 +110,8 @@ public class CreateEnumFromSelectionAction extends DockingAction {
DataType dt = myDataTypeManager.getDataType(category.getCategoryPath(), newName);
while (dt != null) {
- InputDialog dupInputDialog =
- new InputDialog("Duplicate ENUM Name",
- "Please enter a unique name for the new ENUM: ");
+ InputDialog dupInputDialog = new InputDialog("Duplicate ENUM Name",
+ "Please enter a unique name for the new ENUM: ");
tool = plugin.getTool();
tool.showDialog(dupInputDialog);
@@ -119,7 +121,7 @@ public class CreateEnumFromSelectionAction extends DockingAction {
newName = dupInputDialog.getValue();
dt = myDataTypeManager.getDataType(category.getCategoryPath(), newName);
}
- createNewEnum(category, enumArray, newName);
+ createMergedEnum(category, enumArray, newName);
// select new node in tree. Must use invoke later to give the tree a chance to add the
// the new node to the tree.
@@ -130,8 +132,8 @@ public class CreateEnumFromSelectionAction extends DockingAction {
@Override
public void run() {
GTreeNode rootNode = gTree.getViewRoot();
- gTree.setSelectedNodeByNamePath(new String[] { rootNode.getName(), parentNodeName,
- newNodeName });
+ gTree.setSelectedNodeByNamePath(
+ new String[] { rootNode.getName(), parentNodeName, newNodeName });
}
});
}
@@ -168,34 +170,109 @@ public class CreateEnumFromSelectionAction extends DockingAction {
return false;
}
- public void createNewEnum(Category category, Enum[] enumArray, String newName) {
+ private void createMergedEnum(Category category, Enum[] enumsToMerge, String mergedEnumName) {
- // figure out size of the new enum using the max size of the selected enums
+ int maxEnumSize = computeNewEnumSize(enumsToMerge);
+
+ SourceArchive sourceArchive = category.getDataTypeManager().getLocalSourceArchive();
+ Enum mergedEnum = new EnumDataType(category.getCategoryPath(), mergedEnumName, maxEnumSize,
+ category.getDataTypeManager());
+
+ mergedEnum.setSourceArchive(sourceArchive);
+
+ for (Enum element : enumsToMerge) {
+ mergeEnum(mergedEnum, element);
+ }
+
+ addEnumDataType(category, mergedEnum);
+
+ }
+
+ private void addEnumDataType(Category category, Enum mergedEnum) {
+ int id = category.getDataTypeManager().startTransaction("Create New Enum Data Type");
+ category.getDataTypeManager()
+ .addDataType(mergedEnum, DataTypeConflictHandler.REPLACE_HANDLER);
+ category.getDataTypeManager().endTransaction(id, true);
+ }
+
+ private void mergeEnum(Enum mergedEnum, Enum enumToMerge) {
+
+ for (String name : enumToMerge.getNames()) {
+
+ long valueToAdd = enumToMerge.getValue(name);
+ String comment = "";
+
+ if (isDuplicateEntry(mergedEnum, enumToMerge, name)) {
+ continue;
+ }
+
+ if (isConflictingEntry(mergedEnum, enumToMerge, name)) {
+ name = createDeconflictedName(mergedEnum, name);
+
+ comment = "NOTE: Duplicate name with different value";
+ Msg.debug(this,
+ "Merged Enum " + mergedEnum.getName() +
+ " has at least one duplicate named entry with different value than " +
+ "original. Underscore(s) have been appended to name allow addition.");
+ }
+
+ mergedEnum.add(name, valueToAdd, comment);
+
+ }
+ }
+
+ private String createDeconflictedName(Enum enumm, String name) {
+
+ List existingNames = Arrays.asList(enumm.getNames());
+ while (existingNames.contains(name)) {
+ name = name + "_";
+ }
+ return name;
+ }
+
+ private boolean isDuplicateEntry(Enum mergedEnum, Enum enumToMerge, String name) {
+
+ List existingNames = Arrays.asList(mergedEnum.getNames());
+
+ if (!existingNames.contains(name)) {
+ return false;
+ }
+
+ long valueToAdd = enumToMerge.getValue(name);
+ long existingValue = mergedEnum.getValue(name);
+ if (valueToAdd == existingValue) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private boolean isConflictingEntry(Enum mergedEnum, Enum enumToMerge, String name) {
+
+ List existingNames = Arrays.asList(mergedEnum.getNames());
+
+ if (!existingNames.contains(name)) {
+ return false;
+ }
+
+ long valueToAdd = enumToMerge.getValue(name);
+ long existingValue = mergedEnum.getValue(name);
+ if (valueToAdd == existingValue) {
+ return false;
+ }
+
+ return true;
+ }
+
+ // figure out size in bytes of the new enum using the max size of the selected enums
+ private int computeNewEnumSize(Enum[] enumArray) {
int maxEnumSize = 1;
for (Enum element : enumArray) {
if (maxEnumSize < element.getLength()) {
maxEnumSize = element.getLength();
}
}
- SourceArchive sourceArchive = category.getDataTypeManager().getLocalSourceArchive();
- Enum dataType =
- new EnumDataType(category.getCategoryPath(), newName, maxEnumSize,
- category.getDataTypeManager());
-
- for (Enum element : enumArray) {
- String[] names = element.getNames();
- for (String name : names) {
- dataType.add(name, element.getValue(name));
-
- }
- }
-
- dataType.setSourceArchive(sourceArchive);
- int id = category.getDataTypeManager().startTransaction("Create New Enum Data Type");
- category.getDataTypeManager()
- .addDataType(dataType, DataTypeConflictHandler.REPLACE_HANDLER);
- category.getDataTypeManager().endTransaction(id, true);
-
+ return maxEnumSize;
}
}
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/CreateEnumFromSelectionTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/CreateEnumFromSelectionTest.java
index ee20012c52..f2dd1a3695 100644
--- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/CreateEnumFromSelectionTest.java
+++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/CreateEnumFromSelectionTest.java
@@ -105,6 +105,8 @@ public class CreateEnumFromSelectionTest extends AbstractGhidraHeadedIntegration
@Test
public void testCreateEnumFromSelection() throws Exception {
+
+ //NOTE: This test tests basic create enum from selection
// make two test enums in the program name folder
@@ -196,6 +198,9 @@ public class CreateEnumFromSelectionTest extends AbstractGhidraHeadedIntegration
@Test
public void testCreateEnumFromSelectionDupe() throws Exception {
+
+ //NOTE: This test tests basic create enum from selection and
+ // tries to make second new enum with duplicate name and verifies that it won't
// make two test enums in the program name folder
@@ -281,6 +286,115 @@ public class CreateEnumFromSelectionTest extends AbstractGhidraHeadedIntegration
assertNotNull(newEnumNode);
}
+
+ @Test
+ public void testCreateEnumFromSelectionDupeEntryNameOrValue() throws Exception {
+
+ //NOTE: This test tests handing of duplicate entry names and values
+ // duplicate value diff name - add both
+ // duplicate name and value - just add one entry with that combo
+ // duplicate name diff value - add second name with _ appended and a comment to indicate change
+
+ // make two test enums in the program name folder
+
+ Category category = programNode.getCategory();
+ DataTypeManager dataTypeManager = category.getDataTypeManager();
+
+ int id = dataTypeManager.startTransaction("new enum 1");
+ Enum enumm = new EnumDataType("Colors", 1);
+ enumm.add("Red", 0);
+ enumm.add("Green", 0x10);
+ enumm.add("Blue", 0x20);
+
+ category.addDataType(enumm, null);
+ dataTypeManager.endTransaction(id, true);
+ waitForTree();
+
+ int id2 = dataTypeManager.startTransaction("new enum 2");
+ Enum enumm2 = new EnumDataType("MoreColors", 1);
+ enumm2.add("Red", 0); // add dup name same value
+ enumm2.add("Green", 0x5); // add dup name diff value
+ enumm2.add("Black", 0x10); // add dup value diff name
+ enumm2.add("Purple", 0x30);
+ enumm2.add("White", 0x40);
+ enumm2.add("Yellow", 0x50);
+
+ category.addDataType(enumm2, null);
+ dataTypeManager.endTransaction(id2, true);
+ waitForTree();
+
+ program.flushEvents();
+ waitForPostedSwingRunnables();
+
+ DataTypeNode testEnumNode1 = (DataTypeNode) programNode.getChild("Colors");
+ assertNotNull(testEnumNode1);
+
+ DataTypeNode testEnumNode2 = (DataTypeNode) programNode.getChild("MoreColors");
+ assertNotNull(testEnumNode2);
+
+ expandNode(programNode);
+ selectNodes(testEnumNode1, testEnumNode2);
+ waitForTree();
+
+ final DockingActionIf action = getAction(plugin, "Enum from Selection");
+ assertNotNull(action);
+ assertTrue(action.isEnabledForContext(provider.getActionContext(null)));
+ assertTrue(action.isAddToPopup(provider.getActionContext(null)));
+
+ executeOnSwingWithoutBlocking(new Runnable() {
+ @Override
+ public void run() {
+ DataTypeTestUtils.performAction(action, tree);
+ }
+ });
+
+ Window window = waitForWindow("Name new ENUM");
+ assertNotNull(window);
+
+ final JTextField tf = findComponent(window, JTextField.class);
+ assertNotNull(tf);
+
+ tf.setText("myNewEnum");
+ pressButtonByText(window, "OK");
+ assertTrue(!window.isShowing());
+ waitForPostedSwingRunnables();
+ waitForTree();
+
+ DataTypeNode newEnumNode = (DataTypeNode) programNode.getChild("myNewEnum");
+ waitForTree();
+
+ assertNotNull(newEnumNode);
+
+ Enum newEnum = (Enum) newEnumNode.getDataType();
+ long values[] = newEnum.getValues();
+ String names[] = newEnum.getNames();
+
+ assertEquals(values.length, 7);
+ assertEquals(names.length, 8);
+
+
+ assertEquals(newEnum.getName(0x00L), "Red");
+ assertEquals(newEnum.getName(0x5L), "Green_");
+ assertEquals(newEnum.getName(0x10L), "Black"); // single query will return first alphabetically
+ assertEquals(newEnum.getName(0x20L), "Blue");
+ assertEquals(newEnum.getName(0x30L), "Purple");
+ assertEquals(newEnum.getName(0x40L), "White");
+ assertEquals(newEnum.getName(0x50L), "Yellow");
+
+ String[] namesfor10 = newEnum.getNames(0x10);
+ assertEquals(namesfor10.length, 2);
+ assertEquals(namesfor10[0], "Black");
+ assertEquals(namesfor10[1], "Green");
+
+ assertEquals(newEnum.getValue("Red"), 0x00L);
+ assertEquals(newEnum.getValue("Green_"), 0x5L);
+ assertEquals(newEnum.getValue("Green"), 0x10L);
+ assertEquals(newEnum.getValue("Black"), 0x10L);
+ assertEquals(newEnum.getValue("Blue"), 0x20L);
+ assertEquals(newEnum.getValue("Purple"), 0x30L);
+ assertEquals(newEnum.getValue("White"), 0x40L);
+ assertEquals(newEnum.getValue("Yellow"), 0x50L);
+ }
@Test
public void testDontCreateEnumFromSingleSelection() throws Exception {