Merge branch 'GP-225_dragonmacher_PR-2301_astrelsky_ConvertToClassAction' into Ghidra_9.2

This commit is contained in:
dragonmacher 2020-10-05 17:30:46 -04:00
commit a3a550e89a
6 changed files with 183 additions and 53 deletions

View file

@ -184,6 +184,13 @@
<P>Shows all locations that reference the given symbol.</P>
</BLOCKQUOTE>
<H2><A name="Convert_to_Class"></A>Convert Namespace to Class</H2>
<BLOCKQUOTE>
<P>You can convert a <I>Namespace</I> to a <I>Class</I>.
Right mouse click on a namespace and choose the <B>Convert To Class</B> option.</P>
</BLOCKQUOTE>
<H2><A name="Create_Class"></A>Create a Class</H2>
<BLOCKQUOTE>

View file

@ -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);

View file

@ -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);
}
}
}

View file

@ -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,7 +56,10 @@ public class CreateClassAction extends SymbolTreeContextAction {
protected boolean isEnabledForContext(SymbolTreeActionContext context) {
TreePath[] selectionPaths = context.getSelectedSymbolTreePaths();
if (selectionPaths.length == 1) {
if (selectionPaths.length != 1) {
return false;
}
Object object = selectionPaths[0].getLastPathComponent();
if (object instanceof ClassCategoryNode) {
return true;
@ -71,7 +76,6 @@ public class CreateClassAction extends SymbolTreeContextAction {
}
return (symbolType == SymbolType.CLASS || symbolType == SymbolType.LIBRARY);
}
}
return false;
}

View file

@ -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);
}

View file

@ -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
tx(program, () -> {
SymbolTable symTable = program.getSymbolTable();
int transactionID = program.startTransaction("test");
try {
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);