GT-3434 - Updated AddEditDialog to allow users to create namespaces that

match the label name
This commit is contained in:
dragonmacher 2020-01-15 14:02:28 -05:00
parent d66027e27a
commit 7ad8505dcf
9 changed files with 386 additions and 200 deletions

View file

@ -243,14 +243,14 @@ public class ThunkReferenceAddressDialog extends DialogComponentProvider {
return null; return null;
} }
List<Namespace> namespaces = NamespaceUtils.getNamespaces(parentNs, null, program); List<Namespace> namespaces = NamespaceUtils.getNamespaceByPath(program, null, parentNs);
if (namespaces.isEmpty()) { if (namespaces.isEmpty()) {
SymbolTable symbolTable = program.getSymbolTable(); SymbolTable symbolTable = program.getSymbolTable();
for (String libraryName : program.getExternalManager().getExternalLibraryNames()) { for (String libraryName : program.getExternalManager().getExternalLibraryNames()) {
Symbol librarySymbol = symbolTable.getLibrarySymbol(libraryName); Symbol librarySymbol = symbolTable.getLibrarySymbol(libraryName);
namespaces = NamespaceUtils.getNamespaces(parentNs, namespaces = NamespaceUtils.getNamespaceByPath(program,
(Library) librarySymbol.getObject(), program); (Library) librarySymbol.getObject(), parentNs);
if (!namespaces.isEmpty()) { if (!namespaces.isEmpty()) {
break; // use first library containing namespace break; // use first library containing namespace
} }

View file

@ -117,7 +117,7 @@ public class MatchSymbol {
SymbolPath namespacePath = aSymbolPath.getParent(); SymbolPath namespacePath = aSymbolPath.getParent();
if (!aSymbolIdentifier.isExternalSymbol() && namespacePath != null && if (!aSymbolIdentifier.isExternalSymbol() && namespacePath != null &&
!aSymbolPath.equals(bSymbolIdentifier.symbolPath) && !aSymbolPath.equals(bSymbolIdentifier.symbolPath) &&
NamespaceUtils.getNamespace(bProgram, namespacePath, null) != null) { NamespaceUtils.getNonFunctionNamespace(bProgram, namespacePath) != null) {
// skip match with namespace mismatch when source namespace exists in destination // skip match with namespace mismatch when source namespace exists in destination
continue; continue;
} }

View file

@ -172,24 +172,39 @@ public class AddEditDialog extends DialogComponentProvider {
if (parentPath == null) { if (parentPath == null) {
return rootNamespace; return rootNamespace;
} }
String relativeParentPath = parentPath.getPath();
SymbolPath absoluteParentPath = //
new SymbolPath(rootNamespace.getSymbol()).append(parentPath); // Prefer a non-function namespace. This allows us to put a function inside of a namespace
Namespace parentNamespace = NamespaceUtils.getNamespace(program, absoluteParentPath, addr); // sharing the same name.
if (parentNamespace != null) { //
return parentNamespace; SymbolPath fullPath = new SymbolPath(rootNamespace.getSymbol()).append(parentPath);
Namespace nonFunctionNs = NamespaceUtils.getNonFunctionNamespace(program, fullPath);
if (nonFunctionNs != null) {
return nonFunctionNs;
} }
// run the create namespaces command //
CreateNamespacesCmd command = // At this point we can either reuse an existing function namespace or we have to create
new CreateNamespacesCmd(relativeParentPath, rootNamespace, SourceType.USER_DEFINED); // a new non-function namespaces, depending upon the names being used. Only use an
// existing function as a namespace if none of namespace path entries match the function
if (tool.execute(command, program)) { // name.
return command.getNamespace(); //
String name = symbolPath.getName();
if (!parentPath.containsPathEntry(name)) {
Namespace functionNamespace =
NamespaceUtils.getFunctionNamespaceContaining(program, parentPath, addr);
if (functionNamespace != null) {
return functionNamespace;
}
} }
setStatusText(command.getStatusMsg()); CreateNamespacesCmd cmd =
new CreateNamespacesCmd(parentPath.getPath(), rootNamespace, SourceType.USER_DEFINED);
if (tool.execute(cmd, program)) {
return cmd.getNamespace();
}
setStatusText(cmd.getStatusMsg());
return null; return null;
} }

View file

@ -75,12 +75,12 @@ class FunctionsXmlMgr {
* &lt;!ELEMENT FUNCTION (RETURN_TYPE?, ADDRESS_RANGE*, REGULAR_CMT?, REPEATABLE_CMT?, TYPEINFO_CMT?, STACK_FRAME?, REGISTER_VAR*)&gt; * &lt;!ELEMENT FUNCTION (RETURN_TYPE?, ADDRESS_RANGE*, REGULAR_CMT?, REPEATABLE_CMT?, TYPEINFO_CMT?, STACK_FRAME?, REGISTER_VAR*)&gt;
* </code></pre> * </code></pre>
* <p> * <p>
* @param parser * @param parser the parser
* @param overwriteConflicts * @param overwriteConflicts true to overwrite any conflicts
* @param ignoreStackFrames * @param ignoreStackFrames true to ignore stack frames
* @param monitor * @param monitor the task monitor
* @throws AddressFormatException * @throws AddressFormatException if any address is not parsable
* @throws CancelledException * @throws CancelledException if the operation is cancelled through the monitor
*/ */
void read(XmlPullParser parser, boolean overwriteConflicts, boolean ignoreStackFrames, void read(XmlPullParser parser, boolean overwriteConflicts, boolean ignoreStackFrames,
TaskMonitor monitor) throws AddressFormatException, CancelledException { TaskMonitor monitor) throws AddressFormatException, CancelledException {
@ -94,9 +94,7 @@ class FunctionsXmlMgr {
dtParser = new DtParser(dataManager); dtParser = new DtParser(dataManager);
while (parser.peek().isStart()) { while (parser.peek().isStart()) {
if (monitor.isCancelled()) { monitor.checkCanceled();
throw new CancelledException();
}
final XmlElement functionElement = parser.start("FUNCTION"); final XmlElement functionElement = parser.start("FUNCTION");
@ -155,7 +153,8 @@ class FunctionsXmlMgr {
try { try {
Symbol symbol = func.getSymbol(); Symbol symbol = func.getSymbol();
Namespace namespace = Namespace namespace =
NamespaceUtils.getNamespace(program, namespacePath, entryPoint); NamespaceUtils.getFunctionNamespaceAt(program, namespacePath,
entryPoint);
if (namespace == null) { if (namespace == null) {
namespace = program.getGlobalNamespace(); namespace = program.getGlobalNamespace();
} }
@ -258,13 +257,6 @@ class FunctionsXmlMgr {
} }
} }
/**
* Add local vars to a function.
*
* @param function
* @param variables
* @throws InvalidInputException
*/
private void addLocalVars(Function function, List<Variable> variables, private void addLocalVars(Function function, List<Variable> variables,
boolean overwriteConflicts) throws InvalidInputException { boolean overwriteConflicts) throws InvalidInputException {
for (Variable v : variables) { for (Variable v : variables) {
@ -275,8 +267,9 @@ class FunctionsXmlMgr {
try { try {
String name = v.getName(); String name = v.getName();
boolean isDefaultVariableName = (name == null) || boolean isDefaultVariableName = (name == null) ||
SymbolUtilities.getDefaultLocalName(program, v.getStackOffset(), 0).equals( SymbolUtilities.getDefaultLocalName(program, v.getStackOffset(), 0)
name); .equals(
name);
SourceType sourceType = SourceType sourceType =
isDefaultVariableName ? SourceType.DEFAULT : SourceType.USER_DEFINED; isDefaultVariableName ? SourceType.DEFAULT : SourceType.USER_DEFINED;
@ -305,13 +298,9 @@ class FunctionsXmlMgr {
return dtParser.parseDataType(dtName, cp, size); return dtParser.parseDataType(dtName, cp, size);
} }
/** /*
* Returns the text embedded in an optional xml element. If the next element in the stream is not * Returns the text embedded in an optional xml element. If the next element in the stream is not
* the "expectedElementName", the xml parser stream is unchanged. * the "expectedElementName", the xml parser stream is unchanged
* <p>
* @param parser
* @param expectedElementName
* @return
*/ */
private String getElementText(XmlPullParser parser, String expectedElementName) { private String getElementText(XmlPullParser parser, String expectedElementName) {
String result = null; String result = null;

View file

@ -27,6 +27,7 @@ import org.junit.*;
import ghidra.app.cmd.function.CreateFunctionCmd; import ghidra.app.cmd.function.CreateFunctionCmd;
import ghidra.app.cmd.label.AddLabelCmd; import ghidra.app.cmd.label.AddLabelCmd;
import ghidra.app.cmd.label.CreateNamespacesCmd;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.navigation.GoToAddressLabelPlugin; import ghidra.app.plugin.core.navigation.GoToAddressLabelPlugin;
import ghidra.app.util.AddEditDialog; import ghidra.app.util.AddEditDialog;
@ -93,6 +94,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
@After @After
public void tearDown() throws Exception { public void tearDown() throws Exception {
dialog.close();
env.dispose(); env.dispose();
} }
@ -107,14 +109,14 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
assertEquals(globalScope, scope); assertEquals(globalScope, scope);
assertTrue(primaryCheckBox.isSelected()); assertTrue(primaryCheckBox.isSelected());
assertTrue(!primaryCheckBox.isEnabled()); assertTrue(!primaryCheckBox.isEnabled());
dialogCancel(); pressCancel();
} }
@Test @Test
public void testLabelChangeOne() { public void testLabelChangeOne() {
addLabel(addr(0x0100642a)); addLabel(addr(0x0100642a));
setText("printf"); setText("printf");
dialogOK(); pressOk();
Symbol s = getUniqueSymbol(program, "printf", null); Symbol s = getUniqueSymbol(program, "printf", null);
assertNotNull(s); assertNotNull(s);
@ -126,14 +128,14 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
public void testDuplicateLabelForAdd() { public void testDuplicateLabelForAdd() {
addLabel(addr(0x0100642a)); addLabel(addr(0x0100642a));
setText("printf"); setText("printf");
dialogOK(); pressOk();
addLabel(addr(0x0100642c)); addLabel(addr(0x0100642c));
setText("printf"); setText("printf");
assertEquals(" ", dialog.getStatusText()); assertEquals(" ", dialog.getStatusText());
dialogOK(); pressOk();
assertTrue(dialog.getStatusText().length() > 0); assertTrue(dialog.getStatusText().length() > 0);
dialogCancel(); pressCancel();
} }
@Test @Test
@ -141,7 +143,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
addLabel(addr(0x0100642a)); addLabel(addr(0x0100642a));
setText("printf"); setText("printf");
setCheckbox(entryCheckBox, true); setCheckbox(entryCheckBox, true);
dialogOK(); pressOk();
assertTrue(!dialog.isVisible()); assertTrue(!dialog.isVisible());
Symbol s = getUniqueSymbol(program, "printf", null); Symbol s = getUniqueSymbol(program, "printf", null);
assertNotNull(s); assertNotNull(s);
@ -153,7 +155,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
addLabel(addr(0x0100642a)); addLabel(addr(0x0100642a));
setText("printf"); setText("printf");
setCheckbox(pinnedCheckBox, true); setCheckbox(pinnedCheckBox, true);
dialogOK(); pressOk();
assertTrue(!dialog.isVisible()); assertTrue(!dialog.isVisible());
Symbol s = getUniqueSymbol(program, "printf", null); Symbol s = getUniqueSymbol(program, "printf", null);
assertNotNull(s); assertNotNull(s);
@ -161,7 +163,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
editLabel(s); editLabel(s);
setCheckbox(pinnedCheckBox, false); setCheckbox(pinnedCheckBox, false);
dialogOK(); pressOk();
assertTrue(!s.isPinned()); assertTrue(!s.isPinned());
} }
@ -169,7 +171,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
@Test @Test
public void testEmptyLabel() { public void testEmptyLabel() {
addLabel(addr(0x0100642a)); addLabel(addr(0x0100642a));
dialogOK(); pressOk();
assertTrue(dialog.isVisible()); assertTrue(dialog.isVisible());
assertTrue(dialog.getStatusText().length() > 0); assertTrue(dialog.getStatusText().length() > 0);
@ -239,7 +241,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
setText("printf"); setText("printf");
Namespace scope = st.getNamespace(a); Namespace scope = st.getNamespace(a);
setScope(scope); setScope(scope);
dialogOK(); pressOk();
s = getUniqueSymbol(program, "printf", scope); s = getUniqueSymbol(program, "printf", scope);
assertNotNull(s); assertNotNull(s);
assertTrue(!s.isGlobal()); assertTrue(!s.isGlobal());
@ -259,7 +261,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
setText("printf"); setText("printf");
setCheckbox(primaryCheckBox, true); setCheckbox(primaryCheckBox, true);
dialogOK(); pressOk();
s = getUniqueSymbol(program, "entry", null); s = getUniqueSymbol(program, "entry", null);
assertTrue(!s.isPrimary()); assertTrue(!s.isPrimary());
s = getUniqueSymbol(program, "printf", null); s = getUniqueSymbol(program, "printf", null);
@ -286,7 +288,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
addLabel(addr(0x100642a)); addLabel(addr(0x100642a));
setText("aaaa"); setText("aaaa");
assertEquals(0, recentLabels.size()); assertEquals(0, recentLabels.size());
dialogOK(); pressOk();
assertEquals(1, recentLabels.size()); assertEquals(1, recentLabels.size());
assertEquals("aaaa", recentLabels.get(0)); assertEquals("aaaa", recentLabels.get(0));
@ -295,7 +297,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
assertEquals("", getText()); assertEquals("", getText());
//assertEquals("aaaa", getText()); //assertEquals("aaaa", getText());
setText("bbbb"); setText("bbbb");
dialogOK(); pressOk();
assertEquals(2, recentLabels.size()); assertEquals(2, recentLabels.size());
assertEquals("bbbb", recentLabels.get(0)); assertEquals("bbbb", recentLabels.get(0));
@ -308,7 +310,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
for (int i = 0; i < 12; i++) { for (int i = 0; i < 12; i++) {
addLabel(addr(0x100642a)); addLabel(addr(0x100642a));
setText("l" + i); setText("l" + i);
dialogOK(); pressOk();
} }
assertEquals(10, recentLabels.size()); assertEquals(10, recentLabels.size());
@ -328,7 +330,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
assertTrue(entryCheckBox.isSelected()); assertTrue(entryCheckBox.isSelected());
assertTrue(primaryCheckBox.isSelected()); assertTrue(primaryCheckBox.isSelected());
assertTrue(!primaryCheckBox.isEnabled()); assertTrue(!primaryCheckBox.isEnabled());
dialogCancel(); pressCancel();
} }
@ -352,7 +354,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
editLabel(s); editLabel(s);
assertEquals("entry", getText()); assertEquals("entry", getText());
setText("bob"); setText("bob");
dialogOK(); pressOk();
program.flushEvents(); program.flushEvents();
waitForSwing(); waitForSwing();
assertEquals("bob", function.getName()); assertEquals("bob", function.getName());
@ -378,7 +380,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
editLabel(fredSymbol); editLabel(fredSymbol);
setCheckbox(primaryCheckBox, true); setCheckbox(primaryCheckBox, true);
dialogOK(); pressOk();
program.flushEvents(); program.flushEvents();
waitForSwing(); waitForSwing();
assertEquals("fred", function.getName()); assertEquals("fred", function.getName());
@ -400,7 +402,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
Object selectedItem = namespacesComboBox.getSelectedItem(); Object selectedItem = namespacesComboBox.getSelectedItem();
assertEquals(ns, ((AddEditDialog.NamespaceWrapper) selectedItem).getNamespace()); assertEquals(ns, ((AddEditDialog.NamespaceWrapper) selectedItem).getNamespace());
dialogCancel(); pressCancel();
} }
@ -414,7 +416,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
assertTrue(primaryCheckBox.isSelected()); assertTrue(primaryCheckBox.isSelected());
assertTrue(!primaryCheckBox.isEnabled()); assertTrue(!primaryCheckBox.isEnabled());
setText("aaaa"); setText("aaaa");
dialogOK(); pressOk();
s = st.getPrimarySymbol(refAddr); s = st.getPrimarySymbol(refAddr);
assertEquals("aaaa", s.getName()); assertEquals("aaaa", s.getName());
} }
@ -426,7 +428,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
editLabel(s); editLabel(s);
setText("fred"); setText("fred");
dialogOK(); pressOk();
assertEquals("fred", s.getName()); assertEquals("fred", s.getName());
s = st.getPrimarySymbol(a); s = st.getPrimarySymbol(a);
@ -438,17 +440,17 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
public void testDuplicateLabelForEdit() { public void testDuplicateLabelForEdit() {
addLabel(addr(0x100642a)); addLabel(addr(0x100642a));
setText("printf"); setText("printf");
dialogOK(); pressOk();
addLabel(addr(0x100642a)); addLabel(addr(0x100642a));
setText("fred"); setText("fred");
dialogOK(); pressOk();
Symbol s = getUniqueSymbol(program, "printf", null); Symbol s = getUniqueSymbol(program, "printf", null);
editLabel(s); editLabel(s);
setText("fred"); setText("fred");
dialogOK(); pressOk();
assertTrue(dialog.isVisible()); assertTrue(dialog.isVisible());
assertTrue(dialog.getStatusText().length() > 0); assertTrue(dialog.getStatusText().length() > 0);
@ -459,18 +461,18 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
Address a = addr(0x100642a); Address a = addr(0x100642a);
addLabel(a); addLabel(a);
setText("aaaa"); setText("aaaa");
dialogOK(); pressOk();
addLabel(a); addLabel(a);
setText("bbbb"); setText("bbbb");
dialogOK(); pressOk();
addLabel(a); addLabel(a);
setText("cccc"); setText("cccc");
dialogOK(); pressOk();
Symbol s = getUniqueSymbol(program, "bbbb", null); Symbol s = getUniqueSymbol(program, "bbbb", null);
editLabel(s); editLabel(s);
setText("zzzz"); setText("zzzz");
dialogOK(); pressOk();
assertNotNull(getUniqueSymbol(program, "aaaa", null)); assertNotNull(getUniqueSymbol(program, "aaaa", null));
assertNotNull(getUniqueSymbol(program, "zzzz", null)); assertNotNull(getUniqueSymbol(program, "zzzz", null));
assertNotNull(getUniqueSymbol(program, "cccc", null)); assertNotNull(getUniqueSymbol(program, "cccc", null));
@ -491,7 +493,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
assertTrue(!primaryCheckBox.isSelected()); assertTrue(!primaryCheckBox.isSelected());
assertTrue(primaryCheckBox.isEnabled()); assertTrue(primaryCheckBox.isEnabled());
setText("foo"); setText("foo");
dialogOK(); pressOk();
s = st.getPrimarySymbol(a); s = st.getPrimarySymbol(a);
assertNotNull(s); assertNotNull(s);
@ -509,16 +511,16 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
addLabel(s.getAddress()); addLabel(s.getAddress());
setText("aaaa"); setText("aaaa");
dialogOK(); pressOk();
s = getUniqueSymbol(program, "aaaa", null); s = getUniqueSymbol(program, "aaaa", null);
editLabel(s); editLabel(s);
setText("zzzz"); setText("zzzz");
dialogOK(); pressOk();
editLabel(s); editLabel(s);
setText("bbbb"); setText("bbbb");
dialogOK(); pressOk();
assertEquals(3, recentLabels.size()); assertEquals(3, recentLabels.size());
assertEquals("bbbb", recentLabels.get(0)); assertEquals("bbbb", recentLabels.get(0));
@ -532,13 +534,13 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
addLabel(s.getAddress()); addLabel(s.getAddress());
setScope(s.getParentNamespace()); setScope(s.getParentNamespace());
setText("foo"); setText("foo");
dialogOK(); pressOk();
s = getUniqueSymbol(program, "foo", null); s = getUniqueSymbol(program, "foo", null);
editLabel(s); editLabel(s);
assertTrue(primaryCheckBox.isEnabled()); assertTrue(primaryCheckBox.isEnabled());
assertTrue(!primaryCheckBox.isSelected()); assertTrue(!primaryCheckBox.isSelected());
dialogOK(); pressOk();
} }
@Test @Test
@ -579,7 +581,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
Address entryAddress = s.getAddress(); Address entryAddress = s.getAddress();
addLabel(entryAddress); addLabel(entryAddress);
setText("label_1"); setText("label_1");
dialogOK(); pressOk();
assertTrue("Encountered a problem adding a label to the Global " + "namespace", assertTrue("Encountered a problem adding a label to the Global " + "namespace",
!dialog.isVisible()); !dialog.isVisible());
@ -588,7 +590,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
s = getUniqueSymbol(program, "label_1", null); s = getUniqueSymbol(program, "label_1", null);
editLabel(s); editLabel(s);
setText("namespace_1::label_1"); setText("namespace_1::label_1");
dialogOK(); pressOk();
// make sure there were no problems // make sure there were no problems
assertTrue( assertTrue(
@ -599,18 +601,128 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
s = st.getSymbols("label_1").next(); s = st.getSymbols("label_1").next();
editLabel(s); editLabel(s);
setText("Global::label_1"); setText("Global::label_1");
dialogOK(); pressOk();
assertTrue("Encountered a problem changing a symbol's namespace to " + assertTrue("Encountered a problem changing a symbol's namespace to " +
"the Global namespace while editing the symbol", !dialog.isVisible()); "the Global namespace while editing the symbol", !dialog.isVisible());
} }
@Test @Test
public void testEntryNamespacePathWithAmbiguousFunctionName() { public void testSetLabelNamespace_InsideFunctionBody_ToFunctionNamespace() {
Symbol origEntry = getUniqueSymbol(program, "entry", null); Symbol entry = getUniqueSymbol(program, "entry", null);
assertNotNull(origEntry); assertNotNull(entry);
Symbol dupEntry = st.getPrimarySymbol(addr(0x1002239)); Address inBodyAddress = entry.getAddress().add(1);
addLabel(inBodyAddress);
String newName = "label_1";
setText("entry" + Namespace.NAMESPACE_DELIMITER + newName);
pressOk();
assertFalse("Encountered a problem adding a label to the Global namespace",
dialog.isVisible());
Symbol label1 = getSymbol(newName);
Namespace parentNamespace = label1.getParentNamespace();
assertTrue("Namespace is not a function", parentNamespace instanceof Function);
Function fun = (Function) parentNamespace;
assertEquals(entry.getAddress(), fun.getEntryPoint());
}
@Test
public void testSetLabelNamespace_InsideFunctionBody_ToExistingNonFunctionNamespace() {
Symbol entry = getUniqueSymbol(program, "entry", null);
assertNotNull(entry);
String namespaceName = "NewNamespace";
createNamespace(namespaceName);
Address inBodyAddress = entry.getAddress().add(1);
addLabel(inBodyAddress);
String newName = "label_1";
setText(namespaceName + Namespace.NAMESPACE_DELIMITER + newName);
pressOk();
assertFalse("Encountered a problem adding a label to the Global namespace",
dialog.isVisible());
Symbol label1 = getSymbol(newName);
Namespace parentNamespace = label1.getParentNamespace();
assertFalse("Namespace is not a function", parentNamespace instanceof Function);
}
@Test
public void testSetLabelNamespace_InsideFunctionBody_ToNonExistentNonFunctionNamespace() {
Symbol entry = getUniqueSymbol(program, "entry", null);
assertNotNull(entry);
String namespaceName = "NewNamespace";
Address inBodyAddress = entry.getAddress().add(1);
addLabel(inBodyAddress);
String newName = "label_1";
setText(namespaceName + Namespace.NAMESPACE_DELIMITER + newName);
pressOk();
assertFalse("Encountered a problem adding a label to the Global namespace",
dialog.isVisible());
Symbol label1 = getSymbol(newName);
Namespace parentNamespace = label1.getParentNamespace();
assertFalse("Namespace is not a function", parentNamespace instanceof Function);
}
@Test
public void testEntryNamespacePathWithAmbiguousFunctionName() {
Symbol entry = getUniqueSymbol(program, "entry", null);
assertNotNull(entry);
Address otherEntryAddress = addr(0x1002239);
Symbol dupEntry = createOtherEntry(otherEntryAddress);
Address inBodyAddress = otherEntryAddress.add(1);
addLabel(inBodyAddress);
String newName = "label_1";
setText("entry" + Namespace.NAMESPACE_DELIMITER + newName);
pressOk();
assertFalse("Encountered a problem adding a label to the Global namespace",
dialog.isVisible());
Symbol label1 = getSymbol(newName);
Namespace parentNamespace = label1.getParentNamespace();
assertTrue("Namespace is not a function", parentNamespace instanceof Function);
Function fun = (Function) parentNamespace;
assertEquals(dupEntry.getAddress(), fun.getEntryPoint());
}
@Test
public void testSetNamespace_NonExistentNamespace_SameNameAsFunction() throws Exception {
//
// Test that we can create a new namespace using the dialog when:
// 1) that namespace does not exist
// 2) the namespace matches the existing function name
// 3) the function name is not default
//
String functionName = "Foo";
Symbol function = createFunction(functionName);
editLabel(function);
String nsName = functionName;
setText(nsName + Namespace.NAMESPACE_DELIMITER + functionName);
pressOk();
assertFalse("Rename unsuccesful", dialog.isShowing());
Symbol newFunction = getSymbol(functionName);
Namespace parentNs = newFunction.getParentNamespace();
assertFalse(parentNs instanceof Function);
assertEquals(nsName, parentNs.getName());
}
//==================================================================================================
// Private Methods
//==================================================================================================
private Symbol createOtherEntry(Address otherAddress) {
Symbol dupEntry = st.getPrimarySymbol(otherAddress);
assertNotNull(dupEntry); assertNotNull(dupEntry);
int id = program.startTransaction("test"); int id = program.startTransaction("test");
@ -618,42 +730,48 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
dupEntry.setName("entry", SourceType.USER_DEFINED); dupEntry.setName("entry", SourceType.USER_DEFINED);
} }
catch (Exception e) { catch (Exception e) {
fail("Got Exception trying to set the function name to entry at 0x1002239 "); fail("Got Exception trying to set the function name to entry at " + otherAddress);
} }
finally { finally {
program.endTransaction(id, true); program.endTransaction(id, true);
} }
return dupEntry;
Address entryAddress = dupEntry.getAddress(); }
addLabel(entryAddress);
setText("entry::label_1"); private Symbol getSymbol(String functionName) {
dialogOK();
SymbolIterator it = st.getSymbols(functionName);
assertTrue("Encountered a problem adding a label to the Global " + "namespace", Symbol newLabel = it.next();
!dialog.isVisible()); assertNotNull(newLabel);
assertEquals(functionName, newLabel.getName());
SymbolIterator symbolIt = st.getSymbols("label_1"); return newLabel;
Symbol label1 = symbolIt.next(); }
assertNotNull(label1);
Namespace parentNamespace = label1.getParentNamespace(); private Symbol createFunction(String name) throws Exception {
assertTrue(parentNamespace instanceof Function); Symbol entry = getUniqueSymbol(program, "entry", null);
Function fun = (Function) parentNamespace; createEntryFunction();
assertEquals(dupEntry.getAddress(), fun.getEntryPoint()); editLabel(entry);
setText(name);
pressOk();
SymbolIterator it = st.getSymbols(name);
Symbol newLabel = it.next();
assertNotNull(newLabel);
assertEquals(name, newLabel.getName());
return newLabel;
} }
//==================================================================================================
// Private Methods
//==================================================================================================
private Address addr(long addr) { private Address addr(long addr) {
return program.getAddressFactory().getAddress(Long.toHexString(addr)); return program.getAddressFactory().getAddress(Long.toHexString(addr));
} }
private void dialogCancel() { private void pressCancel() {
runSwing(() -> invokeInstanceMethod("cancelCallback", dialog)); runSwing(() -> invokeInstanceMethod("cancelCallback", dialog));
} }
private void dialogOK() { private void pressOk() {
runSwing(() -> invokeInstanceMethod("okCallback", dialog)); runSwing(() -> invokeInstanceMethod("okCallback", dialog), false);
waitForSwing();
} }
private void setText(final String text) { private void setText(final String text) {
@ -738,4 +856,8 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
assertTrue(tool.execute(cmd, program)); assertTrue(tool.execute(cmd, program));
} }
} }
private void createNamespace(String name) {
applyCmd(program, new CreateNamespacesCmd(name, SourceType.USER_DEFINED));
}
} }

View file

@ -1379,7 +1379,7 @@ public class SymbolTablePluginTest extends AbstractGhidraHeadedIntegrationTest {
Command command = new CreateNamespacesCmd(namespaceName, SourceType.USER_DEFINED); Command command = new CreateNamespacesCmd(namespaceName, SourceType.USER_DEFINED);
if (tool.execute(command, program)) { if (tool.execute(command, program)) {
List<Namespace> namespaces = List<Namespace> namespaces =
NamespaceUtils.getNamespaces(namespaceName, null, program); NamespaceUtils.getNamespaceByPath(program, null, namespaceName);
if (namespaces.size() != 1) { if (namespaces.size() != 1) {
Assert.fail("Unable to find the newly created parent namespace."); Assert.fail("Unable to find the newly created parent namespace.");

View file

@ -436,7 +436,7 @@ public class PdbParser {
monitor.checkCanceled(); monitor.checkCanceled();
boolean isClass = namespaceMap.get(path); boolean isClass = namespaceMap.get(path);
Namespace parentNamespace = Namespace parentNamespace =
NamespaceUtils.getNamespace(program, path.getParent(), null); NamespaceUtils.getNonFunctionNamespace(program, path.getParent());
if (parentNamespace == null) { if (parentNamespace == null) {
String type = isClass ? "class" : "namespace"; String type = isClass ? "class" : "namespace";
log.appendMsg("Error: failed to define " + type + ": " + path); log.appendMsg("Error: failed to define " + type + ": " + path);

View file

@ -36,10 +36,7 @@ import ghidra.util.exception.*;
* <b>Assumptions for creating namespaces from a path string: </b> * <b>Assumptions for creating namespaces from a path string: </b>
* <ul> * <ul>
* <li>All elements of a namespace path should be namespace symbols and not other * <li>All elements of a namespace path should be namespace symbols and not other
* symbol types. * symbol types.
* <li>If the method takes an address, then the path can contain a function name provided
* the address is in the body of the function; otherwise
* the names must all be namespaces other than functions.
* <li>Absolute paths can optionally start with the global namespace. * <li>Absolute paths can optionally start with the global namespace.
* <li>You can provide a relative path that will start at the given * <li>You can provide a relative path that will start at the given
* parent namespace (or global if there is no parent provided). * parent namespace (or global if there is no parent provided).
@ -129,43 +126,66 @@ public class NamespaceUtils {
return null; return null;
} }
/**
* Returns a list of all namespaces with the given name in the parent namespace
*
* @param program the program to search
* @param parent the parent namespace from which to find all namespaces with the given name;
* if null, the global namespace will be used
* @param namespaceName the name of the namespaces to retrieve
* @return a list of all namespaces that match the given name in the given parent namespace.
*/
public static List<Namespace> getNamespacesByName(Program program, Namespace parent,
String namespaceName) {
validate(program, parent);
List<Namespace> namespaceList = new ArrayList<>();
List<Symbol> symbols = program.getSymbolTable().getSymbols(namespaceName, parent);
for (Symbol symbol : symbols) {
if (symbol.getSymbolType().isNamespace()) {
namespaceList.add((Namespace) symbol.getObject());
}
}
return namespaceList;
}
/** /**
* Returns a list of namespaces that match the given path. The path can be * Returns a list of namespaces that match the given path. The path can be
* relative to the given root namespace or absolute if the path begins with * relative to the given root namespace or absolute if the path begins with
* the global namespace name. * the global namespace name.
* *
* <P>Note: this path must only contain Namespace names and no other symbol types. * <P>Note: this path must only contain Namespace names and no other symbol types.
* *
* @param namespacePath the path to the desired namespace. * @param program the program to search
* @param rootNamespace the namespace to use as the root for relative paths. If null, the * @param parent the namespace to use as the root for relative paths. If null, the
* global namespace will be used. * global namespace will be used
* @param program the program to search. * @param pathString the path to the desired namespace
* @return a list of namespaces that match the given path. * @return a list of namespaces that match the given path
*/ */
public static List<Namespace> getNamespaces(String namespacePath, Namespace rootNamespace, public static List<Namespace> getNamespaceByPath(Program program, Namespace parent,
Program program) { String pathString) {
validate(program, rootNamespace); validate(program, parent);
rootNamespace = adjustForNullRootNamespace(rootNamespace, namespacePath, program); parent = adjustForNullRootNamespace(parent, pathString, program);
SymbolPath path = new SymbolPath(rootNamespace.getSymbol()); SymbolPath path = new SymbolPath(parent.getSymbol());
if (namespacePath != null) { if (pathString != null) {
path = path.append(new SymbolPath(namespacePath)); path = path.append(new SymbolPath(pathString));
} }
List<String> namespaceNames = path.asList(); List<String> namespaceNames = path.asList();
List<Namespace> namespaces = doGetNamespaces(namespaceNames, parent, program);
List<Namespace> namespaces = doGetNamespaces(namespaceNames, rootNamespace, program);
return namespaces; return namespaces;
} }
private static List<Namespace> doGetNamespaces(List<String> namespaceNames, private static List<Namespace> doGetNamespaces(List<String> namespaceNames,
Namespace rootNamespace, Program program) { Namespace root, Program program) {
if (rootNamespace == null) {
rootNamespace = program.getGlobalNamespace(); if (root == null) {
root = program.getGlobalNamespace();
} }
List<Namespace> parents = Arrays.asList(rootNamespace);
List<Namespace> parents = Arrays.asList(root);
for (String name : namespaceNames) { for (String name : namespaceNames) {
List<Namespace> matches = getMatchingNamespaces(name, parents, program); List<Namespace> matches = getMatchingNamespaces(name, parents, program);
parents = matches; parents = matches;
@ -174,19 +194,19 @@ public class NamespaceUtils {
} }
/** /**
* Returns a list all namespaces that have the given name in any of the given namespaces. * Returns a list all namespaces that have the given name in any of the given namespaces
* *
* @param childName the name of the namespaces to retrieve. * @param childName the name of the namespaces to retrieve
* @param parents a list of all namespaces to search for child namespaces with the given name. * @param parents a list of all namespaces to search for child namespaces with the given name
* @param program the program to search. * @param program the program to search
* @return a list all namespaces that have the given name in any of the given namespaces. * @return a list all namespaces that have the given name in any of the given namespaces
*/ */
public static List<Namespace> getMatchingNamespaces(String childName, List<Namespace> parents, public static List<Namespace> getMatchingNamespaces(String childName, List<Namespace> parents,
Program program) { Program program) {
validate(program, parents); validate(program, parents);
List<Namespace> list = new ArrayList<>(); List<Namespace> list = new ArrayList<>();
for (Namespace parent : parents) { for (Namespace parent : parents) {
list.addAll(getNamespaces(parent, childName, program)); list.addAll(getNamespacesByName(program, parent, childName));
} }
return list; return list;
@ -250,27 +270,6 @@ public class NamespaceUtils {
return searchForAllSymbolsInAnyOfTheseNamespaces(parents, symbolPath.getName(), program); return searchForAllSymbolsInAnyOfTheseNamespaces(parents, symbolPath.getName(), program);
} }
/**
* Returns a list of all namespaces with the given name in the parent namespace.
* @param parent the parent namespace from which to find all namespaces with the given name.
* @param namespaceName the name of the namespaces to retrieve.
* @param program the program to search.
* @return a list of all namespaces that match the given name in
* the given parent namespace.
*/
public static List<Namespace> getNamespaces(Namespace parent, String namespaceName,
Program program) {
validate(program, parent);
List<Namespace> namespaceList = new ArrayList<>();
List<Symbol> symbols = program.getSymbolTable().getSymbols(namespaceName, parent);
for (Symbol symbol : symbols) {
if (symbol.getSymbolType().isNamespace()) {
namespaceList.add((Namespace) symbol.getObject());
}
}
return namespaceList;
}
/** /**
* Returns the first namespace with the given name and that is NOT a function that * Returns the first namespace with the given name and that is NOT a function that
* is within the parent namespace. (ie. the first namespace that is not tied to a program * is within the parent namespace. (ie. the first namespace that is not tied to a program
@ -331,17 +330,22 @@ public class NamespaceUtils {
* and uses the given address to resolve functions with duplicate names. When * and uses the given address to resolve functions with duplicate names. When
* resolving down the namespace path, a function that matches a name will only * resolving down the namespace path, a function that matches a name will only
* be used if the given address is contained in the body of that function. * be used if the given address is contained in the body of that function.
* <P> *
* The root namespace can be a function. * <p>The root namespace can be a function.
*
* <p>If an address is passed, then the path can contain a function name provided the
* address is in the body of the function; otherwise the names must all be namespaces other
* than functions.
* *
* @param namespacePath The namespace name or path string to be parsed. * @param namespacePath The namespace name or path string to be parsed
* This value should not include a trailing symbol name, only namespace names. * This value should not include a trailing symbol name, only namespace names
* @param rootNamespace The parent namespace under which the desired * @param rootNamespace The parent namespace under which the desired
* namespace or path resides. If this value is null, then the * namespace or path resides. If this value is null, then the
* global namespace will be used. * global namespace will be used.
* @param program The current program in which the desired namespace * @param program The current program in which the desired namespace
* resides. * resides
* @param address the address used to resolve possible functions with duplicate names. * @param address the address used to resolve possible functions with duplicate names; may
* be null
* @param source the source of the namespace * @param source the source of the namespace
* @return The namespace that matches the given path. This can be either an existing * @return The namespace that matches the given path. This can be either an existing
* namespace or a newly created one. * namespace or a newly created one.
@ -358,8 +362,8 @@ public class NamespaceUtils {
if (namespacePath == null) { if (namespacePath == null) {
return rootNamespace; return rootNamespace;
} }
SymbolPath path = new SymbolPath(namespacePath);
SymbolPath path = new SymbolPath(namespacePath);
List<String> namespacesList = path.asList(); List<String> namespacesList = path.asList();
SymbolTable symbolTable = program.getSymbolTable(); SymbolTable symbolTable = program.getSymbolTable();
@ -382,40 +386,76 @@ public class NamespaceUtils {
} }
/** /**
* Finds the namespace for the given symbolPath. Since this method * Returns the existing Function at the given address if its {@link SymbolPath} matches the
* takes an address, the symbolPath can contain a function name provided the address * given path
* lives in the body of that function.
* *
* @param program the program from which to get the namespace. * @param program the program
* @param symbolPath the path of namespace names including the name of the desired namespace. * @param symbolPath the path of namespace
* @param address the address used to determine if any function namespaces can be used * @param address the address
* to resolve the path. For a function to be used, it must contain this address in it's body. * @return the namespace represented by the given path, or null if no such namespace exists
* @return the namespace represented by the given path, or null if no such namespace exists.
*/ */
public static Namespace getNamespace(Program program, SymbolPath symbolPath, Address address) { public static Namespace getFunctionNamespaceAt(Program program, SymbolPath symbolPath,
if (address == null) { Address address) {
if (symbolPath == null) {
return program.getGlobalNamespace(); if (symbolPath == null || address == null) {
}
List<Symbol> symbols = getSymbols(symbolPath, program);
for (Symbol symbol : symbols) {
if (symbol.getSymbolType() != SymbolType.FUNCTION &&
symbol.getSymbolType().isNamespace()) {
return (Namespace) symbol.getObject();
}
}
}
else if (symbolPath == null) {
return null; return null;
} }
else {
Symbol[] symbols = program.getSymbolTable().getSymbols(address); Symbol[] symbols = program.getSymbolTable().getSymbols(address);
for (Symbol symbol : symbols) { for (Symbol symbol : symbols) {
symbol.getSymbolType(); if (symbol.getSymbolType() == SymbolType.FUNCTION &&
if (symbol.getSymbolType() == SymbolType.FUNCTION && symbolPath.matchesPathOf(symbol)) {
symbolPath.equals(new SymbolPath(symbol))) { return (Function) symbol.getObject();
return (Function) symbol.getObject(); }
} }
return null;
}
/**
* Returns the existing Function containing the given address if its
* {@link SymbolPath} matches the given path
*
* @param program the program
* @param symbolPath the path of namespace
* @param address the address
* @return the namespace represented by the given path, or null if no such namespace exists
*/
public static Namespace getFunctionNamespaceContaining(Program program, SymbolPath symbolPath,
Address address) {
if (symbolPath == null || address == null) {
return null;
}
FunctionManager fm = program.getFunctionManager();
Function f = fm.getFunctionContaining(address);
if (f != null) {
if (symbolPath.matchesPathOf(f.getSymbol())) {
return f;
}
}
return null;
}
/**
* Finds the namespace for the given symbol path <b>that is not a function</b>
*
* @param program the program from which to get the namespace
* @param symbolPath the path of namespace names including the name of the desired namespace
* @return the namespace represented by the given path, or null if no such namespace exists or
* the namespace is a function
*/
public static Namespace getNonFunctionNamespace(Program program, SymbolPath symbolPath) {
if (symbolPath == null) {
return program.getGlobalNamespace();
}
List<Symbol> symbols = getSymbols(symbolPath, program);
for (Symbol symbol : symbols) {
if (symbol.getSymbolType() != SymbolType.FUNCTION &&
symbol.getSymbolType().isNamespace()) {
return (Namespace) symbol.getObject();
} }
} }
return null; return null;
@ -483,11 +523,12 @@ public class NamespaceUtils {
/** /**
* Convert a namespace to a class by copying all namespace children into a newly created class * Convert a namespace to a class by copying all namespace children into a newly created class
* and then removing the old namespace. * and then removing the old namespace
*
* @param namespace namespace to be converted * @param namespace namespace to be converted
* @return new class namespace * @return new class namespace
* @throws InvalidInputException if namespace was contained within a function and can not be * @throws InvalidInputException if namespace was contained within a function and can not be
* converted to a class * converted to a class
*/ */
public static GhidraClass convertNamespaceToClass(Namespace namespace) public static GhidraClass convertNamespaceToClass(Namespace namespace)
throws InvalidInputException { throws InvalidInputException {
@ -530,7 +571,7 @@ public class NamespaceUtils {
"Namespace contained within Function may not be converted to a class: " + name); "Namespace contained within Function may not be converted to a class: " + name);
} }
// move everything from old namespace into new class namespce // move everything from old namespace into new class namespace
try { try {
for (Symbol s : symbolTable.getSymbols(namespace)) { for (Symbol s : symbolTable.getSymbols(namespace)) {
s.setNamespace(classNamespace); s.setNamespace(classNamespace);

View file

@ -20,7 +20,6 @@ import java.util.*;
import ghidra.program.model.address.GlobalNamespace; import ghidra.program.model.address.GlobalNamespace;
import ghidra.program.model.listing.Library; import ghidra.program.model.listing.Library;
import ghidra.program.model.symbol.*; import ghidra.program.model.symbol.*;
import ghidra.util.SystemUtilities;
/** /**
* A convenience object for parsing a namespace path to a symbol. * A convenience object for parsing a namespace path to a symbol.
@ -190,6 +189,16 @@ public class SymbolPath implements Comparable<SymbolPath> {
return new SymbolPath(list); return new SymbolPath(list);
} }
/**
* Returns true if this path contains any path entry matching the given text
*
* @param text the text for which to search
* @return true if any path entry matches the given text
*/
public boolean containsPathEntry(String text) {
return asList().contains(text);
}
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;
@ -211,15 +220,25 @@ public class SymbolPath implements Comparable<SymbolPath> {
return false; return false;
} }
SymbolPath other = (SymbolPath) obj; SymbolPath other = (SymbolPath) obj;
if (!SystemUtilities.isEqual(parentPath, other.parentPath)) { if (!Objects.equals(parentPath, other.parentPath)) {
return false; return false;
} }
if (!SystemUtilities.isEqual(symbolName, other.symbolName)) { if (!Objects.equals(symbolName, other.symbolName)) {
return false; return false;
} }
return true; return true;
} }
/**
* A convenience method to check if the given symbol's symbol path matches this path
*
* @param s the symbol to check
* @return true if the symbol paths match
*/
public boolean matchesPathOf(Symbol s) {
return equals(new SymbolPath(s));
}
/** /**
* Returns a list of names of the symbols in the symbol path, starting with the name just * Returns a list of names of the symbols in the symbol path, starting with the name just
* below the global namespace. * below the global namespace.