diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallNode.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallNode.java index a52185d5d4..6394cf6f85 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallNode.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/calltree/CallNode.java @@ -141,4 +141,28 @@ public abstract class CallNode extends GTreeSlowLoadingNode { } return false; } + + // overridden since we may have multiple children with the same function name, but in + // different namespaces + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + + CallNode other = (CallNode) obj; + return getSourceAddress().equals(other.getSourceAddress()); + } + + @Override + public int hashCode() { + return getSourceAddress().hashCode(); + } + } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/calltree/CallTreePluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/calltree/CallTreePluginTest.java index a1e3d67af5..afcbd9a6ba 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/calltree/CallTreePluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/calltree/CallTreePluginTest.java @@ -42,6 +42,7 @@ import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.gotoquery.GoToServicePlugin; import ghidra.app.services.GoToService; import ghidra.app.services.ProgramManager; +import ghidra.app.util.NamespaceUtils; import ghidra.framework.plugintool.PluginTool; import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramDB; @@ -158,6 +159,10 @@ public class CallTreePluginTest extends AbstractGhidraHeadedIntegrationTest { } } + private Function function(int addr) throws Exception { + return ensureFunction(addr); + } + private void function(int from, int to) throws Exception { ensureFunction(from); ensureFunction(to); @@ -168,16 +173,39 @@ public class CallTreePluginTest extends AbstractGhidraHeadedIntegrationTest { } } - private void ensureFunction(long from) throws Exception { + private Function function(String namespace, String name, int addr) throws Exception { + Function f = ensureFunction(addr); + tx(program, () -> { + + f.setName(name, SourceType.ANALYSIS); + Namespace ns = NamespaceUtils.createNamespaceHierarchy(namespace, null, program, + SourceType.ANALYSIS); + f.setParentNamespace(ns); + }); + + return f; + } + + private void function(Function from, Function to) { + + // a bit of space so the function call is not at the entry point + int offset = (int) from.getEntryPoint().getOffset() + 5; + int toOffset = (int) to.getEntryPoint().getOffset(); + while (!createReference(offset, toOffset)) { + offset++; + } + } + + private Function ensureFunction(long from) throws Exception { ProgramDB p = builder.getProgram(); FunctionManager fm = p.getFunctionManager(); Function f = fm.getFunctionAt(addr(from)); if (f != null) { - return; + return f; } String a = Long.toHexString(from); - builder.createEmptyFunction("Function_" + a, "0x" + a, 50, DataType.DEFAULT); + return builder.createEmptyFunction("Function_" + a, "0x" + a, 50, DataType.DEFAULT); } private boolean createReference(long from, long to) { @@ -580,12 +608,10 @@ public class CallTreePluginTest extends AbstractGhidraHeadedIntegrationTest { setProviderFunction("0x5000"); - final ToggleDockingAction filterDuplicatesAction = + ToggleDockingAction filterDuplicatesAction = (ToggleDockingAction) getAction("Filter Duplicates"); - if (!filterDuplicatesAction.isSelected()) { - performAction(filterDuplicatesAction, true); - } + setToggleActionSelected(filterDuplicatesAction, new ActionContext(), true); waitForTree(outgoingTree); GTreeNode rootNode = getRootNode(outgoingTree); @@ -597,8 +623,7 @@ public class CallTreePluginTest extends AbstractGhidraHeadedIntegrationTest { // copy the names of the children into a map so that we can verify that there are // no duplicates boolean shouldHaveDuplicates = false; - Map nameCountMap = createNameCountMap(rootNode); - System.out.println("nameCountMap: before: = " + nameCountMap); + Map nameCountMap = createNameCountMap(rootNode); assertDuplicateChildStatus(nameCountMap, shouldHaveDuplicates); performAction(filterDuplicatesAction, true);// deselect @@ -608,7 +633,47 @@ public class CallTreePluginTest extends AbstractGhidraHeadedIntegrationTest { rootNode = getRootNode(outgoingTree); nameCountMap = createNameCountMap(rootNode); shouldHaveDuplicates = true; - System.out.println("nameCountMap: after: = " + nameCountMap); + assertDuplicateChildStatus(nameCountMap, shouldHaveDuplicates); + } + + @Test + public void testFilterIncomingDuplicates_SameFunctionNameInDifferentNamespaces() + throws Exception { + + /* + Create a function call tree that looks like: + + + NS1::Foo + root + NS2::Foo + root + + */ + + Function f1 = function("NS1", "Foo", 0x11000); + Function f2 = function("NS2", "Foo", 0x12000); + Function root = function(0x0000); + function(f1, root); + function(f2, root); + + setProviderFunction("0x0000"); + + ToggleDockingAction filterDuplicatesAction = + (ToggleDockingAction) getAction("Filter Duplicates"); + setToggleActionSelected(filterDuplicatesAction, new ActionContext(), true); + waitForTree(incomingTree); + + GTreeNode rootNode = getRootNode(incomingTree); + List children = rootNode.getChildren(); + assertTrue( + "Outgoing tree does not have callers as expected for function: " + getListingFunction(), + children.size() > 0); + + // copy the names of the children into a map so that we can verify that there are + // no duplicates + boolean shouldHaveDuplicates = false; + Map nameCountMap = createNameCountMap(rootNode); assertDuplicateChildStatus(nameCountMap, shouldHaveDuplicates); } @@ -1071,27 +1136,26 @@ public class CallTreePluginTest extends AbstractGhidraHeadedIntegrationTest { return codeBrowserPlugin.getCurrentAddress(); } - private Map createNameCountMap(GTreeNode node) { - Map map = new HashMap<>(); + private Map createNameCountMap(GTreeNode node) { + Map map = new HashMap<>(); List children = node.getChildren(); for (GTreeNode child : children) { - String name = child.getName(); - Integer integer = map.get(name); + Integer integer = map.get(child); if (integer == null) { integer = 0; } int asInt = integer; asInt++; - map.put(name, asInt); + map.put(child, asInt); } return map; } - private void assertDuplicateChildStatus(Map childCountMap, + private void assertDuplicateChildStatus(Map childCountMap, boolean shouldHaveDuplicates) { boolean foundDuplicates = false; - Set> entrySet = childCountMap.entrySet(); - for (Entry entry : entrySet) { + Set> entrySet = childCountMap.entrySet(); + for (Entry entry : entrySet) { int value = entry.getValue(); if (value != 1) { foundDuplicates = true;