diff --git a/Ghidra/Features/Base/src/main/help/help/topics/SymbolTreePlugin/SymbolTree.htm b/Ghidra/Features/Base/src/main/help/help/topics/SymbolTreePlugin/SymbolTree.htm index cbf1d7a164..d09178d123 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/SymbolTreePlugin/SymbolTree.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/SymbolTreePlugin/SymbolTree.htm @@ -184,6 +184,13 @@

Shows all locations that reference the given symbol.

+

Convert Namespace to Class

+ +
+

You can convert a Namespace to a Class. + Right mouse click on a namespace and choose the Convert To Class option.

+
+

Create a Class

diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java index 349011a304..ab81ad4a09 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/SymbolTreeProvider.java @@ -206,8 +206,15 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { DockingAction setExternalProgramAction = new SetExternalProgramAction(plugin, this); DockingAction createExternalLocationAction = new CreateExternalLocationAction(plugin); DockingAction editExternalLocationAction = new EditExternalLocationAction(plugin); - DockingAction createClassAction = new CreateClassAction(plugin); - DockingAction createNamespaceAction = new CreateNamespaceAction(plugin); + + String createGroup = "0Create"; + int createGroupIndex = 0; + DockingAction createNamespaceAction = new CreateNamespaceAction(plugin, createGroup, + Integer.toString(createGroupIndex++)); + DockingAction createClassAction = new CreateClassAction(plugin, createGroup, + Integer.toString(createGroupIndex++)); + DockingAction convertToClassAction = new ConvertToClassAction(plugin, createGroup, + Integer.toString(createGroupIndex++)); DockingAction renameAction = new RenameAction(plugin); DockingAction cutAction = new CutAction(plugin, this); @@ -231,6 +238,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter { tool.addLocalAction(this, editExternalLocationAction); tool.addLocalAction(this, createClassAction); tool.addLocalAction(this, createNamespaceAction); + tool.addLocalAction(this, convertToClassAction); tool.addLocalAction(this, renameAction); tool.addLocalAction(this, cutAction); tool.addLocalAction(this, pasteAction); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/ConvertToClassAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/ConvertToClassAction.java new file mode 100644 index 0000000000..935715b2ba --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/ConvertToClassAction.java @@ -0,0 +1,99 @@ +/* ### + * 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.symboltree.actions; + +import javax.swing.tree.TreePath; + +import docking.action.MenuData; +import docking.widgets.tree.GTreeNode; +import ghidra.app.plugin.core.symboltree.*; +import ghidra.app.plugin.core.symboltree.nodes.SymbolNode; +import ghidra.app.util.NamespaceUtils; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.*; +import ghidra.util.exception.AssertException; +import ghidra.util.exception.InvalidInputException; + +/** + * Symbol tree action for converting a namespace to a class + */ +public class ConvertToClassAction extends SymbolTreeContextAction { + + private static final String NAME = "Convert to Class"; + + public ConvertToClassAction(SymbolTreePlugin plugin, String group, String subGroup) { + super(NAME, plugin.getName()); + MenuData menuData = new MenuData(new String[] { NAME }, group); + menuData.setMenuSubGroup(subGroup); + setPopupMenuData(menuData); + setEnabled(false); + } + + @Override + public boolean isEnabledForContext(SymbolTreeActionContext context) { + TreePath[] selectionPaths = context.getSelectedSymbolTreePaths(); + if (selectionPaths.length != 1) { + return false; + } + + Object object = selectionPaths[0].getLastPathComponent(); + if (object instanceof SymbolNode) { + SymbolNode symbolNode = (SymbolNode) object; + Symbol symbol = symbolNode.getSymbol(); + return symbol.getSymbolType() == SymbolType.NAMESPACE; + } + return false; + } + + @Override + protected void actionPerformed(SymbolTreeActionContext context) { + TreePath[] selectionPaths = context.getSelectedSymbolTreePaths(); + + Program program = context.getProgram(); + GTreeNode node = (GTreeNode) selectionPaths[0].getLastPathComponent(); + + SymbolGTree tree = context.getSymbolTree(); + GTreeNode root = tree.getViewRoot(); + GTreeNode classesNode = root.getChild(SymbolCategory.CLASS_CATEGORY.getName()); + + Symbol symbol = ((SymbolNode) node).getSymbol(); + Namespace namespace = (Namespace) symbol.getObject(); + if (namespace != null) { + String name = namespace.getName(); + convertToClass(program, namespace); + program.flushEvents(); + context.getSymbolTree().startEditing(classesNode, name); + } + } + + private static void convertToClass(Program program, Namespace ns) { + int id = program.startTransaction(NAME); + boolean success = false; + try { + NamespaceUtils.convertNamespaceToClass(ns); + success = true; + } + catch (InvalidInputException e) { + // This is thrown when the provided namespace is a function + // It was checked in isEnabledForContext and thus cannot occur + throw new AssertException(e); + } + finally { + program.endTransaction(id, success); + } + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CreateClassAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CreateClassAction.java index 64e1603810..7ebdaef8ca 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CreateClassAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CreateClassAction.java @@ -33,9 +33,11 @@ import ghidra.util.exception.InvalidInputException; public class CreateClassAction extends SymbolTreeContextAction { - public CreateClassAction(SymbolTreePlugin plugin) { + public CreateClassAction(SymbolTreePlugin plugin, String group, String subGroup) { super("Create Class", plugin.getName()); - setPopupMenuData(new MenuData(new String[] { "Create Class" }, "0Create")); + MenuData menuData = new MenuData(new String[] { "Create Class" }, group); + menuData.setMenuSubGroup(subGroup); + setPopupMenuData(menuData); setEnabled(false); } @@ -54,23 +56,25 @@ public class CreateClassAction extends SymbolTreeContextAction { protected boolean isEnabledForContext(SymbolTreeActionContext context) { TreePath[] selectionPaths = context.getSelectedSymbolTreePaths(); - if (selectionPaths.length == 1) { - Object object = selectionPaths[0].getLastPathComponent(); - if (object instanceof ClassCategoryNode) { - return true; - } - else if (object instanceof SymbolNode) { - SymbolNode symbolNode = (SymbolNode) object; - Symbol symbol = symbolNode.getSymbol(); - SymbolType symbolType = symbol.getSymbolType(); - if (symbolType == SymbolType.NAMESPACE) { - // allow SymbolType to perform additional checks - Namespace parentNamespace = (Namespace) symbol.getObject(); - return SymbolType.CLASS.isValidParent(context.getProgram(), parentNamespace, - Address.NO_ADDRESS, parentNamespace.isExternal()); - } - return (symbolType == SymbolType.CLASS || symbolType == SymbolType.LIBRARY); + if (selectionPaths.length != 1) { + return false; + } + + Object object = selectionPaths[0].getLastPathComponent(); + if (object instanceof ClassCategoryNode) { + return true; + } + else if (object instanceof SymbolNode) { + SymbolNode symbolNode = (SymbolNode) object; + Symbol symbol = symbolNode.getSymbol(); + SymbolType symbolType = symbol.getSymbolType(); + if (symbolType == SymbolType.NAMESPACE) { + // allow SymbolType to perform additional checks + Namespace parentNamespace = (Namespace) symbol.getObject(); + return SymbolType.CLASS.isValidParent(context.getProgram(), parentNamespace, + Address.NO_ADDRESS, parentNamespace.isExternal()); } + return (symbolType == SymbolType.CLASS || symbolType == SymbolType.LIBRARY); } return false; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CreateNamespaceAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CreateNamespaceAction.java index 22e8d30e9a..25e301db59 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CreateNamespaceAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symboltree/actions/CreateNamespaceAction.java @@ -32,9 +32,11 @@ import ghidra.util.exception.InvalidInputException; public class CreateNamespaceAction extends SymbolTreeContextAction { - public CreateNamespaceAction(SymbolTreePlugin plugin) { + public CreateNamespaceAction(SymbolTreePlugin plugin, String group, String subGroup) { super("Create Namespace", plugin.getName()); - setPopupMenuData(new MenuData(new String[] { "Create Namespace" }, "0Create")); + MenuData menuData = new MenuData(new String[] { "Create Namespace" }, group); + menuData.setMenuSubGroup(subGroup); + setPopupMenuData(menuData); setEnabled(false); } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin2Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin2Test.java index 7a023be384..835f471892 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin2Test.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symboltree/SymbolTreePlugin2Test.java @@ -62,6 +62,7 @@ public class SymbolTreePlugin2Test extends AbstractGhidraHeadedIntegrationTest { private DockingActionIf selectionAction; private DockingActionIf createNamespaceAction; private DockingActionIf createClassAction; + private DockingActionIf convertToClassAction; private ToggleDockingAction goToToggleAction; private SymbolTreeTestUtils util; private SymbolGTree tree; @@ -225,7 +226,7 @@ public class SymbolTreePlugin2Test extends AbstractGhidraHeadedIntegrationTest { for (int i = 0; i < count - 1; i++) { GTreeNode n = ghidraNode.getChild(i); Symbol s = ((SymbolNode) n).getSymbol(); - assertTrue(!s.getName().equals("AnotherLocal")); + assertFalse(s.getName().equals("AnotherLocal")); } // test undo/redo @@ -254,7 +255,7 @@ public class SymbolTreePlugin2Test extends AbstractGhidraHeadedIntegrationTest { for (int i = 0; i < count - 1; i++) { GTreeNode n = ghidraNode.getChild(i); Symbol s = ((SymbolNode) n).getSymbol(); - assertTrue(!s.getName().equals("AnotherLocal")); + assertNotEquals("AnotherLocal", s.getName()); } } @@ -295,11 +296,21 @@ public class SymbolTreePlugin2Test extends AbstractGhidraHeadedIntegrationTest { } - private void performTreeAction(DockingActionIf action, ActionContext context) { - assertTrue(action.isEnabledForContext(context)); - performAction(action, context, true); - program.flushEvents(); - util.waitForTree(); + @Test + public void testConvertNamespaceToClass() throws Exception { + String classNodeName = "MyClass"; + GTreeNode nsNode = rootNode.getChild(SymbolCategory.NAMESPACE_CATEGORY.getName()); + GTreeNode classNode = util.createObject( + nsNode, classNodeName, createNamespaceAction); + + util.selectNode(classNode); + ActionContext context = util.getSymbolTreeContext(); + performTreeAction(convertToClassAction, context); + + GTreeNode classRootNode = rootNode.getChild(SymbolCategory.CLASS_CATEGORY.getName()); + classNode = classRootNode.getChild(classNodeName); + assertNotNull(classNode); + waitForCondition(tree::isEditing); } @Test @@ -313,13 +324,13 @@ public class SymbolTreePlugin2Test extends AbstractGhidraHeadedIntegrationTest { util.selectNode(lNode); ActionContext context = util.getSymbolTreeContext(); - assertTrue(!renameAction.isEnabledForContext(context)); - assertTrue(!cutAction.isEnabledForContext(context)); - assertTrue(!pasteAction.isEnabledForContext(context)); - assertTrue(!deleteAction.isEnabledForContext(context)); - assertTrue(!selectionAction.isEnabledForContext(context)); - assertTrue(!createNamespaceAction.isEnabledForContext(context)); - assertTrue(!createClassAction.isEnabledForContext(context)); + assertFalse(renameAction.isEnabledForContext(context)); + assertFalse(cutAction.isEnabledForContext(context)); + assertFalse(pasteAction.isEnabledForContext(context)); + assertFalse(deleteAction.isEnabledForContext(context)); + assertFalse(selectionAction.isEnabledForContext(context)); + assertFalse(createNamespaceAction.isEnabledForContext(context)); + assertFalse(createClassAction.isEnabledForContext(context)); } @Test @@ -329,16 +340,11 @@ public class SymbolTreePlugin2Test extends AbstractGhidraHeadedIntegrationTest { util.expandNode(lNode); // add a label - SymbolTable symTable = program.getSymbolTable(); - int transactionID = program.startTransaction("test"); - try { + tx(program, () -> { + SymbolTable symTable = program.getSymbolTable(); symTable.createLabel(util.addr(0x010048a1L), "abcdefg", SourceType.USER_DEFINED); - } - finally { - program.endTransaction(transactionID, true); - } + }); - program.flushEvents(); util.waitForTree(); lNode = rootNode.getChild(3); @@ -355,18 +361,13 @@ public class SymbolTreePlugin2Test extends AbstractGhidraHeadedIntegrationTest { // verify that the tree updates Function f = program.getFunctionManager().getFunctionAt(util.addr(0x01002cf5L)); - Symbol s = getUniqueSymbol(program, "AnotherLocal", f); assertNotNull(s); - int transactionID = program.startTransaction("test"); - try { + + tx(program, () -> { s.setName("MyAnotherLocal", SourceType.USER_DEFINED); - } - finally { - program.endTransaction(transactionID, true); - } - program.flushEvents(); - waitForSwing(); + }); + util.waitForTree(); GTreeNode fNode = getFunctionsNode(); @@ -421,11 +422,20 @@ public class SymbolTreePlugin2Test extends AbstractGhidraHeadedIntegrationTest { assertNotNull(createClassAction); createNamespaceAction = getAction(plugin, "Create Namespace"); assertNotNull(createNamespaceAction); + convertToClassAction = getAction(plugin, "Convert To Class"); + assertNotNull(convertToClassAction); goToToggleAction = (ToggleDockingAction) getAction(plugin, "Navigation"); assertNotNull(goToToggleAction); } + private void performTreeAction(DockingActionIf action, ActionContext context) { + assertTrue(action.isEnabledForContext(context)); + performAction(action, context, true); + program.flushEvents(); + util.waitForTree(); + } + private void clickOnNode(GTreeNode node) throws Exception { JTree jTree = (JTree) AbstractGenericTest.getInstanceField("tree", tree);