Test fixes for recent FG layout updates

This commit is contained in:
dragonmacher
2021-09-22 10:02:50 -04:00
parent 9c952ed154
commit a7f8cd3acd
4 changed files with 225 additions and 211 deletions
@@ -189,8 +189,8 @@ class FGActionManager {
return !(context instanceof FunctionGraphVertexLocationInFullViewModeActionContext); return !(context instanceof FunctionGraphVertexLocationInFullViewModeActionContext);
} }
}; };
zoomOutAction.setPopupMenuData( zoomOutAction
new MenuData(new String[] { "Zoom Out" }, popuEndPopupGroup)); .setPopupMenuData(new MenuData(new String[] { "Zoom Out" }, popuEndPopupGroup));
zoomOutAction.setKeyBindingData(new KeyBindingData( zoomOutAction.setKeyBindingData(new KeyBindingData(
KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, DockingUtils.CONTROL_KEY_MODIFIER_MASK))); KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, DockingUtils.CONTROL_KEY_MODIFIER_MASK)));
zoomOutAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Zoom")); zoomOutAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Zoom"));
@@ -325,8 +325,8 @@ class FGActionManager {
menuData.setMenuSubGroup(Integer.toString(vertexGroupingSubgroupOffset++)); menuData.setMenuSubGroup(Integer.toString(vertexGroupingSubgroupOffset++));
editLabelAction.setDescription("Change the label for the code block"); editLabelAction.setDescription("Change the label for the code block");
editLabelAction.setPopupMenuData(menuData); editLabelAction.setPopupMenuData(menuData);
editLabelAction.setHelpLocation( editLabelAction
new HelpLocation("FunctionGraphPlugin", "Vertex_Action_Label")); .setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Vertex_Action_Label"));
DockingAction fullViewAction = new DockingAction("Vertex View Mode", plugin.getName()) { DockingAction fullViewAction = new DockingAction("Vertex View Mode", plugin.getName()) {
@Override @Override
@@ -717,8 +717,8 @@ class FGActionManager {
}; };
selectHoveredEdgesAction.setPopupMenuData(new MenuData( selectHoveredEdgesAction.setPopupMenuData(new MenuData(
new String[] { selectionMenuName, "From Hovered Edges" }, popupSelectionGroup2)); new String[] { selectionMenuName, "From Hovered Edges" }, popupSelectionGroup2));
selectHoveredEdgesAction.setHelpLocation( selectHoveredEdgesAction
new HelpLocation("FunctionGraphPlugin", "Path_Selection")); .setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Path_Selection"));
DockingAction selectFocusedEdgesAction = DockingAction selectFocusedEdgesAction =
new DockingAction("Make Selection From Focused Edges", plugin.getName()) { new DockingAction("Make Selection From Focused Edges", plugin.getName()) {
@@ -751,8 +751,8 @@ class FGActionManager {
}; };
selectFocusedEdgesAction.setPopupMenuData(new MenuData( selectFocusedEdgesAction.setPopupMenuData(new MenuData(
new String[] { selectionMenuName, "From Focused Edges" }, popupSelectionGroup2)); new String[] { selectionMenuName, "From Focused Edges" }, popupSelectionGroup2));
selectFocusedEdgesAction.setHelpLocation( selectFocusedEdgesAction
new HelpLocation("FunctionGraphPlugin", "Path_Selection")); .setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Path_Selection"));
DockingAction clearCurrentSelectionAction = DockingAction clearCurrentSelectionAction =
new DockingAction("Clear Current Selection", plugin.getName()) { new DockingAction("Clear Current Selection", plugin.getName()) {
@@ -775,8 +775,8 @@ class FGActionManager {
}; };
clearCurrentSelectionAction.setPopupMenuData(new MenuData( clearCurrentSelectionAction.setPopupMenuData(new MenuData(
new String[] { selectionMenuName, "Clear Graph Selection" }, popupSelectionGroup3)); new String[] { selectionMenuName, "Clear Graph Selection" }, popupSelectionGroup3));
clearCurrentSelectionAction.setHelpLocation( clearCurrentSelectionAction
new HelpLocation("FunctionGraphPlugin", "Path_Selection")); .setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Path_Selection"));
DockingAction selectAllAction = DockingAction selectAllAction =
new DockingAction("Select All Code Units", plugin.getName()) { new DockingAction("Select All Code Units", plugin.getName()) {
@@ -815,12 +815,12 @@ class FGActionManager {
return isValidContext(context); return isValidContext(context);
} }
}; };
selectAllAction.setKeyBindingData( selectAllAction
new KeyBindingData(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK)); .setKeyBindingData(new KeyBindingData(KeyEvent.VK_A, InputEvent.CTRL_DOWN_MASK));
selectAllAction.setPopupMenuData(new MenuData( selectAllAction.setPopupMenuData(new MenuData(
new String[] { selectionMenuName, "Select All Code Units" }, popupSelectionGroup3)); new String[] { selectionMenuName, "Select All Code Units" }, popupSelectionGroup3));
selectAllAction.setHelpLocation( selectAllAction
new HelpLocation("FunctionGraphPlugin", "Code_Unit_Selection")); .setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Code_Unit_Selection"));
provider.addLocalAction(chooseFormatsAction); provider.addLocalAction(chooseFormatsAction);
provider.addLocalAction(homeAction); provider.addLocalAction(homeAction);
@@ -885,9 +885,7 @@ class FGActionManager {
// icon to be blank in the menu, but to use this icon on the toolbar. // icon to be blank in the menu, but to use this icon on the toolbar.
layoutAction.setDefaultIcon(ResourceManager.loadImage("images/preferences-system.png")); layoutAction.setDefaultIcon(ResourceManager.loadImage("images/preferences-system.png"));
List<ActionState<FGLayoutProvider>> actionStates = List<ActionState<FGLayoutProvider>> actionStates = loadActionStatesForLayoutProviders();
loadActionStatesForLayoutProviders();
for (ActionState<FGLayoutProvider> actionState : actionStates) { for (ActionState<FGLayoutProvider> actionState : actionStates) {
layoutAction.addActionState(actionState); layoutAction.addActionState(actionState);
} }
@@ -900,15 +898,19 @@ class FGActionManager {
} }
private List<ActionState<FGLayoutProvider>> loadActionStatesForLayoutProviders() { private List<ActionState<FGLayoutProvider>> loadActionStatesForLayoutProviders() {
List<FGLayoutProvider> layoutInstances = plugin.getLayoutProviders(); List<FGLayoutProvider> layoutInstances = plugin.getLayoutProviders();
return createActionStates(layoutInstances);
}
private List<ActionState<FGLayoutProvider>> createActionStates(
List<FGLayoutProvider> layoutProviders) {
List<ActionState<FGLayoutProvider>> list = new ArrayList<>(); List<ActionState<FGLayoutProvider>> list = new ArrayList<>();
HelpLocation layoutHelpLocation = HelpLocation layoutHelpLocation =
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout"); new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout");
for (FGLayoutProvider layout : layoutInstances) { for (FGLayoutProvider layout : layoutProviders) {
ActionState<FGLayoutProvider> layoutState = new ActionState<>( ActionState<FGLayoutProvider> layoutState =
layout.getLayoutName(), layout.getActionIcon(), layout); new ActionState<>(layout.getLayoutName(), layout.getActionIcon(), layout);
layoutState.setHelpLocation(layoutHelpLocation); layoutState.setHelpLocation(layoutHelpLocation);
list.add(layoutState); list.add(layoutState);
} }
@@ -1157,9 +1159,8 @@ class FGActionManager {
private void makeSelectionFromAddresses(AddressSet addresses) { private void makeSelectionFromAddresses(AddressSet addresses) {
ProgramSelection selection = new ProgramSelection(addresses); ProgramSelection selection = new ProgramSelection(addresses);
plugin.getTool() plugin.getTool()
.firePluginEvent( .firePluginEvent(new ProgramSelectionPluginEvent("Spoof!", selection,
new ProgramSelectionPluginEvent("Spoof!", selection, provider.getCurrentProgram()));
provider.getCurrentProgram()));
} }
private void ungroupVertices(Set<GroupedFunctionGraphVertex> groupVertices) { private void ungroupVertices(Set<GroupedFunctionGraphVertex> groupVertices) {
@@ -1214,6 +1215,11 @@ class FGActionManager {
layoutAction.setCurrentActionState(state); layoutAction.setCurrentActionState(state);
} }
void setLayouts(List<FGLayoutProvider> layouts) {
List<ActionState<FGLayoutProvider>> states = createActionStates(layouts);
layoutAction.setActionStates(states);
}
ActionState<FGLayoutProvider> getCurrentLayoutState() { ActionState<FGLayoutProvider> getCurrentLayoutState() {
return layoutAction.getCurrentState(); return layoutAction.getCurrentState();
} }
@@ -54,6 +54,7 @@ import ghidra.app.plugin.core.clipboard.ClipboardPlugin;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.functiongraph.graph.*; import ghidra.app.plugin.core.functiongraph.graph.*;
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider; import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider;
import ghidra.app.plugin.core.functiongraph.graph.layout.TestFGLayoutProvider;
import ghidra.app.plugin.core.functiongraph.graph.vertex.*; import ghidra.app.plugin.core.functiongraph.graph.vertex.*;
import ghidra.app.plugin.core.functiongraph.mvc.*; import ghidra.app.plugin.core.functiongraph.mvc.*;
import ghidra.app.services.*; import ghidra.app.services.*;
@@ -74,6 +75,7 @@ import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection; import ghidra.program.util.ProgramSelection;
import ghidra.test.*; import ghidra.test.*;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.exception.AssertException;
import ghidra.util.task.RunManager; import ghidra.util.task.RunManager;
public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedIntegrationTest { public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedIntegrationTest {
@@ -552,11 +554,19 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
protected void showFunctionGraphProvider() { protected void showFunctionGraphProvider() {
ComponentProvider provider = tool.getComponentProvider("Function Graph"); FGProvider provider = (FGProvider) tool.getComponentProvider("Function Graph");
tool.showComponentProvider(provider, true); tool.showComponentProvider(provider, true);
graphProvider = waitForComponentProvider(FGProvider.class); graphProvider = waitForComponentProvider(FGProvider.class);
assertNotNull("Graph not shown", graphProvider); assertNotNull("Graph not shown", graphProvider);
installTestGraphLayout(provider);
}
protected void installTestGraphLayout(FGProvider provider) {
FGActionManager actionManager = provider.getActionManager();
List<FGLayoutProvider> layouts = List.of(new TestFGLayoutProvider());
runSwing(() -> actionManager.setLayouts(layouts));
} }
protected ProgramSelection makeSingleVertexSelectionInCodeBrowser() { protected ProgramSelection makeSingleVertexSelectionInCodeBrowser() {
@@ -1003,7 +1013,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
combined.add(groupedVertex); combined.add(groupedVertex);
pickVertices(combined); pickVertices(combined);
// execute the 'add to group' action // execute the 'add to group' action
JComponent component = getComponent(groupedVertex); JComponent component = getComponent(groupedVertex);
DockingAction action = DockingAction action =
(DockingAction) TestUtils.getInstanceField("addToGroupAction", component); (DockingAction) TestUtils.getInstanceField("addToGroupAction", component);
@@ -1025,7 +1035,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
Collection<FGEdge> ungroupedEdges) { Collection<FGEdge> ungroupedEdges) {
for (FGEdge edge : ungroupedEdges) { for (FGEdge edge : ungroupedEdges) {
// //
// note: the edges we are given *may* be linked to disposed vertices, so we have // note: the edges we are given *may* be linked to disposed vertices, so we have
// to locate the edge in the graph that may represent the given edge. // to locate the edge in the graph that may represent the given edge.
// //
FGEdge currentEdge = getCurrentEdge(functionGraph, edge); FGEdge currentEdge = getCurrentEdge(functionGraph, edge);
@@ -1222,12 +1232,6 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
protected FGData create12345Graph() { protected FGData create12345Graph() {
//
// Note: we are manipulating the graph for testing by removing vertices. Some layouts
// do not handle this well, so we will use one we know works.
//
setMinCrossLayout();
// function sscanf // function sscanf
FGData funtionGraphData = graphFunction("100415a"); FGData funtionGraphData = graphFunction("100415a");
@@ -1316,7 +1320,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
// This tests verifies that a group will not be created if there is only one vertex // This tests verifies that a group will not be created if there is only one vertex
// found upon restoring settings. If we want to put that code back, then this test // found upon restoring settings. If we want to put that code back, then this test
// is again valid. // is again valid.
// //
public void dontTestRestoringWhenCodeBlocksHaveChanged_DoesntRegroup() { public void dontTestRestoringWhenCodeBlocksHaveChanged_DoesntRegroup() {
int transactionID = -1; int transactionID = -1;
try { try {
@@ -1390,38 +1394,38 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
FGData graphData = graphFunction("01002cf5"); FGData graphData = graphFunction("01002cf5");
FunctionGraph functionGraph = graphData.getFunctionGraph(); FunctionGraph functionGraph = graphData.getFunctionGraph();
Graph<FGVertex, FGEdge> graph = functionGraph; Graph<FGVertex, FGEdge> graph = functionGraph;
Set<FGVertex> ungroupedVertices = selectVertices( functionGraph, Set<FGVertex> ungroupedVertices = selectVertices( functionGraph,
"01002d2b" /* Another Local*/, "01002d2b" /* Another Local*/,
"01002d1f" /* MyLocal */); "01002d1f" /* MyLocal */);
Set<FGEdge> ungroupedEdges = getEdges(graph, ungroupedVertices); Set<FGEdge> ungroupedEdges = getEdges(graph, ungroupedVertices);
assertEquals("Did not grab all known edges for vertices", 4, ungroupedEdges.size()); assertEquals("Did not grab all known edges for vertices", 4, ungroupedEdges.size());
group(ungroupedVertices); group(ungroupedVertices);
assertVerticesRemoved(graph, ungroupedVertices); assertVerticesRemoved(graph, ungroupedVertices);
assertEdgesRemoved(graph, ungroupedEdges); assertEdgesRemoved(graph, ungroupedEdges);
// -1 because one one of the edges was between two of the vertices being grouped // -1 because one one of the edges was between two of the vertices being grouped
int expectedGroupedEdgeCount = ungroupedEdges.size() - 1; int expectedGroupedEdgeCount = ungroupedEdges.size() - 1;
GroupedFunctionGraphVertex groupedVertex = GroupedFunctionGraphVertex groupedVertex =
validateNewGroupedVertexFromVertices(functionGraph, ungroupedVertices, validateNewGroupedVertexFromVertices(functionGraph, ungroupedVertices,
expectedGroupedEdgeCount); expectedGroupedEdgeCount);
ungroup(groupedVertex); ungroup(groupedVertex);
assertVertexRemoved(graph, groupedVertex); assertVertexRemoved(graph, groupedVertex);
assertVerticesAdded(graph, ungroupedVertices); assertVerticesAdded(graph, ungroupedVertices);
assertEdgesAdded(functionGraph, ungroupedEdges); assertEdgesAdded(functionGraph, ungroupedEdges);
assertSelected(ungroupedVertices); assertSelected(ungroupedVertices);
} }
// @formatter:on // @formatter:on
protected void doTestGroupingProperlyTranslatesEdgesFromGroupedVerticesToRealVertices() { protected void doTestGroupingProperlyTranslatesEdgesFromGroupedVerticesToRealVertices() {
// //
// WARNING!!! WARNING!!! WARNING!!! WARNING!!! WARNING!!! WARNING!!! // WARNING!!! WARNING!!! WARNING!!! WARNING!!! WARNING!!! WARNING!!!
// This is not a junit test in that it is long, involved, hidden and complicated. We // This is not a junit test in that it is long, involved, hidden and complicated. We
// need to test this functionality, but we don't have a jComplicatedTest, so we will do // need to test this functionality, but we don't have a jComplicatedTest, so we will do
// it here. // it here.
// //
@@ -1429,32 +1433,32 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
// //
// Desired Behavior: We want to be able to group vertices, group grouped vertices and then // Desired Behavior: We want to be able to group vertices, group grouped vertices and then
// ungroup them in any order. For us to be able to do this, our group // ungroup them in any order. For us to be able to do this, our group
// vertices must store enough edge information to be able to ungroup // vertices must store enough edge information to be able to ungroup
// and find vertices for edges *whether or now those vertices have been // and find vertices for edges *whether or now those vertices have been
// grouped or ungrouped* // grouped or ungrouped*
// //
// Original Bug: We had a bug loosely described here: // Original Bug: We had a bug loosely described here:
// 0) Start with a directed graph of vertices. // 0) Start with a directed graph of vertices.
// 1) Create two separate group vertices (A and B), such that A has an edge to B. // 1) Create two separate group vertices (A and B), such that A has an edge to B.
// 2) Create a third group vertex (Z) that contains a non-grouped vertex (B) *and* one // 2) Create a third group vertex (Z) that contains a non-grouped vertex (B) *and* one
// of the other groups. // of the other groups.
// 3) Now, ungroup the 1 remaining originally grouped vertex (A). // 3) Now, ungroup the 1 remaining originally grouped vertex (A).
// 4) **At this point, the code could not determine which endpoint to pick for the edge // 4) **At this point, the code could not determine which endpoint to pick for the edge
// that used to be from Z->A. Which vertex inside of A represented the connection // that used to be from Z->A. Which vertex inside of A represented the connection
// pointing into Z (by way of B). // pointing into Z (by way of B).
// //
// The fix is mentioned in the Desired Behavior section. // The fix is mentioned in the Desired Behavior section.
// //
/* /*
0) Initial Graph 0) Initial Graph
1 -> 2 -> 3 -> 4 1 -> 2 -> 3 -> 4
| |
* *
5 5
*/ */
create12345Graph(); create12345Graph();
@@ -1469,7 +1473,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
FGVertex v4 = vertex("1004196"); FGVertex v4 = vertex("1004196");
FGVertex v5 = vertex("100419c"); FGVertex v5 = vertex("100419c");
// verify initial graph // verify initial graph
verifyEdge(v1, v2); verifyEdge(v1, v2);
verifyEdge(v2, v3); verifyEdge(v2, v3);
verifyEdge(v3, v4); verifyEdge(v3, v4);
@@ -1478,12 +1482,12 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
/* /*
1) Create two separate group vertices (A and B), such that A has an edge to B. 1) Create two separate group vertices (A and B), such that A has an edge to B.
A (v:{1,2} e:{1->2, 2->3}) -> B (v:{3,4} e:{2->3,3->4,3->5}) A (v:{1,2} e:{1->2, 2->3}) -> B (v:{3,4} e:{2->3,3->4,3->5})
| |
* *
5 5
*/ */
GroupedFunctionGraphVertex groupA = group("A", v1, v2); GroupedFunctionGraphVertex groupA = group("A", v1, v2);
@@ -1494,13 +1498,13 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
verifyEdgeCount(2);// no other edges verifyEdgeCount(2);// no other edges
/* /*
2) Create a third group vertex (Z) that contains a non-grouped vertex *and* one 2) Create a third group vertex (Z) that contains a non-grouped vertex *and* one
of the other groups (B). of the other groups (B).
A (v:{1,2} e:{1->2, 2->3}) -> Z ( A (v:{1,2} e:{1->2, 2->3}) -> Z (
v:{B (v:{3,4} e:{2->3,3->4,3->5}), 5} v:{B (v:{3,4} e:{2->3,3->4,3->5}), 5}
e:{2->3, 3->5} e:{2->3, 3->5}
) )
*/ */
@@ -1511,12 +1515,12 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
/* /*
3) Now, ungroup the 1 remaining originally grouped vertex (A). 3) Now, ungroup the 1 remaining originally grouped vertex (A).
1 -> 2 -> Z ( 1 -> 2 -> Z (
v:{B (v:{3,4} e:{2->3,3->4,3->5}), 5} v:{B (v:{3,4} e:{2->3,3->4,3->5}), 5}
e:{2->3, 3->5} e:{2->3, 3->5}
) )
*/ */
ungroup(groupA); ungroup(groupA);
@@ -1526,14 +1530,14 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
verifyEdgeCount(2); verifyEdgeCount(2);
/* /*
4) Now, ungroup Z and go back to having one remaining group vertex (B) 4) Now, ungroup Z and go back to having one remaining group vertex (B)
1 -> 2 -> -> B (v:{3,4} e:{2->3,3->4,3->5}) 1 -> 2 -> -> B (v:{3,4} e:{2->3,3->4,3->5})
| |
* *
5 5
*/ */
ungroup(groupZ); ungroup(groupZ);
@@ -1545,12 +1549,12 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
/* /*
5) Finally, ungroup the last group and make sure the graph is restored 5) Finally, ungroup the last group and make sure the graph is restored
1 -> 2 -> 3 -> 4 1 -> 2 -> 3 -> 4
| |
* *
5 5
*/ */
ungroup(groupB); ungroup(groupB);
@@ -1564,19 +1568,19 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
} }
private void doTestRestoringWhenCodeBlocksHaveChanged_DoesntRegroup() { private void doTestRestoringWhenCodeBlocksHaveChanged_DoesntRegroup() {
// //
// Tests the behavior of how group vertices are restored when one or more of the vertices // Tests the behavior of how group vertices are restored when one or more of the vertices
// inside of the grouped vertex is no longer available when the graph attempts to restore // inside of the grouped vertex is no longer available when the graph attempts to restore
// the group vertex user settings (i.e., when restarting Ghidra, the previously grouped // the group vertex user settings (i.e., when restarting Ghidra, the previously grouped
// vertices should reappear). // vertices should reappear).
// //
// In this test, we will be mutating a group of 2 nodes such // In this test, we will be mutating a group of 2 nodes such
// that one of the nodes has been split into two. This leaves only one vertex to // that one of the nodes has been split into two. This leaves only one vertex to
// be found by the regrouping algorithm. Furthermore, the regrouping will not take place // be found by the regrouping algorithm. Furthermore, the regrouping will not take place
// if at least two vertices cannot be found. // if at least two vertices cannot be found.
// //
// //
// Pick a function and group some nodes. // Pick a function and group some nodes.
// //
FGData graphData = graphFunction("01002cf5"); FGData graphData = graphFunction("01002cf5");
@@ -1587,8 +1591,8 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
group(ungroupedVertices); group(ungroupedVertices);
// 5 edges expected: // 5 edges expected:
// -01002cf5: 2 out // -01002cf5: 2 out
// -01002cf5: 2 in, 1 out // -01002cf5: 2 in, 1 out
int expectedGroupedEdgeCount = 5; int expectedGroupedEdgeCount = 5;
GroupedFunctionGraphVertex groupedVertex = validateNewGroupedVertexFromVertices( GroupedFunctionGraphVertex groupedVertex = validateNewGroupedVertexFromVertices(
@@ -1598,10 +1602,10 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
Address minAddress = addresses.getMinAddress(); Address minAddress = addresses.getMinAddress();
// //
// Ideally, we would like to save, close and re-open the program so that we can get // Ideally, we would like to save, close and re-open the program so that we can get
// a round-trip saving and reloading. However, in the test environment, we cannot save // a round-trip saving and reloading. However, in the test environment, we cannot save
// our programs. So, we will instead just navigate away from the current function, clear // our programs. So, we will instead just navigate away from the current function, clear
// the cache (to make sure that we read the settings again), and then verify that the // the cache (to make sure that we read the settings again), and then verify that the
// data saved in the program has been used to re-group. // data saved in the program has been used to re-group.
// //
graphFunction("0100415a"); graphFunction("0100415a");
@@ -1623,19 +1627,19 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
} }
protected void doTestRestoringWhenCodeBlocksHaveChanged_WillRegroup() { protected void doTestRestoringWhenCodeBlocksHaveChanged_WillRegroup() {
// //
// Tests the behavior of how group vertices are restored when one or more of the vertices // Tests the behavior of how group vertices are restored when one or more of the vertices
// inside of the grouped vertex is no longer available when the graph attempts to restore // inside of the grouped vertex is no longer available when the graph attempts to restore
// the group vertex user settings (i.e., when restarting Ghidra, the previously grouped // the group vertex user settings (i.e., when restarting Ghidra, the previously grouped
// vertices should reappear). // vertices should reappear).
// //
// In this test, we will be mutating a group of 3 nodes such // In this test, we will be mutating a group of 3 nodes such
// that one of the nodes has been split into two. This leaves 2 vertices to // that one of the nodes has been split into two. This leaves 2 vertices to
// be found by the regrouping algorithm. Furthermore, the regrouping *will* still // be found by the regrouping algorithm. Furthermore, the regrouping *will* still
// take place, as at least two vertices cannot be found. // take place, as at least two vertices cannot be found.
// //
// //
// Pick a function and group some nodes. // Pick a function and group some nodes.
// //
FGData graphData = graphFunction("01002cf5"); FGData graphData = graphFunction("01002cf5");
@@ -1646,8 +1650,8 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
group(ungroupedVertices); group(ungroupedVertices);
// 5 edges expected: // 5 edges expected:
// -01002cf5: 2 out // -01002cf5: 2 out
// -01002d11: 2 in, (1 out that was removed) // -01002d11: 2 in, (1 out that was removed)
// -01002d1f: 2 out (1 in that was removed) // -01002d1f: 2 out (1 in that was removed)
int expectedGroupedEdgeCount = 6; int expectedGroupedEdgeCount = 6;
@@ -1659,10 +1663,10 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
Address maxAddress = addresses.getMaxAddress(); Address maxAddress = addresses.getMaxAddress();
// //
// Ideally, we would like to save, close and re-open the program so that we can get // Ideally, we would like to save, close and re-open the program so that we can get
// a round-trip saving and reloading. However, in the test environment, we cannot save // a round-trip saving and reloading. However, in the test environment, we cannot save
// our programs. So, we will instead just navigate away from the current function, clear // our programs. So, we will instead just navigate away from the current function, clear
// the cache (to make sure that we read the settings again), and then verify that the // the cache (to make sure that we read the settings again), and then verify that the
// data saved in the program has been used to re-group. // data saved in the program has been used to re-group.
// //
graphFunction("0100415a"); graphFunction("0100415a");
@@ -1706,12 +1710,12 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
protected void doTestSymbolAddedWhenGrouped_SymbolInsideOfGroupNode() { protected void doTestSymbolAddedWhenGrouped_SymbolInsideOfGroupNode() {
// //
// By default, if the FunctionGraph detects a symbol addition to one of the code blocks // By default, if the FunctionGraph detects a symbol addition to one of the code blocks
// in the graph, then it will split the affected vertex (tested elsewhere). // in the graph, then it will split the affected vertex (tested elsewhere).
// However, if the affected vertex is grouped, then the FG will not split the node, but // However, if the affected vertex is grouped, then the FG will not split the node, but
// should still show the 'stale' indicator. // should still show the 'stale' indicator.
// //
// //
// Pick a function and group some nodes. // Pick a function and group some nodes.
// //
FGData graphData = graphFunction("01002cf5"); FGData graphData = graphFunction("01002cf5");
@@ -1722,8 +1726,8 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
group(ungroupedVertices); group(ungroupedVertices);
// 5 edges expected: // 5 edges expected:
// -01002cf5: 2 out // -01002cf5: 2 out
// -01002cf5: 2 in, 1 out // -01002cf5: 2 in, 1 out
int expectedGroupedEdgeCount = 5; int expectedGroupedEdgeCount = 5;
GroupedFunctionGraphVertex groupedVertex = validateNewGroupedVertexFromVertices( GroupedFunctionGraphVertex groupedVertex = validateNewGroupedVertexFromVertices(
@@ -1748,8 +1752,8 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
return reference.get(); return reference.get();
} }
/** /**
* Finds an edge that represents the given edge, which may no longer exist with * Finds an edge that represents the given edge, which may no longer exist with
* the same (==) edge instances. * the same (==) edge instances.
*/ */
private FGEdge getCurrentEdge(FunctionGraph functionGraph, FGEdge edge) { private FGEdge getCurrentEdge(FunctionGraph functionGraph, FGEdge edge) {
@@ -1998,29 +2002,29 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
private void setMinCrossLayout() { private void setMinCrossLayout() {
Object actionManager = getInstanceField("actionManager", graphProvider); Object actionManager = getInstanceField("actionManager", graphProvider);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final MultiStateDockingAction<Class<? extends FGLayoutProvider>> action = final MultiStateDockingAction<FGLayoutProvider> action =
(MultiStateDockingAction<Class<? extends FGLayoutProvider>>) getInstanceField( (MultiStateDockingAction<FGLayoutProvider>) getInstanceField("layoutAction",
"layoutAction", actionManager); actionManager);
runSwing(() -> { runSwing(() -> {
List<ActionState<Class<? extends FGLayoutProvider>>> states = List<ActionState<FGLayoutProvider>> states = action.getAllActionStates();
action.getAllActionStates(); for (ActionState<FGLayoutProvider> state : states) {
for (ActionState<Class<? extends FGLayoutProvider>> state : states) { FGLayoutProvider layoutProvider = state.getUserData();
Class<? extends FGLayoutProvider> layoutClass = state.getUserData(); if (layoutProvider.getLayoutName().contains("MinCross")) {
if (layoutClass.getSimpleName().contains("MinCross")) {
action.setCurrentActionState(state); action.setCurrentActionState(state);
return;
} }
} }
throw new AssertException("Unable to find MinCross layout");
}); });
} }
protected FGData triggerPersistenceAndReload(String functionAddress) { protected FGData triggerPersistenceAndReload(String functionAddress) {
// //
// Ideally, we would like to save, close and re-open the program so that we can get // Ideally, we would like to save, close and re-open the program so that we can get
// a round-trip saving and reloading. However, in the test environment, we cannot save // a round-trip saving and reloading. However, in the test environment, we cannot save
// our programs. So, we will instead just navigate away from the current function, clear // our programs. So, we will instead just navigate away from the current function, clear
// the cache (to make sure that we read the settings again), and then verify that the // the cache (to make sure that we read the settings again), and then verify that the
// data saved in the program has been used to re-group. // data saved in the program has been used to re-group.
// //
String otherAddress = "0100415a"; String otherAddress = "0100415a";
@@ -2277,7 +2281,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
//================================================================================================== //==================================================================================================
// Private Methods // Private Methods
//================================================================================================== //==================================================================================================
protected void moveView(int amount) { protected void moveView(int amount) {
Point translation = new Point(amount, amount); Point translation = new Point(amount, amount);
@@ -2316,11 +2320,11 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
DockingActionIf action = getAction(tool, "FunctionGraphPlugin", name); DockingActionIf action = getAction(tool, "FunctionGraphPlugin", name);
ToggleDockingAction displayAction = (ToggleDockingAction) action; ToggleDockingAction displayAction = (ToggleDockingAction) action;
setToggleActionSelected(displayAction, new ActionContext(), expectedVisible); setToggleActionSelected(displayAction, new ActionContext(), expectedVisible);
// //
// // make sure the action is not already in the state we expect // // make sure the action is not already in the state we expect
// assertEquals(name + " action is not selected as expected", !expectedVisible, // assertEquals(name + " action is not selected as expected", !expectedVisible,
// displayAction.isSelected()); // displayAction.isSelected());
// //
// performAction(displayAction, true); // performAction(displayAction, true);
} }
@@ -65,12 +65,12 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
@Test @Test
public void testGroupingPersistence() throws Exception { public void testGroupingPersistence() throws Exception {
// //
// Round-trip test to ensure that a grouped graph will be restored after re-opening a // Round-trip test to ensure that a grouped graph will be restored after re-opening a
// program. // program.
// //
// //
// Pick a function and group some nodes. // Pick a function and group some nodes.
// //
FGData graphData = graphFunction("01002cf5"); FGData graphData = graphFunction("01002cf5");
@@ -95,7 +95,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// Record the edges for later validation. Note: we have to keep the string form, as the // Record the edges for later validation. Note: we have to keep the string form, as the
// toString() on the edges will call back to its vertices, which will later have been // toString() on the edges will call back to its vertices, which will later have been
// disposed. // disposed.
Collection<FGEdge> oringalGroupedEdges = new HashSet<>(graph.getEdges());// copy so they don't get cleared Collection<FGEdge> oringalGroupedEdges = new HashSet<>(graph.getEdges());// copy so they don't get cleared
List<String> originalEdgeStrings = new ArrayList<>(oringalGroupedEdges.size()); List<String> originalEdgeStrings = new ArrayList<>(oringalGroupedEdges.size());
for (FGEdge edge : oringalGroupedEdges) { for (FGEdge edge : oringalGroupedEdges) {
originalEdgeStrings.add(edge.toString()); originalEdgeStrings.add(edge.toString());
@@ -136,7 +136,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
/** /**
* Tests that the app will recognize the case where the entry point to a function is invalid, * Tests that the app will recognize the case where the entry point to a function is invalid,
* and generate the appropriate error message when trying to create a function graph. * and generate the appropriate error message when trying to create a function graph.
* *
* Step 1: Make sure the function graph window is closed. * Step 1: Make sure the function graph window is closed.
* Step 2: Clear the entry point bytes * Step 2: Clear the entry point bytes
* Step 3: Open the function graph window to generate the graph again. * Step 3: Open the function graph window to generate the graph again.
@@ -144,8 +144,8 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
*/ */
public void testInvalidFunctionEntryPoint() { public void testInvalidFunctionEntryPoint() {
// First thing we need to do is close the function graph window. It's opened on // First thing we need to do is close the function graph window. It's opened on
// startup by default in this test suite but we want it closed until we clear the // startup by default in this test suite but we want it closed until we clear the
// function code bytes. // function code bytes.
this.getFunctionGraphController().getProvider().closeComponent(); this.getFunctionGraphController().getProvider().closeComponent();
@@ -170,7 +170,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
runSwing(() -> clearAction.actionPerformed(context)); runSwing(() -> clearAction.actionPerformed(context));
waitForBusyTool(tool); waitForBusyTool(tool);
// Open the window; the tool will try to generate a new graph but should fail and generate // Open the window; the tool will try to generate a new graph but should fail and generate
// an error message. // an error message.
DockingActionIf openGraphAction; DockingActionIf openGraphAction;
openGraphAction = getAction(fgp, "Display Function Graph"); openGraphAction = getAction(fgp, "Display Function Graph");
@@ -186,8 +186,8 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
@Test @Test
public void testGroupAndUngroup_WhenOneOfTheGroupIsAGroup() { public void testGroupAndUngroup_WhenOneOfTheGroupIsAGroup() {
// //
// This test seeks to ensure that you can group a selection of vertices when one of // This test seeks to ensure that you can group a selection of vertices when one of
// those vertices is itself a group. // those vertices is itself a group.
// //
@@ -217,7 +217,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
group(secondUngroupedVertices); group(secondUngroupedVertices);
// 5 edges expected: // 5 edges expected:
// -ungrouped vertex: 1 in, 1 out // -ungrouped vertex: 1 in, 1 out
// -grouped vertex : 1 in, 2 out // -grouped vertex : 1 in, 2 out
expectedGroupedEdgeCount = 5; expectedGroupedEdgeCount = 5;
@@ -248,7 +248,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
@Test @Test
public void testGroupingPersistence_WhenOneOfTheGroupIsAGroup() throws Exception { public void testGroupingPersistence_WhenOneOfTheGroupIsAGroup() throws Exception {
// //
// This test seeks to ensure that groups within groups are persisted and restored. // This test seeks to ensure that groups within groups are persisted and restored.
// //
@@ -284,7 +284,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
//printEdges(outerUngroupedEdges); //printEdges(outerUngroupedEdges);
group(outerUngroupedVertices); group(outerUngroupedVertices);
// 5 edges expected: // 5 edges expected:
// -ungrouped vertex: 1 in, 1 out // -ungrouped vertex: 1 in, 1 out
// -grouped vertex : 1 in, 2 out // -grouped vertex : 1 in, 2 out
expectedGroupedEdgeCount = 5; expectedGroupedEdgeCount = 5;
@@ -353,8 +353,8 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
@Test @Test
public void testUngroupAll() { public void testUngroupAll() {
// //
// Group some vertices and then group that vertex with some vertices to create a // Group some vertices and then group that vertex with some vertices to create a
// recursively/nested grouping. Also create a second top-level group. Make sure the // recursively/nested grouping. Also create a second top-level group. Make sure the
// ungroup all action will restore the original graph. // ungroup all action will restore the original graph.
// //
@@ -434,8 +434,8 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// //
// The coloring algorithm: // The coloring algorithm:
// 1) If the grouped vertices are not colored, then use the default group color // 1) If the grouped vertices are not colored, then use the default group color
// 2) If the grouped vertices are colored, but not all the same color, // 2) If the grouped vertices are colored, but not all the same color,
// then use the default group color= // then use the default group color=
// 3) If all grouped vertices share the same color, then make the group that color // 3) If all grouped vertices share the same color, then make the group that color
// //
// This test is for 1) // This test is for 1)
@@ -459,7 +459,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// //
ungroup(group); ungroup(group);
// //
// Test the grouped vertices colors // Test the grouped vertices colors
// //
verifyDefaultColor(v1, v2); verifyDefaultColor(v1, v2);
@@ -470,8 +470,8 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// //
// The coloring algorithm: // The coloring algorithm:
// 1) If the grouped vertices are not colored, then use the default group color // 1) If the grouped vertices are not colored, then use the default group color
// 2) If the grouped vertices are colored, but not all the same color, // 2) If the grouped vertices are colored, but not all the same color,
// then use the default group color= // then use the default group color=
// 3) If all grouped vertices share the same color, then make the group that color // 3) If all grouped vertices share the same color, then make the group that color
// //
// This test is for 2) // This test is for 2)
@@ -500,7 +500,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// //
ungroup(group); ungroup(group);
// //
// Test the grouped vertices colors // Test the grouped vertices colors
// //
verifyColor(v1, newColor); verifyColor(v1, newColor);
@@ -512,8 +512,8 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// //
// The coloring algorithm: // The coloring algorithm:
// 1) If the grouped vertices are not colored, then use the default group color // 1) If the grouped vertices are not colored, then use the default group color
// 2) If the grouped vertices are colored, but not all the same color, // 2) If the grouped vertices are colored, but not all the same color,
// then use the default group color= // then use the default group color=
// 3) If all grouped vertices share the same color, then make the group that color // 3) If all grouped vertices share the same color, then make the group that color
// //
// This test is for 3) // This test is for 3)
@@ -543,7 +543,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// //
ungroup(group); ungroup(group);
// //
// Test the grouped vertices colors // Test the grouped vertices colors
// //
verifyColor(v1, newColor); verifyColor(v1, newColor);
@@ -572,7 +572,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// //
ungroup(group); ungroup(group);
// //
// Test the grouped vertices colors // Test the grouped vertices colors
// //
verifyColor(v1, newGroupColor); verifyColor(v1, newGroupColor);
@@ -607,7 +607,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// //
ungroup(group); ungroup(group);
// //
// Test the grouped vertices colors // Test the grouped vertices colors
// //
verifyColor(v1, newGroupColor); verifyColor(v1, newGroupColor);
@@ -642,7 +642,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// //
ungroup(group); ungroup(group);
// //
// Test the grouped vertices colors // Test the grouped vertices colors
// //
verifyColor(v1, newGroupColor); verifyColor(v1, newGroupColor);
@@ -666,7 +666,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
Color newGroupColor = Color.CYAN; Color newGroupColor = Color.CYAN;
color(group, newGroupColor); color(group, newGroupColor);
// //
// Trigger persistence // Trigger persistence
// //
Address groupAddress = group.getVertexAddress(); Address groupAddress = group.getVertexAddress();
@@ -683,7 +683,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// //
ungroup(group); ungroup(group);
// //
// Test the grouped vertices colors // Test the grouped vertices colors
// //
v1 = vertex("01002d06"); v1 = vertex("01002d06");
@@ -703,7 +703,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
FGVertex v2 = vertex("01002d0f"); FGVertex v2 = vertex("01002d0f");
GroupedFunctionGraphVertex group = group("A", v1, v2); GroupedFunctionGraphVertex group = group("A", v1, v2);
// //
// Trigger persistence // Trigger persistence
// //
Address groupAddress = group.getVertexAddress(); Address groupAddress = group.getVertexAddress();
@@ -720,7 +720,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// //
ungroup(group); ungroup(group);
// //
// Test the grouped vertices colors // Test the grouped vertices colors
// //
v1 = vertex("01002d06"); v1 = vertex("01002d06");
@@ -737,16 +737,16 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// //
// Color just one of the vertices // Color just one of the vertices
// //
Color newColor = Color.RED; Color newColor = Color.RED;
color(v1, newColor); color(v1, newColor);
// //
// Group a node // Group a node
// //
GroupedFunctionGraphVertex group = group("A", v1, v2); GroupedFunctionGraphVertex group = group("A", v1, v2);
// //
// Trigger persistence // Trigger persistence
// //
Address groupAddress = group.getVertexAddress(); Address groupAddress = group.getVertexAddress();
@@ -763,7 +763,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// //
ungroup(group); ungroup(group);
// //
// Test the grouped vertices colors // Test the grouped vertices colors
// //
v1 = vertex("01002d06"); v1 = vertex("01002d06");
@@ -781,16 +781,16 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// //
// Color just one of the vertices // Color just one of the vertices
// //
Color newColor = Color.RED; Color newColor = Color.RED;
color(v1, newColor); color(v1, newColor);
// //
// Group a node // Group a node
// //
GroupedFunctionGraphVertex group = group("A", v1, v2); GroupedFunctionGraphVertex group = group("A", v1, v2);
// //
// Trigger reset // Trigger reset
// //
Address groupAddress = group.getVertexAddress(); Address groupAddress = group.getVertexAddress();
@@ -800,9 +800,9 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
// Make sure the group is gone // Make sure the group is gone
// //
FGVertex vertex = graphData.getFunctionGraph().getVertexForAddress(groupAddress); FGVertex vertex = graphData.getFunctionGraph().getVertexForAddress(groupAddress);
assertFalse(vertex instanceof GroupedFunctionGraphVertex);// the group has been removed assertFalse(vertex instanceof GroupedFunctionGraphVertex);// the group has been removed
// //
// Test the grouped vertices colors // Test the grouped vertices colors
// //
v1 = vertex("01002d06"); v1 = vertex("01002d06");
@@ -838,11 +838,6 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
assertEquals(alpha, alphAfterGroup); assertEquals(alpha, alphAfterGroup);
} }
@Test
public void testSymbolAddedWhenGrouped_SymbolOutsideOfGroupNode() {
// TODO
}
//================================================================================================== //==================================================================================================
// Private Methods // Private Methods
//================================================================================================== //==================================================================================================
@@ -853,85 +848,85 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
FGData graphData = graphFunction("01002cf5"); FGData graphData = graphFunction("01002cf5");
FunctionGraph functionGraph = graphData.getFunctionGraph(); FunctionGraph functionGraph = graphData.getFunctionGraph();
Graph<FGVertex, FGEdge> graph = functionGraph; Graph<FGVertex, FGEdge> graph = functionGraph;
Set<FGVertex> ungroupedVertices = selectVertices( functionGraph, Set<FGVertex> ungroupedVertices = selectVertices( functionGraph,
"01002d2b" /* Another Local*/, "01002d2b" /* Another Local*/,
"01002d1f" /* MyLocal */); "01002d1f" /* MyLocal */);
Set<FGEdge> ungroupedEdges = getEdges(graph, ungroupedVertices); Set<FGEdge> ungroupedEdges = getEdges(graph, ungroupedVertices);
assertEquals("Did not grab all known edges for vertices", 4, ungroupedEdges.size()); assertEquals("Did not grab all known edges for vertices", 4, ungroupedEdges.size());
group(ungroupedVertices); group(ungroupedVertices);
assertVerticesRemoved(graph, ungroupedVertices); assertVerticesRemoved(graph, ungroupedVertices);
assertEdgesRemoved(graph, ungroupedEdges); assertEdgesRemoved(graph, ungroupedEdges);
// -1 because one one of the edges was between two of the vertices being grouped // -1 because one one of the edges was between two of the vertices being grouped
int expectedGroupedEdgeCount = ungroupedEdges.size() - 1; int expectedGroupedEdgeCount = ungroupedEdges.size() - 1;
GroupedFunctionGraphVertex groupedVertex = GroupedFunctionGraphVertex groupedVertex =
validateNewGroupedVertexFromVertices(functionGraph, ungroupedVertices, validateNewGroupedVertexFromVertices(functionGraph, ungroupedVertices,
expectedGroupedEdgeCount); expectedGroupedEdgeCount);
ungroup(groupedVertex); ungroup(groupedVertex);
assertVertexRemoved(graph, groupedVertex); assertVertexRemoved(graph, groupedVertex);
assertVerticesAdded(graph, ungroupedVertices); assertVerticesAdded(graph, ungroupedVertices);
assertEdgesAdded(functionGraph, ungroupedEdges); assertEdgesAdded(functionGraph, ungroupedEdges);
assertSelected(ungroupedVertices); assertSelected(ungroupedVertices);
} }
@Override @Override
protected void doTestRestoringWhenCodeBlocksHaveChanged_WillRegroup() { protected void doTestRestoringWhenCodeBlocksHaveChanged_WillRegroup() {
// //
// Tests the behavior of how group vertices are restored when one or more of the vertices // Tests the behavior of how group vertices are restored when one or more of the vertices
// inside of the grouped vertex is no longer available when the graph attempts to restore // inside of the grouped vertex is no longer available when the graph attempts to restore
// the group vertex user settings (i.e., when restarting Ghidra, the previously grouped // the group vertex user settings (i.e., when restarting Ghidra, the previously grouped
// vertices should reappear). // vertices should reappear).
// //
// In this test, we will be mutating a group of 3 nodes such // In this test, we will be mutating a group of 3 nodes such
// that one of the nodes has been split into two. This leaves 2 vertices to // that one of the nodes has been split into two. This leaves 2 vertices to
// be found by the regrouping algorithm. Furthermore, the regrouping *will* still // be found by the regrouping algorithm. Furthermore, the regrouping *will* still
// take place, as at least two vertices cannot be found. // take place, as at least two vertices cannot be found.
// //
// //
// Pick a function and group some nodes. // Pick a function and group some nodes.
// //
FGData graphData = graphFunction("01002cf5"); FGData graphData = graphFunction("01002cf5");
FunctionGraph functionGraph = graphData.getFunctionGraph(); FunctionGraph functionGraph = graphData.getFunctionGraph();
Set<FGVertex> ungroupedVertices = selectVertices(functionGraph, Set<FGVertex> ungroupedVertices = selectVertices(functionGraph,
"01002d11" /* LAB_01002d11 */, "01002cf5" /* ghidra */, "01002d1f" /* MyLocal */); "01002d11" /* LAB_01002d11 */, "01002cf5" /* ghidra */, "01002d1f" /* MyLocal */);
group(ungroupedVertices); group(ungroupedVertices);
// 5 edges expected: // 5 edges expected:
// -01002cf5: 2 out // -01002cf5: 2 out
// -01002d11: 2 in, (1 out that was removed) // -01002d11: 2 in, (1 out that was removed)
// -01002d1f: 2 out (1 in that was removed) // -01002d1f: 2 out (1 in that was removed)
int expectedGroupedEdgeCount = 6; int expectedGroupedEdgeCount = 6;
GroupedFunctionGraphVertex groupedVertex = validateNewGroupedVertexFromVertices( GroupedFunctionGraphVertex groupedVertex = validateNewGroupedVertexFromVertices(
functionGraph, ungroupedVertices, expectedGroupedEdgeCount); functionGraph, ungroupedVertices, expectedGroupedEdgeCount);
AddressSetView addresses = groupedVertex.getAddresses(); AddressSetView addresses = groupedVertex.getAddresses();
Address minAddress = addresses.getMinAddress(); Address minAddress = addresses.getMinAddress();
Address maxAddress = addresses.getMaxAddress(); Address maxAddress = addresses.getMaxAddress();
// //
// Ideally, we would like to save, close and re-open the program so that we can get // Ideally, we would like to save, close and re-open the program so that we can get
// a round-trip saving and reloading. However, in the test environment, we cannot save // a round-trip saving and reloading. However, in the test environment, we cannot save
// our programs. So, we will instead just navigate away from the current function, clear // our programs. So, we will instead just navigate away from the current function, clear
// the cache (to make sure that we read the settings again), and then verify that the // the cache (to make sure that we read the settings again), and then verify that the
// data saved in the program has been used to re-group. // data saved in the program has been used to re-group.
// //
graphFunction("0100415a"); graphFunction("0100415a");
clearCache(); clearCache();
// //
// Add a label to trigger a code block change // Add a label to trigger a code block change
// //
Address labelAddress = createLabel("01002d18");// in the middle of the LAB_01002d11 code block Address labelAddress = createLabel("01002d18");// in the middle of the LAB_01002d11 code block
// //
// Relaunch the graph, which will use the above persisted group settings... // Relaunch the graph, which will use the above persisted group settings...
// //
@@ -941,22 +936,22 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
FGVertex expectedGroupVertex = functionGraph.getVertexForAddress(minAddress); FGVertex expectedGroupVertex = functionGraph.getVertexForAddress(minAddress);
assertTrue(expectedGroupVertex instanceof GroupedFunctionGraphVertex); assertTrue(expectedGroupVertex instanceof GroupedFunctionGraphVertex);
assertEquals(maxAddress, expectedGroupVertex.getAddresses().getMaxAddress()); assertEquals(maxAddress, expectedGroupVertex.getAddresses().getMaxAddress());
// ...we expect that the two original grouped vertices have again been grouped... // ...we expect that the two original grouped vertices have again been grouped...
FGVertex splitVertex = FGVertex splitVertex =
functionGraph.getVertexForAddress(getAddress("01002d11") /* LAB_01002d11 */); functionGraph.getVertexForAddress(getAddress("01002d11") /* LAB_01002d11 */);
assertTrue("The split vertex should not have been regrouped", assertTrue("The split vertex should not have been regrouped",
!(splitVertex instanceof GroupedFunctionGraphVertex)); !(splitVertex instanceof GroupedFunctionGraphVertex));
FGVertex unchangedVertex = FGVertex unchangedVertex =
functionGraph.getVertexForAddress(getAddress("01002cf5") /* ghidra */); functionGraph.getVertexForAddress(getAddress("01002cf5") /* ghidra */);
assertTrue("An unchanged vertex should have been regrouped: " + unchangedVertex, assertTrue("An unchanged vertex should have been regrouped: " + unchangedVertex,
(unchangedVertex instanceof GroupedFunctionGraphVertex)); (unchangedVertex instanceof GroupedFunctionGraphVertex));
unchangedVertex = functionGraph.getVertexForAddress(getAddress("01002d1f") /* MyLocal */); unchangedVertex = functionGraph.getVertexForAddress(getAddress("01002d1f") /* MyLocal */);
assertTrue("An unchanged vertex should have been regrouped: " + unchangedVertex, assertTrue("An unchanged vertex should have been regrouped: " + unchangedVertex,
(unchangedVertex instanceof GroupedFunctionGraphVertex)); (unchangedVertex instanceof GroupedFunctionGraphVertex));
// ...but the newly created code block has not // ...but the newly created code block has not
FGVertex newlyCreatedVertex = functionGraph.getVertexForAddress(labelAddress); FGVertex newlyCreatedVertex = functionGraph.getVertexForAddress(labelAddress);
assertNotNull(newlyCreatedVertex); assertNotNull(newlyCreatedVertex);
@@ -966,34 +961,34 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest {
protected void doTestSymbolAddedWhenGrouped_SymbolInsideOfGroupNode() { protected void doTestSymbolAddedWhenGrouped_SymbolInsideOfGroupNode() {
// //
// By default, if the FunctionGraph detects a symbol addition to one of the code blocks // By default, if the FunctionGraph detects a symbol addition to one of the code blocks
// in the graph, then it will split the affected vertex (tested elsewhere). // in the graph, then it will split the affected vertex (tested elsewhere).
// However, if the affected vertex is grouped, then the FG will not split the node, but // However, if the affected vertex is grouped, then the FG will not split the node, but
// should still show the 'stale' indicator. // should still show the 'stale' indicator.
// //
// //
// Pick a function and group some nodes. // Pick a function and group some nodes.
// //
FGData graphData = graphFunction("01002cf5"); FGData graphData = graphFunction("01002cf5");
FunctionGraph functionGraph = graphData.getFunctionGraph(); FunctionGraph functionGraph = graphData.getFunctionGraph();
Set<FGVertex> ungroupedVertices = Set<FGVertex> ungroupedVertices =
selectVertices(functionGraph, "01002d11" /* LAB_01002d11 */, "01002cf5" /* ghidra */); selectVertices(functionGraph, "01002d11" /* LAB_01002d11 */, "01002cf5" /* ghidra */);
group(ungroupedVertices); group(ungroupedVertices);
// 5 edges expected: // 5 edges expected:
// -01002cf5: 2 out // -01002cf5: 2 out
// -01002cf5: 2 in, 1 out // -01002cf5: 2 in, 1 out
int expectedGroupedEdgeCount = 5; int expectedGroupedEdgeCount = 5;
GroupedFunctionGraphVertex groupedVertex = validateNewGroupedVertexFromVertices( GroupedFunctionGraphVertex groupedVertex = validateNewGroupedVertexFromVertices(
functionGraph, ungroupedVertices, expectedGroupedEdgeCount); functionGraph, ungroupedVertices, expectedGroupedEdgeCount);
// //
// Add a label to trigger a code block change // Add a label to trigger a code block change
// //
Address labelAddress = createLabel("01002d18");// in the middle of the LAB_01002d11 code block Address labelAddress = createLabel("01002d18");// in the middle of the LAB_01002d11 code block
// //
// Make sure the newly created code block does not have a corresponding vertex // Make sure the newly created code block does not have a corresponding vertex
// //
@@ -36,7 +36,7 @@ import resources.icons.EmptyIcon;
* drop-down icon that allows users to change the state of the button. Also, by default, as * drop-down icon that allows users to change the state of the button. Also, by default, as
* the user presses the button, it will execute the action corresponding to the current * the user presses the button, it will execute the action corresponding to the current
* state. * state.
* *
* <p>Warning: if you use this action in a toolbar, then be sure to call the * <p>Warning: if you use this action in a toolbar, then be sure to call the
* {@link #MultiStateDockingAction(String, String, boolean) correct constructor}. If you call * {@link #MultiStateDockingAction(String, String, boolean) correct constructor}. If you call
* another constructor, or pass false for this boolean above, your * another constructor, or pass false for this boolean above, your
@@ -50,7 +50,7 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
private static Icon EMPTY_ICON = new EmptyIcon(16, 16); private static Icon EMPTY_ICON = new EmptyIcon(16, 16);
private List<ActionState<T>> actionStates = new ArrayList<>(); private List<ActionState<T>> actionStates = new ArrayList<>();
private int currentStateIndex = 0; private int currentStateIndex = -1;
private MultiActionDockingActionIf multiActionGenerator; private MultiActionDockingActionIf multiActionGenerator;
private MultipleActionDockingToolbarButton multipleButton; private MultipleActionDockingToolbarButton multipleButton;
@@ -66,7 +66,7 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
/** /**
* Call this constructor with this action will not be added to a toolbar * Call this constructor with this action will not be added to a toolbar
* *
* @param name the action name * @param name the action name
* @param owner the owner * @param owner the owner
* @see #MultiStateDockingAction(String, String, boolean) * @see #MultiStateDockingAction(String, String, boolean)
@@ -78,7 +78,7 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
/** /**
* Use this constructor explicitly when this action is used in a toolbar, passing true * Use this constructor explicitly when this action is used in a toolbar, passing true
* for <code>isToolbarAction</code> (see the javadoc header note). * for <code>isToolbarAction</code> (see the javadoc header note).
* *
* @param name the action name * @param name the action name
* @param owner the owner * @param owner the owner
* @param isToolbarAction true if this action is a toolbar action * @param isToolbarAction true if this action is a toolbar action
@@ -110,7 +110,7 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
* <p> * <p>
* Also, if the parameter is true, then the button will behave like a button in terms of * Also, if the parameter is true, then the button will behave like a button in terms of
* mouse feedback. If false, then the button will behave more like a label. * mouse feedback. If false, then the button will behave more like a label.
* *
* @param doPerformAction true to call {@link #doActionPerformed(ActionContext)} when the * @param doPerformAction true to call {@link #doActionPerformed(ActionContext)} when the
* user presses the button for this action (not the drop-down menu; see above) * user presses the button for this action (not the drop-down menu; see above)
*/ */
@@ -133,7 +133,7 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
* default, the popup menu items will use the icons as provided by the {@link ActionState}. * default, the popup menu items will use the icons as provided by the {@link ActionState}.
* By passing true to this method, icons will not be used in the popup menu. Instead, a * By passing true to this method, icons will not be used in the popup menu. Instead, a
* checkbox icon will be used to show the active action state. * checkbox icon will be used to show the active action state.
* *
* @param useCheckboxForIcons true to use a checkbox * @param useCheckboxForIcons true to use a checkbox
*/ */
public void setUseCheckboxForIcons(boolean useCheckboxForIcons) { public void setUseCheckboxForIcons(boolean useCheckboxForIcons) {
@@ -144,7 +144,7 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
* Sets the icon to use if the active action state does not supply an icon. This is useful if * Sets the icon to use if the active action state does not supply an icon. This is useful if
* you wish for your action states to not use icon, but desire the action itself to have an * you wish for your action states to not use icon, but desire the action itself to have an
* icon. * icon.
* *
* @param icon the icon * @param icon the icon
*/ */
public void setDefaultIcon(Icon icon) { public void setDefaultIcon(Icon icon) {
@@ -165,7 +165,7 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
* This is the callback to be overridden when the child wishes to respond to user button * This is the callback to be overridden when the child wishes to respond to user button
* presses that are on the button and not the drop-down. This will only be called if * presses that are on the button and not the drop-down. This will only be called if
* {@link #performActionOnPrimaryButtonClick} is true. * {@link #performActionOnPrimaryButtonClick} is true.
* *
* @param context the action context * @param context the action context
*/ */
protected void doActionPerformed(ActionContext context) { protected void doActionPerformed(ActionContext context) {
@@ -270,10 +270,7 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
} }
currentStateIndex = indexOf; currentStateIndex = indexOf;
// we set the icon here to handle the odd case where this action is not used in a toolbar setButtonState(actionState);
if (multipleButton != null) {
setButtonState(actionState);
}
ToolBarData tbd = getToolBarData(); ToolBarData tbd = getToolBarData();
tbd.setIcon(getIcon(actionState)); tbd.setIcon(getIcon(actionState));
@@ -317,6 +314,16 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
private void setButtonState(ActionState<T> actionState) { private void setButtonState(ActionState<T> actionState) {
if (multipleButton == null) {
return;
}
if (actionState == null) {
multipleButton.setIcon(null);
multipleButton.setToolTipText(null);
return;
}
Icon icon = getIcon(actionState); Icon icon = getIcon(actionState);
multipleButton.setIcon(icon); multipleButton.setIcon(icon);
multipleButton.setToolTipText(actionState.getName()); multipleButton.setToolTipText(actionState.getName());
@@ -337,6 +344,9 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
} }
public String getToolTipText() { public String getToolTipText() {
if (actionStates.isEmpty()) {
return getName() + ": <no action states installed>";
}
return getName() + ": " + getCurrentState().getName(); return getName() + ": " + getCurrentState().getName();
} }
@@ -355,8 +365,7 @@ public abstract class MultiStateDockingAction<T> extends DockingAction {
setSelected(isSelected); setSelected(isSelected);
setMenuBarData( setMenuBarData(new MenuData(new String[] { actionState.getName() }));
new MenuData(new String[] { actionState.getName() }));
HelpLocation helpLocation = actionState.getHelpLocation(); HelpLocation helpLocation = actionState.getHelpLocation();
if (helpLocation != null) { if (helpLocation != null) {
setHelpLocation(helpLocation); setHelpLocation(helpLocation);