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 {