GP-3204 Updated CreateEnumFromSelection action to handle duplicate named entries.

This commit is contained in:
ghidra007 2023-03-24 21:35:09 +00:00
parent 3aa071d787
commit eaf804fc33
3 changed files with 223 additions and 28 deletions

View File

@ -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.</P>
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.</P>
</BLOCKQUOTE>
<H3><A name="DeleteDataType"></A>Deleting a Data Type</H3>

View File

@ -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<String> existingNames = Arrays.asList(enumm.getNames());
while (existingNames.contains(name)) {
name = name + "_";
}
return name;
}
private boolean isDuplicateEntry(Enum mergedEnum, Enum enumToMerge, String name) {
List<String> 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<String> 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;
}
}

View File

@ -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 {