mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +02:00
GP-1982 - Function Graph
This commit is contained in:
parent
f808431251
commit
16e32317f0
18 changed files with 2906 additions and 47 deletions
|
@ -24,6 +24,7 @@ import org.jdom.Element;
|
|||
import docking.ComponentPlaceholder;
|
||||
import docking.DockingWindowManager;
|
||||
import docking.options.editor.GhidraColorChooser;
|
||||
import docking.theme.GColor;
|
||||
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
|
||||
import ghidra.app.plugin.core.functiongraph.mvc.FunctionGraphVertexAttributes;
|
||||
import ghidra.framework.options.SaveState;
|
||||
|
@ -111,8 +112,7 @@ class IndependentColorProvider implements FGColorProvider {
|
|||
List<Element> colorElements = xmlElement.getChildren("COLOR");
|
||||
for (Element element : colorElements) {
|
||||
String rgbString = element.getAttributeValue("RGB");
|
||||
int rgb = Integer.parseInt(rgbString);
|
||||
recentColorCache.addColor(new Color(rgb, true));
|
||||
recentColorCache.addColor(Color.decode(rgbString));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -138,7 +138,9 @@ class IndependentColorProvider implements FGColorProvider {
|
|||
//==================================================================================================
|
||||
private class RecentColorCache extends LinkedHashMap<Color, Color> implements Iterable<Color> {
|
||||
private static final int MAX_SIZE = 10;
|
||||
private Color mostRecentColor = Color.blue;
|
||||
|
||||
// not sure what the default color should be here
|
||||
private Color mostRecentColor = new GColor("color.bg");
|
||||
|
||||
RecentColorCache() {
|
||||
super(16, 0.75f, true);
|
||||
|
|
|
@ -37,7 +37,6 @@ import ghidra.graph.viewer.*;
|
|||
import ghidra.graph.viewer.layout.LayoutListener.ChangeType;
|
||||
import ghidra.graph.viewer.layout.LayoutProvider;
|
||||
import ghidra.graph.viewer.layout.VisualGraphLayout;
|
||||
import ghidra.graph.viewer.options.VisualGraphOptions;
|
||||
import ghidra.graph.viewer.renderer.VisualGraphEdgeLabelRenderer;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
|
@ -219,14 +218,14 @@ public class FGComponent extends GraphComponent<FGVertex, FGEdge, FunctionGraph>
|
|||
// note: this label renderer is the stamp for the label; we use another edge label
|
||||
// renderer inside of the VisualGraphRenderer
|
||||
VisualGraphEdgeLabelRenderer edgeLabelRenderer =
|
||||
new VisualGraphEdgeLabelRenderer(Color.BLACK);
|
||||
edgeLabelRenderer.setNonPickedForegroundColor(Color.LIGHT_GRAY);
|
||||
new VisualGraphEdgeLabelRenderer(new GColor("color.fg.label.picked"));
|
||||
edgeLabelRenderer.setNonPickedForegroundColor(new GColor("color.fg.label.non-picked"));
|
||||
edgeLabelRenderer.setRotateEdgeLabels(false);
|
||||
renderContext.setEdgeLabelRenderer(edgeLabelRenderer);
|
||||
|
||||
viewer.setGraphOptions(vgOptions);
|
||||
Color bgColor = vgOptions.getGraphBackgroundColor();
|
||||
if (bgColor.equals(VisualGraphOptions.DEFAULT_GRAPH_BACKGROUND_COLOR)) {
|
||||
if (vgOptions.isDefaultBackgroundColor(bgColor)) {
|
||||
|
||||
// Give user notice when seeing the graph for a non-function (such as an undefined
|
||||
// function), as this is typical for Ghidra UI widgets.
|
||||
|
|
|
@ -26,6 +26,7 @@ import edu.uci.ics.jung.visualization.picking.PickedInfo;
|
|||
import ghidra.app.plugin.core.functiongraph.graph.FGVertexType;
|
||||
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
|
||||
import ghidra.program.util.ProgramSelection;
|
||||
import ghidra.util.ColorUtils;
|
||||
|
||||
public class FGVertexPickableBackgroundPaintTransformer implements Function<FGVertex, Paint> {
|
||||
|
||||
|
@ -37,8 +38,7 @@ public class FGVertexPickableBackgroundPaintTransformer implements Function<FGVe
|
|||
private final Color pickedExitColor;
|
||||
|
||||
private static Color mix(Color c1, Color c2) {
|
||||
return new Color((c1.getRed() + c2.getRed()) / 2, (c1.getGreen() + c2.getGreen()) / 2,
|
||||
(c1.getBlue() + c2.getBlue()) / 2);
|
||||
return ColorUtils.blend(c1, c2, .5f);
|
||||
}
|
||||
|
||||
public FGVertexPickableBackgroundPaintTransformer(PickedInfo<FGVertex> info, Color pickedColor,
|
||||
|
|
|
@ -20,6 +20,7 @@ import java.awt.event.MouseEvent;
|
|||
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import docking.theme.GThemeDefaults.Colors;
|
||||
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
|
||||
import ghidra.app.plugin.core.functiongraph.graph.FGVertexType;
|
||||
import ghidra.app.plugin.core.functiongraph.mvc.FGController;
|
||||
|
@ -39,7 +40,7 @@ import ghidra.program.util.ProgramSelection;
|
|||
*/
|
||||
public interface FGVertex extends VisualVertex {
|
||||
|
||||
static final Color TOOLTIP_BACKGROUND_COLOR = new Color(255, 255, 230);
|
||||
static final Color TOOLTIP_BACKGROUND_COLOR = Colors.TOOLTIP_BACKGROUND;
|
||||
|
||||
public FGVertex cloneVertex(FGController newController);
|
||||
|
||||
|
|
|
@ -21,11 +21,13 @@ import java.util.Set;
|
|||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.BevelBorder;
|
||||
import javax.swing.border.Border;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.GenericHeader;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.ToolBarData;
|
||||
import docking.theme.GColor;
|
||||
import ghidra.app.plugin.core.functiongraph.FunctionGraphPlugin;
|
||||
import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
|
||||
import ghidra.app.plugin.core.functiongraph.mvc.FGController;
|
||||
|
@ -137,10 +139,11 @@ public class GroupedFunctionGraphComponentPanel extends AbstractGraphComponentPa
|
|||
add(genericHeader, BorderLayout.NORTH);
|
||||
add(contentPanel, BorderLayout.CENTER);
|
||||
|
||||
BevelBorder beveledBorder =
|
||||
(BevelBorder) BorderFactory.createBevelBorder(BevelBorder.RAISED,
|
||||
new Color(225, 225, 225), new Color(155, 155, 155), new Color(96, 96, 96),
|
||||
new Color(0, 0, 0));
|
||||
Border beveledBorder =
|
||||
BorderFactory.createBevelBorder(BevelBorder.RAISED,
|
||||
new GColor("color.border.bevel.highlight"),
|
||||
new GColor("color.border.bevel.shadow"));
|
||||
|
||||
setBorder(beveledBorder);
|
||||
|
||||
createActions();
|
||||
|
|
|
@ -25,7 +25,6 @@ import ghidra.app.plugin.core.functiongraph.graph.FGEdge;
|
|||
import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph;
|
||||
import ghidra.app.plugin.core.functiongraph.mvc.FGController;
|
||||
import ghidra.app.plugin.core.functiongraph.mvc.FunctionGraphOptions;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.xml.XmlUtilities;
|
||||
|
||||
/**
|
||||
|
@ -93,26 +92,11 @@ class GroupedVertexInfo extends VertexInfo {
|
|||
return FunctionGraphOptions.DEFAULT_GROUP_BACKGROUND_COLOR;
|
||||
}
|
||||
|
||||
StringTokenizer tokenizer = new StringTokenizer(colorString, ",");
|
||||
int tokenCount = tokenizer.countTokens();
|
||||
if (tokenCount != 4) {
|
||||
return FunctionGraphOptions.DEFAULT_GROUP_BACKGROUND_COLOR;
|
||||
}
|
||||
|
||||
String redString = tokenizer.nextToken();
|
||||
String greenString = tokenizer.nextToken();
|
||||
String blueString = tokenizer.nextToken();
|
||||
String alphaString = tokenizer.nextToken();
|
||||
|
||||
try {
|
||||
int red = Integer.parseInt(redString);
|
||||
int green = Integer.parseInt(greenString);
|
||||
int blue = Integer.parseInt(blueString);
|
||||
int alpha = Integer.parseInt(alphaString);
|
||||
return new Color(red, green, blue, alpha);
|
||||
return Color.decode(colorString);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
Msg.error(this, "Unexpected exception parsing number", e);
|
||||
// must be an old format
|
||||
return FunctionGraphOptions.DEFAULT_GROUP_BACKGROUND_COLOR;
|
||||
}
|
||||
}
|
||||
|
@ -121,8 +105,9 @@ class GroupedVertexInfo extends VertexInfo {
|
|||
if (color == null) {
|
||||
return encodeColor(FunctionGraphOptions.DEFAULT_GROUP_BACKGROUND_COLOR);
|
||||
}
|
||||
return color.getRed() + "," + color.getGreen() + "," + color.getBlue() + "," +
|
||||
color.getAlpha();
|
||||
|
||||
int rgb = color.getRGB();
|
||||
return Integer.toString(rgb);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -26,11 +26,13 @@ import java.util.List;
|
|||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.BevelBorder;
|
||||
import javax.swing.border.Border;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.GenericHeader;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.ToolBarData;
|
||||
import docking.theme.GColor;
|
||||
import docking.theme.GThemeDefaults.Colors;
|
||||
import docking.widgets.fieldpanel.FieldPanel;
|
||||
import docking.widgets.fieldpanel.Layout;
|
||||
|
@ -134,10 +136,10 @@ public class ListingGraphComponentPanel extends AbstractGraphComponentPanel {
|
|||
|
||||
add(listingPanel, BorderLayout.CENTER);
|
||||
|
||||
BevelBorder beveledBorder =
|
||||
(BevelBorder) BorderFactory.createBevelBorder(BevelBorder.RAISED,
|
||||
new Color(225, 225, 225), new Color(155, 155, 155), new Color(96, 96, 96),
|
||||
new Color(0, 0, 0));
|
||||
Border beveledBorder =
|
||||
BorderFactory.createBevelBorder(BevelBorder.RAISED,
|
||||
new GColor("color.border.bevel.highlight"),
|
||||
new GColor("color.border.bevel.shadow"));
|
||||
setBorder(beveledBorder);
|
||||
|
||||
addKeyListener(new FieldPanelKeyListener());
|
||||
|
@ -542,7 +544,7 @@ public class ListingGraphComponentPanel extends AbstractGraphComponentPanel {
|
|||
|
||||
previewListingPanel.getFieldPanel()
|
||||
.setBackgroundColorModel(
|
||||
new HighlightingColorModel(address, getColorForEdge(edge)));
|
||||
new HighlightingColorModel(address, getToolTipColorForEdge(edge)));
|
||||
}
|
||||
|
||||
private void initializeToolTipComponent(Address goToAddress) {
|
||||
|
@ -556,9 +558,13 @@ public class ListingGraphComponentPanel extends AbstractGraphComponentPanel {
|
|||
previewListingPanel.getFieldPanel().setCursorOn(false);
|
||||
}
|
||||
|
||||
private Color getColorForEdge(FGEdge edge) {
|
||||
private Color getToolTipColorForEdge(FGEdge edge) {
|
||||
FunctionGraphOptions options = controller.getFunctionGraphOptions();
|
||||
Color c = options.getColor(edge.getFlowType());
|
||||
return withAlpha(c, 125);
|
||||
}
|
||||
|
||||
private Color withAlpha(Color c, int alpha) {
|
||||
return new Color(c.getRed(), c.getGreen(), c.getBlue(), 125);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import docking.ActionContext;
|
|||
import docking.action.*;
|
||||
import docking.menu.MultiActionDockingAction;
|
||||
import docking.menu.MultipleActionDockingToolbarButton;
|
||||
import docking.theme.GColor;
|
||||
import ghidra.app.plugin.core.functiongraph.FGColorProvider;
|
||||
import ghidra.app.plugin.core.functiongraph.FunctionGraphPlugin;
|
||||
import ghidra.app.plugin.core.functiongraph.mvc.FGController;
|
||||
|
@ -47,7 +48,7 @@ public class SetVertexMostRecentColorAction extends MultiActionDockingAction {
|
|||
this.controller = controller;
|
||||
this.vertex = vertex;
|
||||
setDescription("Set this block's background color");
|
||||
colorIcon = new ColorIcon3D(new Color(189, 221, 252), 12, 12) {
|
||||
colorIcon = new ColorIcon3D(new GColor("color.bg.functiongraph.paint.icon"), 12, 12) {
|
||||
@Override
|
||||
public Color getColor() {
|
||||
return controller.getMostRecentColor();
|
||||
|
|
|
@ -20,6 +20,7 @@ import java.util.*;
|
|||
import java.util.Map.Entry;
|
||||
|
||||
import docking.theme.GColor;
|
||||
import docking.theme.GThemeDefaults.Colors.Palette;
|
||||
import ghidra.app.plugin.core.functiongraph.FunctionGraphPlugin;
|
||||
import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutOptions;
|
||||
import ghidra.framework.options.Options;
|
||||
|
@ -264,7 +265,7 @@ public class FunctionGraphOptions extends VisualGraphOptions {
|
|||
return getConditionalJumpEdgeColor();
|
||||
}
|
||||
|
||||
return Color.BLACK;
|
||||
return Palette.BLACK;
|
||||
}
|
||||
|
||||
public Color getHighlightColor(FlowType flowType) {
|
||||
|
@ -278,7 +279,7 @@ public class FunctionGraphOptions extends VisualGraphOptions {
|
|||
return getConditionalJumpEdgeHighlightColor();
|
||||
}
|
||||
|
||||
return Color.BLACK;
|
||||
return Palette.BLACK;
|
||||
}
|
||||
|
||||
public boolean optionChangeRequiresRelayout(String optionName) {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,980 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.functiongraph;
|
||||
|
||||
import static ghidra.graph.viewer.GraphViewerUtils.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.Clipboard;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.ComponentProvider;
|
||||
import docking.action.DockingAction;
|
||||
import docking.dnd.GClipboard;
|
||||
import docking.theme.GThemeDefaults.Colors.Palette;
|
||||
import edu.uci.ics.jung.algorithms.layout.Layout;
|
||||
import edu.uci.ics.jung.visualization.VisualizationModel;
|
||||
import edu.uci.ics.jung.visualization.VisualizationViewer;
|
||||
import edu.uci.ics.jung.visualization.util.Caching;
|
||||
import generic.test.TestUtils;
|
||||
import ghidra.app.cmd.label.AddLabelCmd;
|
||||
import ghidra.app.events.ProgramSelectionPluginEvent;
|
||||
import ghidra.app.nav.LocationMemento;
|
||||
import ghidra.app.nav.Navigatable;
|
||||
import ghidra.app.plugin.core.colorizer.ColorizingPlugin;
|
||||
import ghidra.app.plugin.core.colorizer.ColorizingService;
|
||||
import ghidra.app.plugin.core.functiongraph.graph.*;
|
||||
import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex;
|
||||
import ghidra.app.plugin.core.functiongraph.mvc.*;
|
||||
import ghidra.app.plugin.core.navigation.GoToAddressLabelPlugin;
|
||||
import ghidra.app.plugin.core.navigation.NextPrevAddressPlugin;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.program.model.block.*;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.FunctionManager;
|
||||
import ghidra.program.model.symbol.SourceType;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.program.util.ProgramSelection;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class FunctionGraphPlugin1Test extends AbstractFunctionGraphTest {
|
||||
|
||||
public FunctionGraphPlugin1Test() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
tool.addPlugin(GoToAddressLabelPlugin.class.getName());
|
||||
tool.addPlugin(NextPrevAddressPlugin.class.getName());
|
||||
|
||||
waitForGraphToLoad();
|
||||
}
|
||||
|
||||
// public void testCodeBlockSpeed() throws CancelledException {
|
||||
//// ProgramDB cppProgram = env.getProgram("msvidctl.dll_MFCAnalysis");
|
||||
// ProgramDB cppProgram = env.getProgram("winword.exe");
|
||||
//
|
||||
// FunctionManager functionManager = cppProgram.getFunctionManager();
|
||||
//// Function function = functionManager.getFunctionAt(getAddress("5a1f903e"));
|
||||
// Function function = functionManager.getFunctionAt(getAddress("3036d629"));
|
||||
//
|
||||
// long startTime = System.nanoTime();
|
||||
// BasicBlockModel blockModel = new BasicBlockModel(cppProgram, false);
|
||||
// CodeBlockIterator iterator =
|
||||
// blockModel.getCodeBlocksContaining(function.getBody(), TaskMonitorAdapter.DUMMY_MONITOR);
|
||||
// while (iterator.hasNext()) {
|
||||
// iterator.next();
|
||||
// }
|
||||
// long endTime = System.nanoTime();
|
||||
// double totalTime = (endTime - startTime) / 1000000000d;
|
||||
// System.err.println("total time: " + totalTime);
|
||||
// }
|
||||
|
||||
@Test
|
||||
public void testLocationChanged() {
|
||||
// get the graph contents
|
||||
FGData graphData = getFunctionGraphData();
|
||||
assertNotNull(graphData);
|
||||
assertTrue("Unexpectedly received an empty FunctionGraphData", graphData.hasResults());
|
||||
ProgramLocation location = getLocationForAddressString(startAddressString);
|
||||
assertTrue(graphData.containsLocation(location));
|
||||
FunctionGraph functionGraph = graphData.getFunctionGraph();
|
||||
|
||||
// locate vertex with cursor
|
||||
FGVertex focusedVertex = getFocusVertex(functionGraph);
|
||||
assertNotNull("We did not start with a focused vertex", focusedVertex);
|
||||
|
||||
// change the location
|
||||
String newLocationString = "01004192";
|
||||
goToAddress(newLocationString);
|
||||
|
||||
// locate vertex with cursor
|
||||
graphData = getFunctionGraphData();
|
||||
assertNotNull(graphData);
|
||||
assertTrue("Unexpectedly received an empty FunctionGraphData", graphData.hasResults());
|
||||
ProgramLocation newLocation = getLocationForAddressString(newLocationString);
|
||||
assertTrue(graphData.containsLocation(newLocation));
|
||||
|
||||
functionGraph = graphData.getFunctionGraph();
|
||||
FGVertex newFocusedVertex = functionGraph.getFocusedVertex();
|
||||
assertTrue(newFocusedVertex.containsProgramLocation(newLocation));
|
||||
|
||||
// make sure the two vertices are not the same
|
||||
assertTrue("Changing locations in the code browser did not move the cursor location to " +
|
||||
"a new graph vertex", !focusedVertex.equals(newFocusedVertex));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProgramSelectionAcrossVerticesFromCodeBrowser() {
|
||||
FGData graphData = getFunctionGraphData();// make sure the graph gets loaded
|
||||
assertNotNull(graphData);
|
||||
assertTrue("Unexpectedly received an empty FunctionGraphData", graphData.hasResults());
|
||||
|
||||
ProgramSelection ps = makeMultiVertexSelectionInCodeBrowser();
|
||||
|
||||
// this address is in a different vertex than the start address
|
||||
ProgramLocation location = getLocationForAddressString("0x01004192");
|
||||
assertTrue(graphData.containsLocation(location));
|
||||
FunctionGraph functionGraph = graphData.getFunctionGraph();
|
||||
FGVertex startVertex = functionGraph.getVertexForAddress(location.getAddress());
|
||||
|
||||
// locate vertex with cursor
|
||||
assertNotNull("We did not start with a focused vertex", startVertex);
|
||||
assertTrue(startVertex.containsProgramLocation(location));
|
||||
|
||||
// make a selection starting at the current location
|
||||
ProgramSelection firstSelection = startVertex.getProgramSelection();
|
||||
assertTrue("A selection too big for one vertex has fit into a start vertex",
|
||||
!ps.equals(firstSelection));
|
||||
|
||||
Address address = getAddress("0x01004196");
|
||||
FGVertex secondVertex = functionGraph.getVertexForAddress(address);
|
||||
ProgramSelection secondSelection = secondVertex.getProgramSelection();
|
||||
assertTrue(!secondSelection.isEmpty());
|
||||
|
||||
assertTrue(ps.getMinAddress().equals(firstSelection.getMinAddress()));
|
||||
assertTrue(ps.getMaxAddress().equals(secondSelection.getMaxAddress()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGraphWithCloseAndReopenProgram_ForSCR_7813() {
|
||||
//
|
||||
// This test is meant to ensure that graph contents are properly disposed and that no
|
||||
// exceptions happen while switching programs.
|
||||
//
|
||||
FGData graphData = getFunctionGraphData();
|
||||
assertNotNull(graphData);
|
||||
assertTrue("Unexpectedly received an empty FunctionGraphData", graphData.hasResults());
|
||||
|
||||
ProgramManager pm = tool.getService(ProgramManager.class);
|
||||
pm.closeProgram(program, true);
|
||||
|
||||
// should have empty data after closing the program
|
||||
graphData = getFunctionGraphData();
|
||||
assertNotNull(graphData);
|
||||
assertTrue("Graph data should be empty after closing the program", !graphData.hasResults());
|
||||
|
||||
pm.openProgram(program.getDomainFile());
|
||||
program.flushEvents();
|
||||
|
||||
// we should have some sort of non-null data--either real or empty
|
||||
graphData = getFunctionGraphData();
|
||||
assertNotNull(graphData);
|
||||
|
||||
goToAddress(startAddressString);
|
||||
|
||||
// verify we can still graph data
|
||||
graphData = getFunctionGraphData();
|
||||
assertNotNull(graphData);
|
||||
assertTrue("Unexpectedly received an empty FunctionGraphData", graphData.hasResults());
|
||||
}
|
||||
|
||||
// note: unreliable--garbage collection works differently across platforms (sad face)
|
||||
// public void testClearCacheAndMemoryLeak() {
|
||||
// //
|
||||
// // Test that when we clear the cache of a graph the vertices of that graph will be
|
||||
// // garbage collected
|
||||
// //
|
||||
// WeakSet<FunctionGraphVertex> weakSet =
|
||||
// WeakDataStructureFactory.createSingleThreadAccessWeakSet();
|
||||
//
|
||||
// FunctionGraphData graphData = getFunctionGraphData();
|
||||
// assertNotNull(graphData);
|
||||
// assertTrue("Unexpectedly received an empty FunctionGraphData", graphData.hasResults());
|
||||
//
|
||||
// FunctionGraph graph = graphData.getFunctionGraph();
|
||||
// FunctionGraphVertex rootVertex = graph.getRootVertex();
|
||||
// assertNotNull(rootVertex);
|
||||
//
|
||||
// weakSet.add(rootVertex);
|
||||
// assertTrue(weakSet.iterator().hasNext());
|
||||
//
|
||||
// // move to a new function so that we the FG will not be holding a reference to the
|
||||
// // originally graphed function
|
||||
// String address = "01002239";
|
||||
// goToAddress(address);
|
||||
//
|
||||
// FunctionGraphData newGraphData = getFunctionGraphData();
|
||||
// assertNotNull(newGraphData);
|
||||
// assertTrue("Unexpectedly received an empty FunctionGraphData", newGraphData.hasResults());
|
||||
//
|
||||
// assertTrue(
|
||||
// "Function Graph did not graph a new function as expected at address: " + address,
|
||||
// !graphData.equals(newGraphData));
|
||||
//
|
||||
// triggerGraphDisposal(graphData);
|
||||
//
|
||||
// rootVertex = null;
|
||||
// graph = null;
|
||||
// graphData = null;
|
||||
//
|
||||
// // let (force) the Garbage Collector run
|
||||
// System.gc();
|
||||
// sleep(100);
|
||||
// System.gc();
|
||||
// sleep(100);
|
||||
// System.gc();
|
||||
// sleep(100);
|
||||
//
|
||||
// boolean isNotCollected = weakSet.iterator().hasNext();
|
||||
// assertFalse(isNotCollected);
|
||||
// }
|
||||
//
|
||||
// private void triggerGraphDisposal(FunctionGraphData dataToDispose) {
|
||||
// Object controller = getInstanceField("controller", graphProvider);
|
||||
// LRUMap<?, ?> cache = (LRUMap<?, ?>) getInstanceField("graphCache", controller);
|
||||
// cache.clear();
|
||||
// dataToDispose.dispose();
|
||||
// }
|
||||
|
||||
@Test
|
||||
public void testSaveVertexPositions() {
|
||||
//
|
||||
// Test that we can move a node, load a new graph, reload the original graph and have
|
||||
// the moved node return to the moved position
|
||||
//
|
||||
|
||||
FGData originalGraphData = getFunctionGraphData();
|
||||
assertNotNull(originalGraphData);
|
||||
assertTrue("Unexpectedly received an empty FunctionGraphData",
|
||||
originalGraphData.hasResults());
|
||||
|
||||
FunctionGraph graph = originalGraphData.getFunctionGraph();
|
||||
final FGVertex rootVertex = graph.getRootVertex();
|
||||
assertNotNull(rootVertex);
|
||||
|
||||
Point2D originalPoint = rootVertex.getLocation();
|
||||
|
||||
double dx = originalPoint.getX() + 100;
|
||||
double dy = originalPoint.getY() + 100;
|
||||
|
||||
Point2D newPoint = new Point2D.Double(dx, dy);
|
||||
final Layout<FGVertex, FGEdge> primaryLayout = getPrimaryLayout();
|
||||
final Point2D finalNewPoint = newPoint;
|
||||
runSwing(() -> primaryLayout.setLocation(rootVertex, finalNewPoint));
|
||||
|
||||
// we have to wait for the paint to take place, as the rendering will change the vertex
|
||||
// locations
|
||||
FGPrimaryViewer primaryGraphViewer = getPrimaryGraphViewer();
|
||||
primaryGraphViewer.repaint();
|
||||
waitForSwing();
|
||||
|
||||
// now that we have changed the data, load a new graph and then come back to the start
|
||||
// graph so that we can see that the settings have been re-applied
|
||||
String address = "01002239";
|
||||
goToAddress(address);
|
||||
|
||||
FGData newGraphData = getFunctionGraphData();
|
||||
assertNotNull(newGraphData);
|
||||
assertTrue("Unexpectedly received an empty FunctionGraphData", newGraphData.hasResults());
|
||||
assertTrue(!newGraphData.equals(originalGraphData));
|
||||
|
||||
// ...now go back and check the position
|
||||
goToAddress(startAddressString);
|
||||
|
||||
newGraphData = getFunctionGraphData();
|
||||
assertNotNull(newGraphData);
|
||||
assertTrue("Unexpectedly received an empty FunctionGraphData", newGraphData.hasResults());
|
||||
|
||||
graph = newGraphData.getFunctionGraph();
|
||||
FGVertex newRootVertex = graph.getRootVertex();
|
||||
assertNotNull(newRootVertex);
|
||||
|
||||
waitForSwing();
|
||||
|
||||
// Note: we can't test for exact equality based upon the location we set, as the values
|
||||
// are updated based upon other factors, like screen location and size. We want to make
|
||||
// sure that the value is not the default.
|
||||
Point2D reloadedPoint = newRootVertex.getLocation();
|
||||
assertTrue("Vertex location not restored after regraphing a function",
|
||||
!originalPoint.equals(reloadedPoint));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRelayout() throws Exception {
|
||||
doTestRelayout(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReload() throws Exception {
|
||||
doTestRelayout(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLabelChangeAtVertexEntryUpdatesTitle() {
|
||||
int txID = -1;
|
||||
try {
|
||||
txID = program.startTransaction("Test: " + testName.getMethodName());
|
||||
doTestLabelChangeAtVertexEntryUpdatesTitle();
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txID, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangeFormat() throws Exception {
|
||||
//
|
||||
// Test that we can change the view's format. As part of this test verify:
|
||||
// -That each graph gets the view changes
|
||||
// -Test reset format
|
||||
// -That view changes are persisted
|
||||
//
|
||||
FGController primaryController = getFunctionGraphController();
|
||||
waitForBusyRunManager(primaryController);
|
||||
|
||||
FGData functionGraphData = getFunctionGraphData();
|
||||
FunctionGraph functionGraph = functionGraphData.getFunctionGraph();
|
||||
|
||||
// Be sure to pick a vertex that will get bigger when a new field is added. The
|
||||
// root vertex is already so wide (due to the function signature), that adding a small
|
||||
// field does not change its width.
|
||||
FGVertex vertex = functionGraph.getVertexForAddress(getAddress("1004178"));
|
||||
Rectangle originalBounds = vertex.getBounds();
|
||||
|
||||
// Also, be sure that we are not on the function signature field, as that does not have
|
||||
// the 'Bytes' field.
|
||||
goTo("1004179");
|
||||
|
||||
addBytesFormatFieldFactory();
|
||||
|
||||
//
|
||||
// Verify the vertex size has change (due to the format getting larger)
|
||||
//
|
||||
FGPrimaryViewer viewer = getPrimaryGraphViewer();
|
||||
viewer.repaint();
|
||||
waitForSwing();
|
||||
|
||||
Rectangle updatedBounds = vertex.getBounds();
|
||||
assertTrue("bounds not updated - was: " + originalBounds + "; is now: " + updatedBounds,
|
||||
originalBounds.width < updatedBounds.width);
|
||||
|
||||
performResetFormatAction();
|
||||
|
||||
viewer.repaint();
|
||||
waitForSwing();
|
||||
|
||||
Rectangle newNewBounds = vertex.getBounds();
|
||||
assertTrue(updatedBounds.width > newNewBounds.width);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyKeyBinding() throws Exception {
|
||||
//
|
||||
// Make a program selection and test that executing the copy keybinding will copy the
|
||||
// selection (across vertices).
|
||||
//
|
||||
|
||||
//
|
||||
// Initialize the clipboard with known data
|
||||
//
|
||||
Clipboard systemClipboard = GClipboard.getSystemClipboard();
|
||||
systemClipboard.setContents(DUMMY_TRANSFERABLE, null);
|
||||
waitForSwing();
|
||||
|
||||
//
|
||||
// Verify our initial state
|
||||
FGData graphData = getFunctionGraphData();
|
||||
assertNotNull(graphData);
|
||||
assertTrue("Unexpectedly received an empty FunctionGraphData", graphData.hasResults());
|
||||
ProgramLocation location = getLocationForAddressString(startAddressString);
|
||||
assertTrue(graphData.containsLocation(location));
|
||||
FunctionGraph functionGraph = graphData.getFunctionGraph();
|
||||
|
||||
// locate vertex with cursor
|
||||
FGVertex focusedVertex = getFocusVertex(functionGraph);
|
||||
assertNotNull("We did not start with a focused vertex", focusedVertex);
|
||||
|
||||
//
|
||||
// Create a selection that we will copy from the executing the action
|
||||
//
|
||||
AddressSetView addresses = focusedVertex.getAddresses();
|
||||
Address address = addresses.getMinAddress();
|
||||
ProgramSelection selection =
|
||||
new ProgramSelection(program.getAddressFactory(), address, address.add(8));
|
||||
tool.firePluginEvent(new ProgramSelectionPluginEvent("Test", selection, program));
|
||||
|
||||
//
|
||||
// Validate and execute the action
|
||||
//
|
||||
DockingAction copyAction = getCopyAction();
|
||||
FGController controller = getFunctionGraphController();
|
||||
ComponentProvider provider = controller.getProvider();
|
||||
assertTrue(copyAction.isEnabledForContext(provider.getActionContext(null)));
|
||||
|
||||
performAction(copyAction, provider, false);
|
||||
|
||||
waitForTasks();
|
||||
|
||||
Transferable contents = systemClipboard.getContents(systemClipboard);
|
||||
assertNotNull(contents);
|
||||
assertTrue("Contents not copied into system clipboard", (contents != DUMMY_TRANSFERABLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCopyAction() {
|
||||
//
|
||||
// Put the cursor in a vertex on a field with text and make sure that the copy action
|
||||
// is enabled.
|
||||
//
|
||||
|
||||
//
|
||||
// Initialize the clipboard with known data
|
||||
//
|
||||
Clipboard systemClipboard = GClipboard.getSystemClipboard();
|
||||
systemClipboard.setContents(DUMMY_TRANSFERABLE, null);
|
||||
waitForSwing();
|
||||
|
||||
//
|
||||
// Verify our initial state
|
||||
FGData graphData = getFunctionGraphData();
|
||||
assertNotNull(graphData);
|
||||
assertTrue("Unexpectedly received an empty FunctionGraphData", graphData.hasResults());
|
||||
ProgramLocation location = getLocationForAddressString(startAddressString);
|
||||
assertTrue(graphData.containsLocation(location));
|
||||
FunctionGraph functionGraph = graphData.getFunctionGraph();
|
||||
|
||||
// locate vertex with cursor
|
||||
FGVertex focusedVertex = getFocusVertex(functionGraph);
|
||||
assertNotNull("We did not start with a focused vertex", focusedVertex);
|
||||
|
||||
//
|
||||
// Put the cursor on a copyable thing
|
||||
//
|
||||
codeBrowser.goToField(getAddress("0x01004196"), "Mnemonic", 0, 0, 2, true);
|
||||
waitForSwing();
|
||||
|
||||
// sanity check
|
||||
DockingAction copyAction = getCopyAction();
|
||||
assertClipboardServiceAddress(copyAction, "0x01004196");
|
||||
|
||||
//
|
||||
// Validate and execute the action
|
||||
//
|
||||
FGController controller = getFunctionGraphController();
|
||||
ComponentProvider provider = controller.getProvider();
|
||||
ActionContext actionContext = provider.getActionContext(null);
|
||||
boolean isEnabled = copyAction.isEnabledForContext(actionContext);
|
||||
debugAction(copyAction, actionContext);
|
||||
assertTrue(isEnabled);
|
||||
performAction(copyAction, actionContext, true);
|
||||
|
||||
Transferable contents = systemClipboard.getContents(systemClipboard);
|
||||
assertNotNull(contents);
|
||||
assertTrue("Contents not copied into system clipboard", (contents != DUMMY_TRANSFERABLE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSatelliteViewIsResizedToFit() {
|
||||
//
|
||||
// Test that the satellite view zoomed to fit completely in the window if the window
|
||||
// is resized or the graph is made bigger via dragging a vertex.
|
||||
//
|
||||
showSatellite();// make sure it is on
|
||||
|
||||
FGController controller = getFunctionGraphController();
|
||||
waitForBusyRunManager(controller);
|
||||
FGView view = controller.getView();
|
||||
VisualizationViewer<FGVertex, FGEdge> satelliteViewer = view.getSatelliteViewer();
|
||||
Double originalGraphScale = getGraphScale(satelliteViewer);
|
||||
|
||||
//
|
||||
// window size change test
|
||||
//
|
||||
final Window window = windowForComponent(graphProvider.getComponent());
|
||||
final Dimension originalSize = window.getSize();
|
||||
final Dimension newSize = new Dimension(originalSize.width >> 1, originalSize.height >> 1);
|
||||
runSwing(() -> window.setSize(newSize));
|
||||
waitForSwing();
|
||||
waitForBusyGraph();
|
||||
|
||||
Double newGraphScale = getGraphScale(satelliteViewer);
|
||||
Assert.assertNotEquals("The graph's scale did not change after resizing the window",
|
||||
originalGraphScale, newGraphScale);
|
||||
|
||||
runSwing(() -> window.setSize(originalSize));
|
||||
waitForSwing();
|
||||
waitForBusyGraph();
|
||||
|
||||
newGraphScale = getGraphScale(satelliteViewer);
|
||||
assertEquals(originalGraphScale, newGraphScale);
|
||||
|
||||
//
|
||||
// graph size change test
|
||||
//
|
||||
FGData functionGraphData = getFunctionGraphData();
|
||||
FunctionGraph functionGraph = functionGraphData.getFunctionGraph();
|
||||
VisualizationViewer<FGVertex, FGEdge> primaryGraphViewer = view.getPrimaryGraphViewer();
|
||||
VisualizationModel<FGVertex, FGEdge> model = primaryGraphViewer.getModel();
|
||||
final Layout<FGVertex, FGEdge> graphLayout = model.getGraphLayout();
|
||||
final FGVertex vertex = functionGraph.getRootVertex();
|
||||
final Point2D startPoint = graphLayout.apply(vertex);
|
||||
|
||||
final Point2D newPoint = new Point2D.Double(startPoint.getX() + 2000, startPoint.getY());
|
||||
runSwing(() -> {
|
||||
Caching cachingLayout = (Caching) graphLayout;
|
||||
cachingLayout.clear();
|
||||
graphLayout.setLocation(vertex, newPoint);
|
||||
});
|
||||
waitForSwing();
|
||||
|
||||
Double scaleAfterDragging = getGraphScale(satelliteViewer);
|
||||
Assert.assertNotEquals(newGraphScale, scaleAfterDragging);
|
||||
|
||||
// put the vertex back and make sure the scale is reverted
|
||||
runSwing(() -> {
|
||||
Caching cachingLayout = (Caching) graphLayout;
|
||||
cachingLayout.clear();
|
||||
graphLayout.setLocation(vertex, startPoint);
|
||||
});
|
||||
waitForSwing();
|
||||
|
||||
scaleAfterDragging = getGraphScale(satelliteViewer);
|
||||
assertEquals(newGraphScale, scaleAfterDragging);
|
||||
}
|
||||
|
||||
public void TODO_testPersistence() {
|
||||
//
|
||||
// This wants to test that graph perspective info is saved between Ghidra sessions. In
|
||||
// other words, is my graph location and zoom level the same between Ghidra runs. We
|
||||
// also want to test that colors and vertex locations are persisted between sessions.
|
||||
//
|
||||
//
|
||||
|
||||
// Note: This is probably too hard to worry about. To test this, we have to setup a
|
||||
// Ghidra environment with varied values. Then, close down Ghidra, relaunch Ghidra,
|
||||
// and test the changed values for sameness
|
||||
}
|
||||
|
||||
public void TODO_testEdgeHover() {
|
||||
// hover should show information about the connected nodes in a tooltip?
|
||||
|
||||
// should hover highlight the same edge in the satellite?
|
||||
|
||||
//
|
||||
// Note: these are GUI intensive tests--low reward/benefit ratios
|
||||
//
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGraphNodesCreated() throws Exception {
|
||||
FGData graphData = getFunctionGraphData();
|
||||
assertNotNull(graphData);
|
||||
assertTrue("Unexpectedly received an empty FunctionGraphData", graphData.hasResults());
|
||||
FunctionGraph functionGraph = graphData.getFunctionGraph();
|
||||
Collection<FGVertex> vertices = functionGraph.getVertices();
|
||||
|
||||
BlockModelService blockService = tool.getService(BlockModelService.class);
|
||||
CodeBlockModel blockModel = blockService.getActiveBlockModel(program);
|
||||
FunctionManager functionManager = program.getFunctionManager();
|
||||
Function function = functionManager.getFunctionContaining(getAddress(startAddressString));
|
||||
CodeBlockIterator iterator =
|
||||
blockModel.getCodeBlocksContaining(function.getBody(), TaskMonitor.DUMMY);
|
||||
|
||||
// we should have one vertex for each code block
|
||||
Set<Address> vertexAddresses = new HashSet<>();
|
||||
for (FGVertex vertex : vertices) {
|
||||
AddressSetView addresses = vertex.getAddresses();
|
||||
vertexAddresses.add(addresses.getMinAddress());
|
||||
}
|
||||
|
||||
for (; iterator.hasNext();) {
|
||||
CodeBlock codeBlock = iterator.next();
|
||||
assertTrue(vertexAddresses.contains(codeBlock.getMinAddress()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClearColorAction() throws Exception {
|
||||
tool.addPlugin(ColorizingPlugin.class.getName());
|
||||
|
||||
FGVertex focusedVertex = getFocusedVertex();
|
||||
ColorizingService colorizingService = tool.getService(ColorizingService.class);
|
||||
Color appliedBackgroundColor =
|
||||
colorizingService.getBackgroundColor(focusedVertex.getVertexAddress());
|
||||
|
||||
Color testColor = Palette.RED;
|
||||
assertTrue("Unexpected start color--must change the test!",
|
||||
!testColor.equals(appliedBackgroundColor));
|
||||
|
||||
chooseColor(focusedVertex, testColor);
|
||||
|
||||
Color newVertexBackgroundColor = focusedVertex.getUserDefinedColor();
|
||||
assertEquals("Background color not set", testColor, newVertexBackgroundColor);
|
||||
|
||||
DockingAction clearColorAction = getClearColorAction(focusedVertex);
|
||||
performAction(clearColorAction, graphProvider, true);
|
||||
|
||||
Color userDefinedColor = focusedVertex.getUserDefinedColor();
|
||||
assertNull(userDefinedColor);
|
||||
|
||||
Color serviceBackgroundColor =
|
||||
colorizingService.getBackgroundColor(focusedVertex.getVertexAddress());
|
||||
assertNull("Clear action did not clear the service's applied color",
|
||||
serviceBackgroundColor);
|
||||
}
|
||||
|
||||
// test that navigating a vertex updates the code browser's location
|
||||
@Test
|
||||
public void testNavigationFromVertexToCodeBrowser() {
|
||||
|
||||
//
|
||||
// This test covers navigation, which relies on the provider being focused to work
|
||||
//
|
||||
setProviderAlwaysFocused();
|
||||
|
||||
FGData graphData = getFunctionGraphData();
|
||||
assertNotNull(graphData);
|
||||
assertTrue("Unexpectedly received an empty FunctionGraphData", graphData.hasResults());
|
||||
ProgramLocation location = getLocationForAddressString(startAddressString);
|
||||
assertTrue(graphData.containsLocation(location));
|
||||
FunctionGraph functionGraph = graphData.getFunctionGraph();
|
||||
|
||||
// locate vertex with cursor
|
||||
FGVertex focusedVertex = getFocusVertex(functionGraph);
|
||||
assertNotNull("We did not start with a focused vertex", focusedVertex);
|
||||
|
||||
Collection<FGVertex> vertices = functionGraph.getVertices();
|
||||
FGVertex otherVertex = null;
|
||||
for (FGVertex vertex : vertices) {
|
||||
if (vertex != focusedVertex) {
|
||||
otherVertex = vertex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertNotNull(otherVertex);
|
||||
|
||||
Address address = otherVertex.getAddresses().getMinAddress();
|
||||
final ProgramLocation newVertexLocation = new ProgramLocation(program, address);
|
||||
final FGController controller =
|
||||
(FGController) TestUtils.getInstanceField("controller", graphProvider);
|
||||
|
||||
runSwing(() -> controller.display(program, newVertexLocation));
|
||||
|
||||
// we must 'fake out' the listing to generate a location event from within the listing
|
||||
pressRightArrowKey(otherVertex);
|
||||
waitForSwing();
|
||||
|
||||
ProgramLocation codeBrowserLocation = runSwing(() -> codeBrowser.getCurrentLocation());
|
||||
ProgramLocation actualVertexLocation = otherVertex.getProgramLocation();
|
||||
assertEquals(newVertexLocation.getAddress(), actualVertexLocation.getAddress());
|
||||
assertEquals(actualVertexLocation.getAddress(), codeBrowserLocation.getAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFullyZoomedOutOption() throws Exception {
|
||||
//
|
||||
// Test the default option that fits the entire graph into the window. Then toggle the
|
||||
// option and test that a new graph starts fully zoomed-in.
|
||||
//
|
||||
|
||||
hideSatellite();// for readability
|
||||
setGraphWindowSize(300, 300);// make window small for easier testing
|
||||
setZoomOutOption(true);
|
||||
|
||||
assertZoomedOut();
|
||||
|
||||
setZoomOutOption(false);
|
||||
|
||||
assertZoomedIn();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNavigationHistory_VertexChangesOption() throws Exception {
|
||||
|
||||
setNavigationHistoryOption(NavigationHistoryChoices.VERTEX_CHANGES);
|
||||
|
||||
FGData graphData = getFunctionGraphData();
|
||||
FunctionGraph graph = graphData.getFunctionGraph();
|
||||
Collection<FGVertex> vertices = graph.getVertices();
|
||||
|
||||
FGVertex start = getFocusedVertex();
|
||||
|
||||
Iterator<FGVertex> it = vertices.iterator();
|
||||
FGVertex v1 = it.next();
|
||||
pickVertex(v1);
|
||||
|
||||
FGVertex v2 = it.next();
|
||||
pickVertex(v2);
|
||||
|
||||
FGVertex v3 = it.next();
|
||||
pickVertex(v3);
|
||||
|
||||
assertInHistory(start, v1, v2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNavigationHistory_NavigationEventsOption() throws Exception {
|
||||
|
||||
setNavigationHistoryOption(NavigationHistoryChoices.NAVIGATION_EVENTS);
|
||||
|
||||
clearHistory();
|
||||
|
||||
FGVertex v1 = vertex("01004178");
|
||||
pickVertex(v1);
|
||||
|
||||
FGVertex v2 = vertex("01004192");
|
||||
pickVertex(v2);
|
||||
|
||||
FGVertex v3 = vertex("010041a4");
|
||||
pickVertex(v3);
|
||||
|
||||
// in this navigation mode, merely selecting nodes does *not* put previous nodes in history
|
||||
assertNotInHistory(v1, v2);
|
||||
|
||||
//
|
||||
// Perform a navigation action (e.g., goTo()) and verify the old function is in the history
|
||||
//
|
||||
Address ghidra = getAddress("0x01002cf5");
|
||||
goTo(ghidra);
|
||||
assertInHistory(v3.getVertexAddress());
|
||||
|
||||
Address foo = getAddress("0x01002339");
|
||||
goTo(foo);
|
||||
assertInHistory(v3.getVertexAddress(), ghidra);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Private Methods
|
||||
//==================================================================================================
|
||||
|
||||
private void assertNotInHistory(FGVertex... vertices) {
|
||||
|
||||
List<Address> vertexAddresses =
|
||||
Arrays.stream(vertices)
|
||||
.map(v -> v.getVertexAddress())
|
||||
.collect(Collectors.toList());
|
||||
assertNotInHistory(vertexAddresses);
|
||||
}
|
||||
|
||||
private void assertNotInHistory(List<Address> addresses) {
|
||||
|
||||
List<LocationMemento> locations = getNavigationHistory();
|
||||
List<Address> actualAddresses =
|
||||
locations.stream()
|
||||
.map(memento -> memento.getProgramLocation().getAddress())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (Address a : addresses) {
|
||||
assertFalse("Vertex address should not be in the history list: " + a + ".\nHistory: " +
|
||||
actualAddresses + "\nNavigated vertices: " + Arrays.asList(addresses),
|
||||
actualAddresses.contains(a));
|
||||
}
|
||||
}
|
||||
|
||||
private void clearHistory() {
|
||||
GoToService goTo = tool.getService(GoToService.class);
|
||||
Navigatable navigatable = goTo.getDefaultNavigatable();
|
||||
|
||||
NavigationHistoryService service = tool.getService(NavigationHistoryService.class);
|
||||
service.clear(navigatable);
|
||||
}
|
||||
|
||||
private List<LocationMemento> getNavigationHistory() {
|
||||
|
||||
GoToService goTo = tool.getService(GoToService.class);
|
||||
Navigatable navigatable = goTo.getDefaultNavigatable();
|
||||
|
||||
NavigationHistoryService service = tool.getService(NavigationHistoryService.class);
|
||||
List<LocationMemento> locations = service.getPreviousLocations(navigatable);
|
||||
return locations;
|
||||
}
|
||||
|
||||
private void assertInHistory(FGVertex... vertices) {
|
||||
|
||||
List<Address> vertexAddresses =
|
||||
Arrays.stream(vertices)
|
||||
.map(v -> v.getVertexAddress())
|
||||
.collect(Collectors.toList());
|
||||
assertInHistory(vertexAddresses);
|
||||
}
|
||||
|
||||
private void assertInHistory(Address... addresses) {
|
||||
assertInHistory(Arrays.asList(addresses));
|
||||
}
|
||||
|
||||
private void assertInHistory(List<Address> expectedAddresses) {
|
||||
|
||||
List<LocationMemento> actualLocations = getNavigationHistory();
|
||||
assertTrue(
|
||||
"Vertex address should be in the history list: " + expectedAddresses + ".\nHistory: " +
|
||||
actualLocations + "\nNavigated vertices: " + expectedAddresses,
|
||||
expectedAddresses.size() <= actualLocations.size());
|
||||
|
||||
List<Address> actualAddresses =
|
||||
actualLocations.stream()
|
||||
.map(memento -> memento.getProgramLocation().getAddress())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (Address a : expectedAddresses) {
|
||||
|
||||
assertTrue("Vertex address should be in the history list: " + a + ".\nActual: " +
|
||||
actualAddresses + "\nExpected: " + expectedAddresses,
|
||||
actualAddresses.contains(a));
|
||||
}
|
||||
}
|
||||
|
||||
private void setNavigationHistoryOption(NavigationHistoryChoices choice) throws Exception {
|
||||
FGController controller = getFunctionGraphController();
|
||||
FunctionGraphOptions options = controller.getFunctionGraphOptions();
|
||||
runSwing(() -> setInstanceField("navigationHistoryChoice", options, choice));
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
private void doTestLabelChangeAtVertexEntryUpdatesTitle() {
|
||||
// get the graph contents
|
||||
FGData graphData = getFunctionGraphData();
|
||||
assertNotNull(graphData);
|
||||
assertTrue("Unexpectedly received an empty FunctionGraphData", graphData.hasResults());
|
||||
|
||||
// locate vertex with cursor
|
||||
Address vertexAddressWithDefaultLabel = getAddress("01004178");
|
||||
FunctionGraph graph = graphData.getFunctionGraph();
|
||||
FGVertex vertex = graph.getVertexForAddress(vertexAddressWithDefaultLabel);
|
||||
String originalTitle = vertex.getTitle();
|
||||
|
||||
// add a label in the listing
|
||||
String labelName = testName.getMethodName();
|
||||
AddLabelCmd addCmd =
|
||||
new AddLabelCmd(vertexAddressWithDefaultLabel, labelName, SourceType.USER_DEFINED);
|
||||
addCmd.applyTo(program);
|
||||
program.flushEvents();
|
||||
waitForSwing();
|
||||
|
||||
// make sure the label appears in the vertex
|
||||
String updatedTitle = vertex.getTitle();
|
||||
Assert.assertNotEquals(originalTitle, updatedTitle);
|
||||
assertTrue(updatedTitle.indexOf(testName.getMethodName()) != -1);
|
||||
}
|
||||
|
||||
private void doTestRelayout(boolean fullReload) throws Exception {
|
||||
|
||||
//
|
||||
// This test covers navigation, which relies on the provider being focused to work
|
||||
//
|
||||
setProviderAlwaysFocused();
|
||||
|
||||
//
|
||||
// Test that we can move a node, call relayout and that the moved node will not be
|
||||
// at the moved position.
|
||||
//
|
||||
|
||||
FGData originalGraphData = getFunctionGraphData();
|
||||
assertNotNull(originalGraphData);
|
||||
assertTrue("Unexpectedly received an empty FunctionGraphData",
|
||||
originalGraphData.hasResults());
|
||||
|
||||
//
|
||||
// Unusual Code: The initial load values may not be exactly the same as the values
|
||||
// set during a relayout. To make sure that we are comparing apples-to-apples,
|
||||
// we want to record the initial values after performing a relayout. Then,
|
||||
// we change a node's position, relayout again, and check the final values
|
||||
// with that after the first relayout.
|
||||
//
|
||||
if (fullReload) {
|
||||
performReload();
|
||||
}
|
||||
else {
|
||||
performRelayout();
|
||||
}
|
||||
|
||||
originalGraphData = getFunctionGraphData();
|
||||
FunctionGraph graph = originalGraphData.getFunctionGraph();
|
||||
final FGVertex rootVertex = graph.getRootVertex();
|
||||
assertNotNull(rootVertex);
|
||||
|
||||
Point2D originalPoint = rootVertex.getLocation();
|
||||
|
||||
double dx = originalPoint.getX() + 100;
|
||||
double dy = originalPoint.getY() + 100;
|
||||
|
||||
Point2D newPoint = new Point2D.Double(dx, dy);
|
||||
final Layout<FGVertex, FGEdge> primaryLayout = getPrimaryLayout();
|
||||
final Point2D finalNewPoint = newPoint;
|
||||
runSwing(() -> primaryLayout.setLocation(rootVertex, finalNewPoint));
|
||||
|
||||
assertEquals("Vertex location not correctly set", newPoint, rootVertex.getLocation());
|
||||
|
||||
// we have to wait for the paint to take place, as the rendering will change the vertex
|
||||
// locations
|
||||
FGPrimaryViewer primaryGraphViewer = getPrimaryGraphViewer();
|
||||
primaryGraphViewer.repaint();
|
||||
waitForSwing();
|
||||
|
||||
// move the location a bit, for later testing the sync between the listing and the graph
|
||||
goToAddress("1004196");
|
||||
|
||||
// relayout
|
||||
if (fullReload) {
|
||||
performReload();
|
||||
}
|
||||
else {
|
||||
performRelayout();
|
||||
}
|
||||
|
||||
FGData newGraphData = getFunctionGraphData();
|
||||
assertNotNull(newGraphData);
|
||||
assertTrue("Unexpectedly received an empty FunctionGraphData", newGraphData.hasResults());
|
||||
|
||||
FunctionGraph newGraph = newGraphData.getFunctionGraph();
|
||||
FGVertex newRootVertex = newGraph.getRootVertex();
|
||||
assertNotNull(newRootVertex);
|
||||
|
||||
waitForSwing();
|
||||
|
||||
// Note: we can't test for exact equality based upon the location we set, as the values
|
||||
// are updated based upon other factors, like screen location and size. We want to make
|
||||
// sure that the value is not the default.
|
||||
Point2D reloadedPoint = newRootVertex.getLocation();
|
||||
assertTrue(
|
||||
"Vertex location not restored to default after performing a relayout " +
|
||||
"original point: " + originalPoint + " - reloaded point: " + reloadedPoint,
|
||||
pointsAreSimilar(originalPoint, reloadedPoint));
|
||||
|
||||
//
|
||||
// Make sure the CodeBrowser's location matches ours after the relayout (the location should
|
||||
// get broadcast to the CodeBrowser)
|
||||
//
|
||||
|
||||
// Note: there is a timing failure that happens for this check; the event broadcast
|
||||
// only happens if the FG provider has focus; in parallel batch mode focus is
|
||||
// unreliable
|
||||
if (!BATCH_MODE) {
|
||||
assertTrue(graphAddressMatchesCodeBrowser(newGraph));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean graphAddressMatchesCodeBrowser(FunctionGraph graph) {
|
||||
FGVertex focusedVertex = runSwing(() -> graph.getFocusedVertex());
|
||||
ProgramLocation graphLocation = focusedVertex.getProgramLocation();
|
||||
ProgramLocation codeBrowserLocation = runSwing(() -> codeBrowser.getCurrentLocation());
|
||||
return graphLocation.getAddress().equals(codeBrowserLocation.getAddress());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,841 @@
|
|||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.functiongraph;
|
||||
|
||||
import static ghidra.graph.viewer.GraphViewerUtils.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Point;
|
||||
import java.util.*;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.theme.GThemeDefaults.Colors.Palette;
|
||||
import edu.uci.ics.jung.graph.Graph;
|
||||
import generic.test.TestUtils;
|
||||
import ghidra.app.cmd.label.AddLabelCmd;
|
||||
import ghidra.app.cmd.label.DeleteLabelCmd;
|
||||
import ghidra.app.cmd.refs.AddMemRefCmd;
|
||||
import ghidra.app.cmd.refs.RemoveReferenceCmd;
|
||||
import ghidra.app.plugin.core.colorizer.ColorizingPlugin;
|
||||
import ghidra.app.plugin.core.colorizer.ColorizingService;
|
||||
import ghidra.app.plugin.core.functiongraph.graph.*;
|
||||
import ghidra.app.plugin.core.functiongraph.graph.vertex.*;
|
||||
import ghidra.app.plugin.core.functiongraph.mvc.*;
|
||||
import ghidra.app.plugin.core.navigation.GoToAddressLabelPlugin;
|
||||
import ghidra.app.plugin.core.navigation.NextPrevAddressPlugin;
|
||||
import ghidra.app.util.viewer.listingpanel.ListingPanel;
|
||||
import ghidra.graph.viewer.GraphPerspectiveInfo;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.program.model.symbol.*;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.program.util.ProgramSelection;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class FunctionGraphPlugin2Test extends AbstractFunctionGraphTest {
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
tool.addPlugin(GoToAddressLabelPlugin.class.getName());
|
||||
tool.addPlugin(NextPrevAddressPlugin.class.getName());
|
||||
|
||||
waitForGraphToLoad();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectionFromCodeBrowser() {
|
||||
FGData graphData = getFunctionGraphData();
|
||||
assertNotNull(graphData);
|
||||
assertTrue("Unexpectedly received an empty FunctionGraphData", graphData.hasResults());
|
||||
ProgramLocation location = getLocationForAddressString(startAddressString);
|
||||
assertTrue(graphData.containsLocation(location));
|
||||
FunctionGraph functionGraph = graphData.getFunctionGraph();
|
||||
|
||||
// locate vertex with cursor
|
||||
FGVertex focusedVertex = getFocusVertex(functionGraph);
|
||||
assertNotNull("We did not start with a focused vertex", focusedVertex);
|
||||
assertTrue(focusedVertex.containsProgramLocation(location));
|
||||
|
||||
// make a selection starting at the current location
|
||||
ProgramSelection ps = makeSingleVertexSelectionInCodeBrowser();
|
||||
ProgramSelection vertexSelection = focusedVertex.getProgramSelection();
|
||||
assertTrue(!ps.isEmpty());
|
||||
assertEquals("A selection from the code browser that is completely contained in the " +
|
||||
"tested vertex is not in the vertex", ps, vertexSelection);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRedockSatellite() {
|
||||
|
||||
showSatellite();// make sure it is on
|
||||
|
||||
undockSatellite();
|
||||
redockSatellite();
|
||||
assertNoUndockedProvider();
|
||||
assertSatelliteVisible(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUndockSatellite() {
|
||||
|
||||
showSatellite();// make sure it is on
|
||||
|
||||
assertNoUndockedProvider();
|
||||
|
||||
undockSatellite();
|
||||
|
||||
assertSatelliteVisible(true);
|
||||
assertUndockedProviderShowing();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShowSatelliteButtonWhenDocked() {
|
||||
showSatellite();// make sure it is on
|
||||
|
||||
toggleSatalliteVisible(false);
|
||||
assertSatelliteVisible(false);
|
||||
|
||||
pressShowSatelliteButton();
|
||||
|
||||
assertSatelliteVisible(true);
|
||||
assertNoUndockedProvider();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShowSatelliteButtonWhenUnDocked() {
|
||||
showSatellite();// make sure it is on
|
||||
|
||||
undockSatellite();
|
||||
assertUndockedProviderShowing();
|
||||
|
||||
closeUndockedProvider();
|
||||
assertUndockedProviderNotShowing();
|
||||
|
||||
pressShowSatelliteButton();
|
||||
|
||||
assertUndockedProviderShowing();
|
||||
assertSatelliteVisible(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUndockWhileInvisible() {
|
||||
|
||||
toggleSatalliteVisible(false);
|
||||
assertSatelliteVisible(false);
|
||||
|
||||
undockSatellite();
|
||||
assertUndockedProviderShowing();
|
||||
|
||||
redockSatellite();
|
||||
|
||||
// note: redocking the satellite will make it visible again
|
||||
assertSatelliteVisible(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSnapshotWithUndockedSatellite() {
|
||||
|
||||
undockSatellite();
|
||||
|
||||
FGController newController = cloneGraph();
|
||||
assertUndockedProviderShowing(newController.getProvider());
|
||||
isSatelliteVisible(newController);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
// list cast
|
||||
@Test
|
||||
public void testSnapshot() {
|
||||
List<FGProvider> disconnectedProviders =
|
||||
(List<FGProvider>) getInstanceField("disconnectedProviders", graphPlugin);
|
||||
assertTrue(disconnectedProviders.isEmpty());
|
||||
|
||||
FGController primaryController = getFunctionGraphController();
|
||||
waitForBusyRunManager(primaryController);
|
||||
ProgramLocation location = graphProvider.getLocation();
|
||||
GraphPerspectiveInfo<FGVertex, FGEdge> primaryPerspective =
|
||||
primaryController.getGraphPerspective(location);
|
||||
|
||||
DockingActionIf snapshotAction =
|
||||
getAction(tool, graphPlugin.getName(), "Function Graph Clone");
|
||||
performAction(snapshotAction, true);
|
||||
|
||||
assertEquals(1, disconnectedProviders.size());
|
||||
FGProvider providerClone = disconnectedProviders.get(0);
|
||||
FGController controllerClone = (FGController) getInstanceField("controller", providerClone);
|
||||
waitForBusyRunManager(controllerClone);
|
||||
ProgramLocation cloneLocation = providerClone.getLocation();
|
||||
GraphPerspectiveInfo<FGVertex, FGEdge> clonePerspective =
|
||||
controllerClone.getGraphPerspective(cloneLocation);
|
||||
|
||||
double primaryPerspectiveZoom = primaryPerspective.getZoom();
|
||||
double clonePerspectiveZoom = clonePerspective.getZoom();
|
||||
assertEquals(primaryPerspectiveZoom, clonePerspectiveZoom, .001);
|
||||
|
||||
Point primaryPoint = primaryPerspective.getLayoutTranslateCoordinates();
|
||||
Point clonePoint = clonePerspective.getLayoutTranslateCoordinates();
|
||||
|
||||
assertPointsAreAboutEqual("Cloned graph view info does not match the source graph",
|
||||
primaryPoint, clonePoint);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testZoom() {
|
||||
setZoom(0.5d);
|
||||
|
||||
waitForAnimation();
|
||||
|
||||
FGPrimaryViewer primaryGraphViewer = getPrimaryGraphViewer();
|
||||
Double originalGraphScale = getGraphScale(primaryGraphViewer);
|
||||
Msg.debug(this, "original scale: " + originalGraphScale);
|
||||
|
||||
// zoom at code level
|
||||
Msg.debug(this, "zooming in...");
|
||||
zoomInCompletely();
|
||||
Double zoomedInGraphScale = getGraphScale(primaryGraphViewer);
|
||||
Msg.debug(this, "new scale: " + zoomedInGraphScale);
|
||||
|
||||
Assert.assertNotEquals(originalGraphScale, zoomedInGraphScale);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplitAndMergeNodesOnStaleGraph_ForReference() {
|
||||
int txID = -1;
|
||||
try {
|
||||
txID = program.startTransaction("Test: " + testName.getMethodName());
|
||||
doTestSplitAndMergeNodesOnStaleGraph_ForReference();
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txID, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSplitAndMergeNodesOnStaleGraph_ForSymbol() {
|
||||
int txID = -1;
|
||||
try {
|
||||
txID = program.startTransaction("Test: " + testName.getMethodName());
|
||||
doTestSplitAndMergeNodesOnStaleGraph_ForSymbol();
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(txID, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetVertexColor() {
|
||||
FGVertex focusedVertex = getFocusedVertex();
|
||||
|
||||
JComponent panel = focusedVertex.getComponent();
|
||||
ListingPanel listingPanel =
|
||||
(ListingPanel) TestUtils.getInstanceField("listingPanel", panel);
|
||||
Color startBackgrond = listingPanel.getTextBackgroundColor();
|
||||
Color testColor = Palette.RED;
|
||||
assertTrue("Unexpected start color--must change the test!",
|
||||
!testColor.equals(startBackgrond));
|
||||
|
||||
chooseColor(focusedVertex, testColor);
|
||||
|
||||
Color newBackground = listingPanel.getTextBackgroundColor();
|
||||
assertTrue(!startBackgrond.equals(newBackground));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSharedColorExperience() throws Exception {
|
||||
//
|
||||
// Tests the new way of coloring vertices, by way of the ColorizerService, which will
|
||||
// set the color in both the vertex and the listing (really just in the listing, but
|
||||
// the vertex displays this color.
|
||||
//
|
||||
|
||||
// install ColorizerPlugin
|
||||
tool.addPlugin(ColorizingPlugin.class.getName());
|
||||
|
||||
FGVertex vertex = getFocusedVertex();
|
||||
ColorizingService colorizingService = tool.getService(ColorizingService.class);
|
||||
Color appliedBackgroundColor =
|
||||
colorizingService.getBackgroundColor(vertex.getVertexAddress());
|
||||
|
||||
Color testColor = Palette.RED;
|
||||
assertTrue("Unexpected start color--must change the test!",
|
||||
!testColor.equals(appliedBackgroundColor));
|
||||
|
||||
chooseColor(vertex, testColor);
|
||||
|
||||
// make sure the service is also cognizant of the color change
|
||||
appliedBackgroundColor = colorizingService.getBackgroundColor(vertex.getVertexAddress());
|
||||
assertEquals(testColor, appliedBackgroundColor);
|
||||
|
||||
Color vBg = vertex.getBackgroundColor();
|
||||
assertEquals(appliedBackgroundColor, vBg);
|
||||
|
||||
//
|
||||
// Reload and make sure the color is re-applied to the vertex (this was broken)
|
||||
//
|
||||
Address vertexAddress = vertex.getVertexAddress();
|
||||
performReload();
|
||||
FGVertex reloadedVertex = vertex(vertexAddress);
|
||||
assertNotSame(vertex, reloadedVertex);
|
||||
vBg = reloadedVertex.getBackgroundColor();
|
||||
assertEquals(appliedBackgroundColor, vBg);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetMostRecentColorAction() throws Exception {
|
||||
//
|
||||
// Test that the 'set most recent color' action will set the color of the vertex *and*
|
||||
// in the Listing.
|
||||
//
|
||||
// install ColorizerPlugin
|
||||
tool.addPlugin(ColorizingPlugin.class.getName());
|
||||
|
||||
FGVertex focusedVertex = getFocusedVertex();
|
||||
ColorizingService colorizingService = tool.getService(ColorizingService.class);
|
||||
Color startBackgroundColor =
|
||||
colorizingService.getBackgroundColor(focusedVertex.getVertexAddress());
|
||||
|
||||
FGController controller = getFunctionGraphController();
|
||||
Color mostRecentColor = controller.getMostRecentColor();
|
||||
|
||||
Assert.assertNotEquals(
|
||||
"Test environment not setup correctly--should have default backgrond " +
|
||||
"colors applied",
|
||||
startBackgroundColor, mostRecentColor);
|
||||
|
||||
SetVertexMostRecentColorAction setRecentColorAction =
|
||||
getSetMostRecentColorAction(focusedVertex);
|
||||
performAction(setRecentColorAction, graphProvider, true);
|
||||
|
||||
Color newVertexBackgroundColor = focusedVertex.getBackgroundColor();
|
||||
assertEquals("'Set Most Recent Color' action did not apply that color to the vertex",
|
||||
mostRecentColor, newVertexBackgroundColor);
|
||||
|
||||
Color newBackgroundColor =
|
||||
colorizingService.getBackgroundColor(focusedVertex.getVertexAddress());
|
||||
assertEquals("'Set Most Recent Color' action did not apply that color to the color service",
|
||||
mostRecentColor, newBackgroundColor);
|
||||
}
|
||||
|
||||
// TODO: see SCR 9208 - we don't currently support this, although we could
|
||||
public void dont_testNavigatingBackwardsRestoresPerspectiveInfo_ZoomOutOn() throws Exception {
|
||||
//
|
||||
// Test, that with the default 'zoomed-out' option *on* for new graphs, we will restore the
|
||||
// user's previous graph perspective data when they navigate back to a function that
|
||||
// were examining (instead of zooming back out to the full view).
|
||||
//
|
||||
|
||||
hideSatellite();// for readability
|
||||
setGraphWindowSize(300, 300);// make window small for easier testing
|
||||
|
||||
setZoomOutOption(true);
|
||||
|
||||
// zoom in to a vertex (pick something beside the entry point)
|
||||
goTo("1004196");
|
||||
zoomInCompletely();
|
||||
|
||||
// move that vertex off center
|
||||
moveView(10);
|
||||
FGVertex v = getFocusedVertex();
|
||||
Point originalLocation = getPointInViewSpaceForVertex(getPrimaryGraphViewer(), v);
|
||||
|
||||
// 'go to' a new function to change the graph
|
||||
goTo("01002239");
|
||||
|
||||
// trigger a back navigation
|
||||
navigateBack();
|
||||
|
||||
// make sure that the zoom is restored (note: the point information reflects both
|
||||
// location and zoom)
|
||||
Point restoredLocation = getPointInViewSpaceForVertex(getPrimaryGraphViewer(), v);
|
||||
assertEquals(originalLocation, restoredLocation);
|
||||
}
|
||||
|
||||
// not sure how to trigger a selection from a node via the GUI...if we use the API method,
|
||||
// as it does now, the CodeBrowser does not get the callback, because that's how the system works.
|
||||
// If it is worth testing, and we can do it from the mouse, then have at it
|
||||
public void dont_testSelectionFromGraphToCodeBrowser() {
|
||||
FGData graphData = getFunctionGraphData();
|
||||
assertNotNull(graphData);
|
||||
assertTrue("Unexpectedly received an empty FunctionGraphData", graphData.hasResults());
|
||||
ProgramLocation location = getLocationForAddressString(startAddressString);
|
||||
assertTrue(graphData.containsLocation(location));
|
||||
FunctionGraph functionGraph = graphData.getFunctionGraph();
|
||||
|
||||
// locate vertex with cursor
|
||||
FGVertex focusedVertex = getFocusVertex(functionGraph);
|
||||
assertNotNull("We did not start with a focused vertex", focusedVertex);
|
||||
|
||||
AddressSetView addresses = focusedVertex.getAddresses();
|
||||
Address address = addresses.getMinAddress();
|
||||
focusedVertex.setProgramSelection(new ProgramSelection(address, address));
|
||||
|
||||
// make sure the code browser now contains a matching selection
|
||||
ProgramSelection firstSelection = focusedVertex.getProgramSelection();
|
||||
ListingPanel listingPanel = codeBrowser.getListingPanel();
|
||||
ProgramSelection codeBrowserSelection = listingPanel.getProgramSelection();
|
||||
assertTrue(!firstSelection.isEmpty());
|
||||
assertEquals("Selecting text in the vertex did not select text in the code browser",
|
||||
firstSelection, codeBrowserSelection);
|
||||
|
||||
Collection<FGVertex> vertices = functionGraph.getVertices();
|
||||
FGVertex otherVertex = null;
|
||||
for (FGVertex vertex : vertices) {
|
||||
if (vertex != focusedVertex) {
|
||||
otherVertex = vertex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertNotNull(otherVertex);
|
||||
|
||||
Address otherAddress = otherVertex.getAddresses().getMinAddress();
|
||||
otherVertex.setProgramSelection(new ProgramSelection(otherAddress, otherAddress));
|
||||
ProgramSelection secondSelection = otherVertex.getProgramSelection();
|
||||
assertTrue(!secondSelection.isEmpty());
|
||||
|
||||
codeBrowserSelection = listingPanel.getProgramSelection();
|
||||
|
||||
// the new code browser selection should have both of our vertex selections
|
||||
assertTrue(codeBrowserSelection.getMinAddress().equals(firstSelection.getMinAddress()));
|
||||
assertTrue(codeBrowserSelection.getMaxAddress().equals(secondSelection.getMaxAddress()));
|
||||
}
|
||||
|
||||
// TODO: see SCR 9208 - we don't currently support this, although we could
|
||||
public void dontTestNavigatingBackwardsRestoresPerspectiveInfo_ZoomOutOff() throws Exception {
|
||||
//
|
||||
// Test, that with the default 'zoomed-out' option *off* for new graphs, we will restore the
|
||||
// user's previous graph perspective data when they navigate back to a function that
|
||||
// were examining (instead of zooming back out to the full view).
|
||||
//
|
||||
|
||||
hideSatellite();// for readability
|
||||
setGraphWindowSize(300, 300);// make window small for easier testing
|
||||
|
||||
setZoomOutOption(false);
|
||||
|
||||
// zoom in to a vertex (pick something beside the entry point)
|
||||
goTo("1004196");
|
||||
zoomInCompletely();
|
||||
|
||||
// move that vertex off center
|
||||
moveView(10);
|
||||
FGVertex v = getFocusedVertex();
|
||||
Point originalLocation = getPointInViewSpaceForVertex(getPrimaryGraphViewer(), v);
|
||||
|
||||
// 'go to' a new function to change the graph
|
||||
goTo("01002239");
|
||||
|
||||
// trigger a back navigation
|
||||
navigateBack();
|
||||
|
||||
// make sure that the zoom is restored (note: the point information reflects both
|
||||
// location and zoom)
|
||||
Point restoredLocation = getPointInViewSpaceForVertex(getPrimaryGraphViewer(), v);
|
||||
assertEquals(originalLocation, restoredLocation);
|
||||
}
|
||||
|
||||
//
|
||||
// TODO: I have changed the groups such that you can group a single node, which lets you
|
||||
// replace the view of the node with user-defined text.
|
||||
//
|
||||
// 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
|
||||
// is again valid.
|
||||
//
|
||||
@Override
|
||||
public void dontTestRestoringWhenCodeBlocksHaveChanged_DoesntRegroup() {
|
||||
int transactionID = -1;
|
||||
try {
|
||||
transactionID = program.startTransaction(testName.getMethodName());
|
||||
doTestRestoringWhenCodeBlocksHaveChanged_DoesntRegroup();
|
||||
}
|
||||
finally {
|
||||
program.endTransaction(transactionID, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void doTestSplitAndMergeNodesOnStaleGraph_ForReference() {
|
||||
//
|
||||
// Test that we can split a node into two for reference operations:
|
||||
// 1) Adding a reference splits a node
|
||||
// 2) Removing a reference merges a node
|
||||
// 2) The above actions do not take place with automatic updates on
|
||||
//
|
||||
// Edges cases:
|
||||
// 1) Adding a reference to a location already containing a symbol does not split the node
|
||||
// 2) Removing a reference with other references at the vertex entry point does not merge nodes
|
||||
//
|
||||
|
||||
// Find a good test function.
|
||||
goToAddress("01002cf5");
|
||||
|
||||
// Find a node to manipulate
|
||||
FGData graphData = getFunctionGraphData();
|
||||
assertNotNull(graphData);
|
||||
assertTrue("Unexpectedly received an empty FunctionGraphData", graphData.hasResults());
|
||||
|
||||
String vertexAddressString = "01002d2b";
|
||||
Address vertexAddress = getAddress(vertexAddressString);
|
||||
ProgramLocation location = getLocationForAddressString(vertexAddressString);
|
||||
assertTrue(graphData.containsLocation(location));
|
||||
FunctionGraph functionGraph = graphData.getFunctionGraph();
|
||||
Graph<FGVertex, FGEdge> graph = functionGraph;
|
||||
FGVertex initialVertex = functionGraph.getVertexForAddress(vertexAddress);
|
||||
|
||||
// Find a location within that node to which we will add a label
|
||||
String referenceFromAddress = "01002d3d";
|
||||
String referenceToAddress = "01002d47";
|
||||
Address fromAddress = getAddress(referenceFromAddress);
|
||||
Address toAddress = getAddress(referenceToAddress);
|
||||
|
||||
//
|
||||
// Start with case 1 -- adding a reference
|
||||
//
|
||||
// Verify the graph is not stale
|
||||
FGController controller = getFunctionGraphController();
|
||||
FGView view = controller.getView();
|
||||
assertTrue(!view.isGraphViewStale());
|
||||
|
||||
// Add a reference
|
||||
AddMemRefCmd addCmd =
|
||||
new AddMemRefCmd(fromAddress, toAddress, RefType.DATA, SourceType.USER_DEFINED, -1);
|
||||
addCmd.applyTo(program);
|
||||
program.flushEvents();
|
||||
waitForSwing();
|
||||
|
||||
waitForAnimation();
|
||||
|
||||
// Verify the edited node has been split--the old vertex is gone; two new vertices exist
|
||||
assertTrue(!graph.containsVertex(initialVertex));
|
||||
FGVertex newParentVertex = functionGraph.getVertexForAddress(fromAddress);
|
||||
assertNotNull(newParentVertex);
|
||||
|
||||
FGVertex newChildVertex = functionGraph.getVertexForAddress(toAddress);
|
||||
assertNotNull(newChildVertex);
|
||||
|
||||
Collection<FGEdge> parentOutEdges = graph.getOutEdges(newParentVertex);
|
||||
assertTrue(parentOutEdges.size() == 1);
|
||||
assertEquals(newChildVertex, parentOutEdges.iterator().next().getEnd());
|
||||
|
||||
// Verify the graph is stale
|
||||
assertTrue(view.isGraphViewStale());
|
||||
|
||||
// Now remove the reference
|
||||
ReferenceManager referenceManager = program.getReferenceManager();
|
||||
Reference reference = referenceManager.getReference(fromAddress, toAddress, -1);
|
||||
assertNotNull(reference);
|
||||
RemoveReferenceCmd deleteCmd = new RemoveReferenceCmd(reference);
|
||||
deleteCmd.applyTo(program);
|
||||
program.flushEvents();
|
||||
waitForSwing();
|
||||
waitForAnimation();
|
||||
|
||||
// Verify the new nodes have been merged
|
||||
assertTrue(!graph.containsVertex(newParentVertex));
|
||||
assertTrue(!graph.containsVertex(newChildVertex));
|
||||
|
||||
FGVertex newOldVertex = functionGraph.getVertexForAddress(vertexAddress);
|
||||
assertNotNull(newParentVertex);
|
||||
|
||||
FGVertex newOldChildVertex = functionGraph.getVertexForAddress(toAddress);
|
||||
assertNotNull(newChildVertex);
|
||||
|
||||
assertEquals(newOldVertex, newOldChildVertex);// this should be the same after the merge
|
||||
|
||||
//
|
||||
// Edge Cases
|
||||
//
|
||||
|
||||
// Relayout to put the graph in a known good state
|
||||
performRelayout();
|
||||
|
||||
// Find a node to manipulate (one that already has a reference)
|
||||
referenceFromAddress = "01002d29";
|
||||
referenceToAddress = "01002d52";
|
||||
fromAddress = getAddress(referenceFromAddress);
|
||||
toAddress = getAddress(referenceToAddress);
|
||||
FGVertex fromVertex = functionGraph.getVertexForAddress(vertexAddress);
|
||||
assertTrue(graph.containsVertex(fromVertex));
|
||||
|
||||
// Add a reference to the entry point of the node
|
||||
addCmd =
|
||||
new AddMemRefCmd(fromAddress, toAddress, RefType.DATA, SourceType.USER_DEFINED, -1);
|
||||
addCmd.applyTo(program);
|
||||
program.flushEvents();
|
||||
waitForSwing();
|
||||
waitForAnimation();
|
||||
|
||||
// Verify the node was not split
|
||||
assertTrue(graph.containsVertex(fromVertex));
|
||||
|
||||
// Now remove the reference we added
|
||||
reference = referenceManager.getReference(fromAddress, toAddress, -1);
|
||||
assertNotNull(reference);
|
||||
deleteCmd = new RemoveReferenceCmd(reference);
|
||||
deleteCmd.applyTo(program);
|
||||
program.flushEvents();
|
||||
waitForSwing();
|
||||
waitForAnimation();
|
||||
|
||||
// Verify the node was not merged
|
||||
assertTrue(graph.containsVertex(fromVertex));
|
||||
}
|
||||
|
||||
protected void doTestRestoringWhenCodeBlocksHaveChanged_DoesntRegroup() {
|
||||
//
|
||||
// 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
|
||||
// the group vertex user settings (i.e., when restarting Ghidra, the previously grouped
|
||||
// vertices should reappear).
|
||||
//
|
||||
// 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
|
||||
// be found by the regrouping algorithm. Furthermore, the regrouping will not take place
|
||||
// if at least two vertices cannot be found.
|
||||
//
|
||||
|
||||
//
|
||||
// Pick a function and group some nodes.
|
||||
//
|
||||
FGData graphData = graphFunction("01002cf5");
|
||||
FunctionGraph functionGraph = graphData.getFunctionGraph();
|
||||
|
||||
Set<FGVertex> ungroupedVertices =
|
||||
selectVertices(functionGraph, "01002d11" /* LAB_01002d11 */, "01002cf5" /* ghidra */);
|
||||
|
||||
group(ungroupedVertices);
|
||||
|
||||
// 5 edges expected:
|
||||
// -01002cf5: 2 out
|
||||
// -01002cf5: 2 in, 1 out
|
||||
int expectedGroupedEdgeCount = 5;
|
||||
GroupedFunctionGraphVertex groupedVertex = validateNewGroupedVertexFromVertices(
|
||||
functionGraph, ungroupedVertices, expectedGroupedEdgeCount);
|
||||
|
||||
AddressSetView addresses = groupedVertex.getAddresses();
|
||||
Address minAddress = addresses.getMinAddress();
|
||||
|
||||
//
|
||||
// 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
|
||||
// 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
|
||||
// data saved in the program has been used to re-group.
|
||||
//
|
||||
graphFunction("0100415a");
|
||||
clearCache();
|
||||
|
||||
//
|
||||
// Add a label to trigger a code block change
|
||||
//
|
||||
createLabel("01002d18");// in the middle of the LAB_01002d11 code block
|
||||
|
||||
//
|
||||
// Relaunch the graph, which will use the above persisted group settings...
|
||||
//
|
||||
graphData = graphFunction("01002cf5");
|
||||
waitForAnimation();// the re-grouping may be using animation, which runs after the graph is loaded
|
||||
functionGraph = graphData.getFunctionGraph();
|
||||
FGVertex expectedGroupVertex = functionGraph.getVertexForAddress(minAddress);
|
||||
assertFalse(expectedGroupVertex instanceof GroupedFunctionGraphVertex);
|
||||
}
|
||||
|
||||
protected void doTestSplitAndMergeNodesOnStaleGraph_ForSymbol() {
|
||||
//
|
||||
// Test that we can split a node into two for symbol operations:
|
||||
// 1) Adding a symbol splits a node
|
||||
// 2) Removing a symbol merges a node
|
||||
// 2) The above actions do not take place with automatic updates on
|
||||
//
|
||||
// Edges cases:
|
||||
// 1) Adding a symbol to a location already containing a symbol does not split the node
|
||||
// 2) Removing a symbol with other symbols at the vertex entry point does not merge nodes
|
||||
//
|
||||
|
||||
// Find a good test function.
|
||||
goToAddress("01002cf5");
|
||||
|
||||
// Find a node to manipulate
|
||||
FGData graphData = getFunctionGraphData();
|
||||
assertNotNull(graphData);
|
||||
assertTrue("Unexpectedly received an empty FunctionGraphData", graphData.hasResults());
|
||||
|
||||
String vertexAddressString = "01002d2b";
|
||||
Address vertexAddress = getAddress(vertexAddressString);
|
||||
ProgramLocation location = getLocationForAddressString(vertexAddressString);
|
||||
assertTrue(graphData.containsLocation(location));
|
||||
FunctionGraph functionGraph = graphData.getFunctionGraph();
|
||||
Graph<FGVertex, FGEdge> graph = functionGraph;
|
||||
FGVertex initialVertex = functionGraph.getVertexForAddress(vertexAddress);
|
||||
|
||||
// Find a location within that node to which we will add a label
|
||||
String labelAddressString = "01002d47";
|
||||
Address labelAddress = getAddress(labelAddressString);
|
||||
|
||||
//
|
||||
// Start with case 1 -- adding a symbol
|
||||
//
|
||||
// Verify the graph is not stale
|
||||
FGController controller = getFunctionGraphController();
|
||||
FGView view = controller.getView();
|
||||
assertTrue(!view.isGraphViewStale());
|
||||
|
||||
// Add a label
|
||||
String labelName = testName.getMethodName();
|
||||
AddLabelCmd addCmd = new AddLabelCmd(labelAddress, labelName, SourceType.USER_DEFINED);
|
||||
addCmd.applyTo(program);
|
||||
program.flushEvents();
|
||||
waitForSwing();
|
||||
waitForAnimation();
|
||||
|
||||
// Verify the edited node has been split--the old vertex is gone; two new vertices exist
|
||||
assertTrue(!graph.containsVertex(initialVertex));
|
||||
FGVertex newParentVertex = functionGraph.getVertexForAddress(vertexAddress);
|
||||
assertNotNull(newParentVertex);
|
||||
|
||||
FGVertex newChildVertex = functionGraph.getVertexForAddress(labelAddress);
|
||||
assertNotNull(newChildVertex);
|
||||
|
||||
Collection<FGEdge> parentOutEdges = graph.getOutEdges(newParentVertex);
|
||||
assertTrue(parentOutEdges.size() == 1);
|
||||
assertEquals(newChildVertex, parentOutEdges.iterator().next().getEnd());
|
||||
|
||||
// Verify the graph is stale
|
||||
assertTrue(view.isGraphViewStale());
|
||||
|
||||
// Now remove the label
|
||||
DeleteLabelCmd deleteCmd = new DeleteLabelCmd(labelAddress, labelName);
|
||||
deleteCmd.applyTo(program);
|
||||
program.flushEvents();
|
||||
waitForSwing();
|
||||
waitForAnimation();
|
||||
|
||||
// Verify the new nodes have been merged
|
||||
assertTrue(!graph.containsVertex(newParentVertex));
|
||||
assertTrue(!graph.containsVertex(newChildVertex));
|
||||
|
||||
FGVertex newOldVertex = functionGraph.getVertexForAddress(vertexAddress);
|
||||
assertNotNull(newParentVertex);
|
||||
|
||||
FGVertex newOldChildVertex = functionGraph.getVertexForAddress(labelAddress);
|
||||
assertNotNull(newChildVertex);
|
||||
|
||||
assertEquals(newOldVertex, newOldChildVertex);// this should be the same after the merge
|
||||
|
||||
// Verify the graph is still stale
|
||||
assertTrue(view.isGraphViewStale());
|
||||
|
||||
//
|
||||
// Edge Cases
|
||||
//
|
||||
|
||||
// Relayout to put the graph in a known good state
|
||||
performRelayout();
|
||||
|
||||
// Find a node to manipulate (one that already has a label)
|
||||
FGVertex vertexWithLabel = functionGraph.getVertexForAddress(vertexAddress);
|
||||
|
||||
// Add a label to the entry point of the node
|
||||
labelName = testName.getMethodName() + "2";
|
||||
addCmd = new AddLabelCmd(vertexAddress, labelName, SourceType.USER_DEFINED);
|
||||
addCmd.applyTo(program);
|
||||
program.flushEvents();
|
||||
waitForSwing();
|
||||
waitForAnimation();
|
||||
|
||||
// Verify the node was not split
|
||||
assertTrue(graph.containsVertex(vertexWithLabel));
|
||||
|
||||
// Verify the graph is stale
|
||||
assertTrue(view.isGraphViewStale());
|
||||
|
||||
// Now remove the label we added
|
||||
deleteCmd = new DeleteLabelCmd(vertexAddress, labelName);
|
||||
deleteCmd.applyTo(program);
|
||||
program.flushEvents();
|
||||
waitForSwing();
|
||||
waitForAnimation();
|
||||
|
||||
// Verify the node was not merged
|
||||
assertTrue(graph.containsVertex(vertexWithLabel));
|
||||
}
|
||||
|
||||
// note: unreliable--garbage collection works differently across platforms (sad face)
|
||||
// public void testClearCacheAndMemoryLeak() {
|
||||
// //
|
||||
// // Test that when we clear the cache of a graph the vertices of that graph will be
|
||||
// // garbage collected
|
||||
// //
|
||||
// WeakSet<FunctionGraphVertex> weakSet =
|
||||
// WeakDataStructureFactory.createSingleThreadAccessWeakSet();
|
||||
//
|
||||
// FunctionGraphData graphData = getFunctionGraphData();
|
||||
// assertNotNull(graphData);
|
||||
// assertTrue("Unexpectedly received an empty FunctionGraphData", graphData.hasResults());
|
||||
//
|
||||
// FunctionGraph graph = graphData.getFunctionGraph();
|
||||
// FunctionGraphVertex rootVertex = graph.getRootVertex();
|
||||
// assertNotNull(rootVertex);
|
||||
//
|
||||
// weakSet.add(rootVertex);
|
||||
// assertTrue(weakSet.iterator().hasNext());
|
||||
//
|
||||
// // move to a new function so that we the FG will not be holding a reference to the
|
||||
// // originally graphed function
|
||||
// String address = "01002239";
|
||||
// goToAddress(address);
|
||||
//
|
||||
// FunctionGraphData newGraphData = getFunctionGraphData();
|
||||
// assertNotNull(newGraphData);
|
||||
// assertTrue("Unexpectedly received an empty FunctionGraphData", newGraphData.hasResults());
|
||||
//
|
||||
// assertTrue(
|
||||
// "Function Graph did not graph a new function as expected at address: " + address,
|
||||
// !graphData.equals(newGraphData));
|
||||
//
|
||||
// triggerGraphDisposal(graphData);
|
||||
//
|
||||
// rootVertex = null;
|
||||
// graph = null;
|
||||
// graphData = null;
|
||||
//
|
||||
// // let (force) the Garbage Collector run
|
||||
// System.gc();
|
||||
// sleep(100);
|
||||
// System.gc();
|
||||
// sleep(100);
|
||||
// System.gc();
|
||||
// sleep(100);
|
||||
//
|
||||
// boolean isNotCollected = weakSet.iterator().hasNext();
|
||||
// assertFalse(isNotCollected);
|
||||
// }
|
||||
//
|
||||
// private void triggerGraphDisposal(FunctionGraphData dataToDispose) {
|
||||
// Object controller = getInstanceField("controller", graphProvider);
|
||||
// LRUMap<?, ?> cache = (LRUMap<?, ?>) getInstanceField("graphCache", controller);
|
||||
// cache.clear();
|
||||
// dataToDispose.dispose();
|
||||
// }
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue