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> <P>Shows all locations that reference the given symbol.</P>
</BLOCKQUOTE> </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> <H2><A name="Create_Class"></A>Create a Class</H2>
<BLOCKQUOTE> <BLOCKQUOTE>

View file

@ -206,8 +206,15 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
DockingAction setExternalProgramAction = new SetExternalProgramAction(plugin, this); DockingAction setExternalProgramAction = new SetExternalProgramAction(plugin, this);
DockingAction createExternalLocationAction = new CreateExternalLocationAction(plugin); DockingAction createExternalLocationAction = new CreateExternalLocationAction(plugin);
DockingAction editExternalLocationAction = new EditExternalLocationAction(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 renameAction = new RenameAction(plugin);
DockingAction cutAction = new CutAction(plugin, this); DockingAction cutAction = new CutAction(plugin, this);
@ -231,6 +238,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
tool.addLocalAction(this, editExternalLocationAction); tool.addLocalAction(this, editExternalLocationAction);
tool.addLocalAction(this, createClassAction); tool.addLocalAction(this, createClassAction);
tool.addLocalAction(this, createNamespaceAction); tool.addLocalAction(this, createNamespaceAction);
tool.addLocalAction(this, convertToClassAction);
tool.addLocalAction(this, renameAction); tool.addLocalAction(this, renameAction);
tool.addLocalAction(this, cutAction); tool.addLocalAction(this, cutAction);
tool.addLocalAction(this, pasteAction); 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 class CreateClassAction extends SymbolTreeContextAction {
public CreateClassAction(SymbolTreePlugin plugin) { public CreateClassAction(SymbolTreePlugin plugin, String group, String subGroup) {
super("Create Class", plugin.getName()); 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); setEnabled(false);
} }
@ -54,23 +56,25 @@ public class CreateClassAction extends SymbolTreeContextAction {
protected boolean isEnabledForContext(SymbolTreeActionContext context) { protected boolean isEnabledForContext(SymbolTreeActionContext context) {
TreePath[] selectionPaths = context.getSelectedSymbolTreePaths(); TreePath[] selectionPaths = context.getSelectedSymbolTreePaths();
if (selectionPaths.length == 1) { if (selectionPaths.length != 1) {
Object object = selectionPaths[0].getLastPathComponent(); return false;
if (object instanceof ClassCategoryNode) { }
return true;
} Object object = selectionPaths[0].getLastPathComponent();
else if (object instanceof SymbolNode) { if (object instanceof ClassCategoryNode) {
SymbolNode symbolNode = (SymbolNode) object; return true;
Symbol symbol = symbolNode.getSymbol(); }
SymbolType symbolType = symbol.getSymbolType(); else if (object instanceof SymbolNode) {
if (symbolType == SymbolType.NAMESPACE) { SymbolNode symbolNode = (SymbolNode) object;
// allow SymbolType to perform additional checks Symbol symbol = symbolNode.getSymbol();
Namespace parentNamespace = (Namespace) symbol.getObject(); SymbolType symbolType = symbol.getSymbolType();
return SymbolType.CLASS.isValidParent(context.getProgram(), parentNamespace, if (symbolType == SymbolType.NAMESPACE) {
Address.NO_ADDRESS, parentNamespace.isExternal()); // allow SymbolType to perform additional checks
} Namespace parentNamespace = (Namespace) symbol.getObject();
return (symbolType == SymbolType.CLASS || symbolType == SymbolType.LIBRARY); return SymbolType.CLASS.isValidParent(context.getProgram(), parentNamespace,
Address.NO_ADDRESS, parentNamespace.isExternal());
} }
return (symbolType == SymbolType.CLASS || symbolType == SymbolType.LIBRARY);
} }
return false; return false;
} }

View file

@ -32,9 +32,11 @@ import ghidra.util.exception.InvalidInputException;
public class CreateNamespaceAction extends SymbolTreeContextAction { public class CreateNamespaceAction extends SymbolTreeContextAction {
public CreateNamespaceAction(SymbolTreePlugin plugin) { public CreateNamespaceAction(SymbolTreePlugin plugin, String group, String subGroup) {
super("Create Namespace", plugin.getName()); 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); setEnabled(false);
} }

View file

@ -62,6 +62,7 @@ public class SymbolTreePlugin2Test extends AbstractGhidraHeadedIntegrationTest {
private DockingActionIf selectionAction; private DockingActionIf selectionAction;
private DockingActionIf createNamespaceAction; private DockingActionIf createNamespaceAction;
private DockingActionIf createClassAction; private DockingActionIf createClassAction;
private DockingActionIf convertToClassAction;
private ToggleDockingAction goToToggleAction; private ToggleDockingAction goToToggleAction;
private SymbolTreeTestUtils util; private SymbolTreeTestUtils util;
private SymbolGTree tree; private SymbolGTree tree;
@ -225,7 +226,7 @@ public class SymbolTreePlugin2Test extends AbstractGhidraHeadedIntegrationTest {
for (int i = 0; i < count - 1; i++) { for (int i = 0; i < count - 1; i++) {
GTreeNode n = ghidraNode.getChild(i); GTreeNode n = ghidraNode.getChild(i);
Symbol s = ((SymbolNode) n).getSymbol(); Symbol s = ((SymbolNode) n).getSymbol();
assertTrue(!s.getName().equals("AnotherLocal")); assertFalse(s.getName().equals("AnotherLocal"));
} }
// test undo/redo // test undo/redo
@ -254,7 +255,7 @@ public class SymbolTreePlugin2Test extends AbstractGhidraHeadedIntegrationTest {
for (int i = 0; i < count - 1; i++) { for (int i = 0; i < count - 1; i++) {
GTreeNode n = ghidraNode.getChild(i); GTreeNode n = ghidraNode.getChild(i);
Symbol s = ((SymbolNode) n).getSymbol(); 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) { @Test
assertTrue(action.isEnabledForContext(context)); public void testConvertNamespaceToClass() throws Exception {
performAction(action, context, true); String classNodeName = "MyClass";
program.flushEvents(); GTreeNode nsNode = rootNode.getChild(SymbolCategory.NAMESPACE_CATEGORY.getName());
util.waitForTree(); 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 @Test
@ -313,13 +324,13 @@ public class SymbolTreePlugin2Test extends AbstractGhidraHeadedIntegrationTest {
util.selectNode(lNode); util.selectNode(lNode);
ActionContext context = util.getSymbolTreeContext(); ActionContext context = util.getSymbolTreeContext();
assertTrue(!renameAction.isEnabledForContext(context)); assertFalse(renameAction.isEnabledForContext(context));
assertTrue(!cutAction.isEnabledForContext(context)); assertFalse(cutAction.isEnabledForContext(context));
assertTrue(!pasteAction.isEnabledForContext(context)); assertFalse(pasteAction.isEnabledForContext(context));
assertTrue(!deleteAction.isEnabledForContext(context)); assertFalse(deleteAction.isEnabledForContext(context));
assertTrue(!selectionAction.isEnabledForContext(context)); assertFalse(selectionAction.isEnabledForContext(context));
assertTrue(!createNamespaceAction.isEnabledForContext(context)); assertFalse(createNamespaceAction.isEnabledForContext(context));
assertTrue(!createClassAction.isEnabledForContext(context)); assertFalse(createClassAction.isEnabledForContext(context));
} }
@Test @Test
@ -329,16 +340,11 @@ public class SymbolTreePlugin2Test extends AbstractGhidraHeadedIntegrationTest {
util.expandNode(lNode); util.expandNode(lNode);
// add a label // add a label
SymbolTable symTable = program.getSymbolTable(); tx(program, () -> {
int transactionID = program.startTransaction("test"); SymbolTable symTable = program.getSymbolTable();
try {
symTable.createLabel(util.addr(0x010048a1L), "abcdefg", SourceType.USER_DEFINED); symTable.createLabel(util.addr(0x010048a1L), "abcdefg", SourceType.USER_DEFINED);
} });
finally {
program.endTransaction(transactionID, true);
}
program.flushEvents();
util.waitForTree(); util.waitForTree();
lNode = rootNode.getChild(3); lNode = rootNode.getChild(3);
@ -355,18 +361,13 @@ public class SymbolTreePlugin2Test extends AbstractGhidraHeadedIntegrationTest {
// verify that the tree updates // verify that the tree updates
Function f = program.getFunctionManager().getFunctionAt(util.addr(0x01002cf5L)); Function f = program.getFunctionManager().getFunctionAt(util.addr(0x01002cf5L));
Symbol s = getUniqueSymbol(program, "AnotherLocal", f); Symbol s = getUniqueSymbol(program, "AnotherLocal", f);
assertNotNull(s); assertNotNull(s);
int transactionID = program.startTransaction("test");
try { tx(program, () -> {
s.setName("MyAnotherLocal", SourceType.USER_DEFINED); s.setName("MyAnotherLocal", SourceType.USER_DEFINED);
} });
finally {
program.endTransaction(transactionID, true);
}
program.flushEvents();
waitForSwing();
util.waitForTree(); util.waitForTree();
GTreeNode fNode = getFunctionsNode(); GTreeNode fNode = getFunctionsNode();
@ -421,11 +422,20 @@ public class SymbolTreePlugin2Test extends AbstractGhidraHeadedIntegrationTest {
assertNotNull(createClassAction); assertNotNull(createClassAction);
createNamespaceAction = getAction(plugin, "Create Namespace"); createNamespaceAction = getAction(plugin, "Create Namespace");
assertNotNull(createNamespaceAction); assertNotNull(createNamespaceAction);
convertToClassAction = getAction(plugin, "Convert To Class");
assertNotNull(convertToClassAction);
goToToggleAction = (ToggleDockingAction) getAction(plugin, "Navigation"); goToToggleAction = (ToggleDockingAction) getAction(plugin, "Navigation");
assertNotNull(goToToggleAction); 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 { private void clickOnNode(GTreeNode node) throws Exception {
JTree jTree = (JTree) AbstractGenericTest.getInstanceField("tree", tree); JTree jTree = (JTree) AbstractGenericTest.getInstanceField("tree", tree);