Created the concept of graph types and display options for those graph types.

This commit is contained in:
ghidravore
2021-08-09 13:18:23 -04:00
parent cf293853e8
commit 210cc0bca0
84 changed files with 4102 additions and 1822 deletions
@@ -63,7 +63,7 @@ public class DisplayAsGraphAction extends DisplayAsAction {
public void addGraph(ObjectContainer container) { public void addGraph(ObjectContainer container) {
GraphDisplayProvider graphProvider = graphBroker.getDefaultGraphDisplayProvider(); GraphDisplayProvider graphProvider = graphBroker.getDefaultGraphDisplayProvider();
AttributedGraph graph = new AttributedGraph(); AttributedGraph graph = new AttributedGraph(container.getName(), new EmptyGraphType());
AttributedVertex start = graph.addVertex(container.toString(), container.getName()); AttributedVertex start = graph.addVertex(container.toString(), container.getName());
graphContainer(container, graph, start); graphContainer(container, graph, start);
try { try {
@@ -81,7 +81,7 @@ public class DisplayFilteredGraphAction extends DisplayFilteredAction {
@Override @Override
protected void finishGetOffspring(ObjectContainer container, final List<String> path) { protected void finishGetOffspring(ObjectContainer container, final List<String> path) {
GraphDisplayProvider graphProvider = graphBroker.getDefaultGraphDisplayProvider(); GraphDisplayProvider graphProvider = graphBroker.getDefaultGraphDisplayProvider();
AttributedGraph graph = new AttributedGraph(); AttributedGraph graph = new AttributedGraph(container.getName(), new EmptyGraphType());
AttributedVertex start = graph.addVertex(container.getName(), container.toString()); AttributedVertex start = graph.addVertex(container.getName(), container.toString());
graphContainer(container, graph, start); graphContainer(container, graph, start);
try { try {
@@ -22,7 +22,7 @@ import ghidra.service.graph.*;
* Script to generate graph to test BrandesKopf algorithm * Script to generate graph to test BrandesKopf algorithm
*/ */
public class GenerateBrandesKopfGraphScript extends GhidraScript { public class GenerateBrandesKopfGraphScript extends GhidraScript {
private AttributedGraph graph = new AttributedGraph(); private AttributedGraph graph = new AttributedGraph("test", new EmptyGraphType());
private int nextEdgeID = 1; private int nextEdgeID = 1;
@Override @Override
@@ -22,7 +22,7 @@ import ghidra.service.graph.*;
* Sample script to test graph service * Sample script to test graph service
*/ */
public class GenerateTestGraphScript extends GhidraScript { public class GenerateTestGraphScript extends GhidraScript {
private AttributedGraph graph = new AttributedGraph(); private AttributedGraph graph = new AttributedGraph("Test", new EmptyGraphType());
private int nextEdgeID = 1; private int nextEdgeID = 1;
@Override @Override
@@ -22,7 +22,7 @@ import ghidra.service.graph.*;
* Example script for creating and displaying a graph in ghidra * Example script for creating and displaying a graph in ghidra
*/ */
public class ExampleGraphServiceScript extends GhidraScript { public class ExampleGraphServiceScript extends GhidraScript {
private AttributedGraph graph = new AttributedGraph(); private AttributedGraph graph = new AttributedGraph("Test", new EmptyGraphType());
private int nextEdgeID = 1; private int nextEdgeID = 1;
@Override @Override
@@ -107,7 +107,7 @@ public class GraphClassesScript extends GhidraScript {
*/ */
private AttributedGraph createGraph() throws Exception { private AttributedGraph createGraph() throws Exception {
AttributedGraph g = new AttributedGraph(); AttributedGraph g = new AttributedGraph("Test Graph", new EmptyGraphType());
for (Structure classStructure : classStructures) { for (Structure classStructure : classStructures) {
@@ -15,6 +15,8 @@
*/ */
package ghidra.app.plugin.core.datamgr.actions; package ghidra.app.plugin.core.datamgr.actions;
import java.awt.Color;
import ghidra.app.util.ToolTipUtils; import ghidra.app.util.ToolTipUtils;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.service.graph.*; import ghidra.service.graph.*;
@@ -35,9 +37,8 @@ public class TypeGraphTask extends Task {
private String graphTitle; private String graphTitle;
private GraphDisplayProvider graphService; private GraphDisplayProvider graphService;
public static final String TYPE_ATTRIBUTE = "Type"; public static final String COMPOSITE = "Composite";
public static final String EMBEDDED = "Composite"; public static final String REFERENCE = "Reference";
public static final String POINTER = "Reference";
/* /*
* Constructor * Constructor
@@ -57,8 +58,18 @@ public class TypeGraphTask extends Task {
@Override @Override
public void run(TaskMonitor monitor) throws CancelledException { public void run(TaskMonitor monitor) throws CancelledException {
GraphType graphType = new GraphTypeBuilder("Data Graph")
.edgeType(REFERENCE)
.edgeType(COMPOSITE)
.build();
AttributedGraph graph = new AttributedGraph(); GraphDisplayOptions options = new GraphDisplayOptionsBuilder(graphType)
.defaultVertexColor(Color.BLUE)
.edge(COMPOSITE, Color.MAGENTA)
.edge(REFERENCE, Color.BLUE)
.build();
AttributedGraph graph = new AttributedGraph(graphTitle, graphType);
try { try {
if (type instanceof Pointer) { if (type instanceof Pointer) {
recursePointer((Pointer) type, graph, null, monitor); recursePointer((Pointer) type, graph, null, monitor);
@@ -75,7 +86,7 @@ public class TypeGraphTask extends Task {
GraphDisplay display; GraphDisplay display;
try { try {
display = graphService.getGraphDisplay(false, monitor); display = graphService.getGraphDisplay(false, monitor);
display.setGraph(graph, graphTitle, false, monitor); display.setGraph(graph, options, graphTitle, false, monitor);
} }
catch (GraphException e) { catch (GraphException e) {
Msg.showError(this, null, "Data Type Graph Error", Msg.showError(this, null, "Data Type Graph Error",
@@ -94,11 +105,8 @@ public class TypeGraphTask extends Task {
} }
else { else {
AttributedEdge edge = graph.addEdge(lastVertex, newVertex); AttributedEdge edge = graph.addEdge(lastVertex, newVertex);
if (edgeType == POINTER) { edge.setEdgeType(edgeType);
edge.setAttribute("Color", "Blue"); if (edge.hasAttribute(AttributedGraph.WEIGHT)) {
}
edge.setAttribute(TYPE_ATTRIBUTE, edgeType);
if (edge.hasAttribute("Weight")) {
//did this already, don't cycle //did this already, don't cycle
return; return;
} }
@@ -115,7 +123,7 @@ public class TypeGraphTask extends Task {
recursePointer((Pointer) dt, graph, newVertex, monitor); recursePointer((Pointer) dt, graph, newVertex, monitor);
} }
else if (dt instanceof Composite) { else if (dt instanceof Composite) {
recurseComposite((Composite) dt, graph, newVertex, EMBEDDED, monitor); recurseComposite((Composite) dt, graph, newVertex, COMPOSITE, monitor);
} }
} }
} }
@@ -135,7 +143,7 @@ public class TypeGraphTask extends Task {
recursePointer((Pointer) ptrType, graph, lastVertex, monitor); recursePointer((Pointer) ptrType, graph, lastVertex, monitor);
} }
else if (ptrType instanceof Composite) { else if (ptrType instanceof Composite) {
recurseComposite((Composite) ptrType, graph, lastVertex, POINTER, monitor); recurseComposite((Composite) ptrType, graph, lastVertex, REFERENCE, monitor);
} }
} }
@@ -148,10 +148,11 @@ public class GraphDisplayBrokerPlugin extends Plugin
} }
@Override @Override
public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, Map<String, String> properties, public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, TaskMonitor monitor)
TaskMonitor monitor) throws GraphException { throws GraphException {
if (defaultGraphDisplayProvider != null) { if (defaultGraphDisplayProvider != null) {
return defaultGraphDisplayProvider.getGraphDisplay(reuseGraph, properties, monitor); return defaultGraphDisplayProvider.getGraphDisplay(reuseGraph, monitor);
} }
return null; return null;
} }
@@ -15,7 +15,7 @@
*/ */
package ghidra.app.services; package ghidra.app.services;
import java.util.*; import java.util.List;
import ghidra.app.plugin.core.graph.GraphDisplayBrokerListener; import ghidra.app.plugin.core.graph.GraphDisplayBrokerListener;
import ghidra.app.plugin.core.graph.GraphDisplayBrokerPlugin; import ghidra.app.plugin.core.graph.GraphDisplayBrokerPlugin;
@@ -60,25 +60,7 @@ public interface GraphDisplayBroker {
* @return a {@link GraphDisplay} object to sends graphs to be displayed or exported. * @return a {@link GraphDisplay} object to sends graphs to be displayed or exported.
* @throws GraphException thrown if an error occurs trying to get a graph display * @throws GraphException thrown if an error occurs trying to get a graph display
*/ */
public default GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, TaskMonitor monitor) public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, TaskMonitor monitor)
throws GraphException {
return getDefaultGraphDisplay(reuseGraph, Collections.emptyMap(), monitor);
}
/**
* A convenience method for getting a {@link GraphDisplay} from the currently active provider
*
* <p>This method allows users to override default graph properties for the graph provider
* being created. See the graph provider implementation for a list of supported properties
*
* @param reuseGraph if true, the provider will attempt to re-use a current graph display
* @param properties a {@code Map} of property key/values that can be used to customize the display
* @param monitor the {@link TaskMonitor} that can be used to cancel the operation
* @return a {@link GraphDisplay} object to sends graphs to be displayed or exported.
* @throws GraphException thrown if an error occurs trying to get a graph display
*/
public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, Map<String, String> properties,
TaskMonitor monitor)
throws GraphException; throws GraphException;
/** /**
@@ -0,0 +1,24 @@
/* ###
* 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.graph;
public class BlockFlowGraphType extends ProgramGraphType {
public BlockFlowGraphType() {
super("Block Flow Graph", "Shows program basic block flow");
}
}
@@ -0,0 +1,24 @@
/* ###
* 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.graph;
public class CallGraphType extends ProgramGraphType {
public CallGraphType() {
super("Call Graph", "Shows relationships between functions");
}
}
@@ -0,0 +1,25 @@
/* ###
* 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.graph;
public class CodeFlowGraphType extends ProgramGraphType {
public CodeFlowGraphType() {
super("Code Flow Graph",
"Shows code block flow (similar to Block Flow graph type, but shows the code in each veretx)");
}
}
@@ -0,0 +1,24 @@
/* ###
* 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.graph;
public class DataFlowGraphType extends ProgramGraphType {
public DataFlowGraphType() {
super("Data Flow Graph", "Shows program data relationships");
}
}
@@ -0,0 +1,98 @@
/* ###
* 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.graph;
import static ghidra.graph.ProgramGraphType.*;
import static ghidra.service.graph.VertexShape.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.service.graph.GraphDisplayOptions;
import ghidra.service.graph.VertexShape;
import ghidra.util.WebColors;
/**
* {@link GraphDisplayOptions} for {@link ProgramGraphType}
*/
public class ProgramGraphDisplayOptions extends GraphDisplayOptions {
/**
* constructor
* @param graphType the specific ProgramGraphType subclass for these options
* @param tool if non-null, will load values from tool options
*/
public ProgramGraphDisplayOptions(ProgramGraphType graphType, PluginTool tool) {
super(graphType, tool);
}
@Override
protected void initializeDefaults() {
setDefaultVertexShape(ELLIPSE);
setDefaultVertexColor(WebColors.RED);
setDefaultEdgeColor(WebColors.RED);
setFavoredEdgeType(FALL_THROUGH);
configureVertexType(BODY, RECTANGLE, WebColors.BLUE);
configureVertexType(ENTRY, TRIANGLE_DOWN, WebColors.DARK_ORANGE);
configureVertexType(EXIT, TRIANGLE_UP, WebColors.DARK_MAGENTA);
configureVertexType(SWITCH, DIAMOND, WebColors.DARK_CYAN);
configureVertexType(EXTERNAL, RECTANGLE, WebColors.DARK_GREEN);
configureVertexType(BAD, ELLIPSE, WebColors.RED);
configureVertexType(DATA, ELLIPSE, WebColors.PINK);
configureVertexType(ENTRY_NEXUS, ELLIPSE, WebColors.WHEAT);
configureVertexType(INSTRUCTION, VertexShape.HEXAGON, WebColors.BLUE);
configureVertexType(STACK, RECTANGLE, WebColors.GREEN);
configureEdgeType(ENTRY_EDGE, WebColors.GRAY);
configureEdgeType(FALL_THROUGH, WebColors.BLUE);
configureEdgeType(UNCONDITIONAL_JUMP, WebColors.DARK_GREEN);
configureEdgeType(UNCONDITIONAL_CALL, WebColors.DARK_ORANGE);
configureEdgeType(TERMINATOR, WebColors.PURPLE);
configureEdgeType(JUMP_TERMINATOR, WebColors.PURPLE);
configureEdgeType(INDIRECTION, WebColors.PINK);
configureEdgeType(CONDITIONAL_JUMP, WebColors.DARK_GOLDENROD);
configureEdgeType(CONDITIONAL_CALL, WebColors.DARK_ORANGE);
configureEdgeType(CONDITIONAL_TERMINATOR, WebColors.PURPLE);
configureEdgeType(CONDITIONAL_CALL_TERMINATOR, WebColors.PURPLE);
configureEdgeType(COMPUTED_JUMP, WebColors.CYAN);
configureEdgeType(COMPUTED_CALL, WebColors.CYAN);
configureEdgeType(COMPUTED_CALL_TERMINATOR, WebColors.PURPLE);
configureEdgeType(CONDITIONAL_COMPUTED_CALL, WebColors.CYAN);
configureEdgeType(CONDITIONAL_COMPUTED_JUMP, WebColors.CYAN);
configureEdgeType(CALL_OVERRIDE_UNCONDITIONAL, WebColors.RED);
configureEdgeType(JUMP_OVERRIDE_UNCONDITIONAL, WebColors.RED);
configureEdgeType(CALLOTHER_OVERRIDE_CALL, WebColors.RED);
configureEdgeType(CALLOTHER_OVERRIDE_JUMP, WebColors.RED);
configureEdgeType(READ, WebColors.GREEN);
configureEdgeType(WRITE, WebColors.RED);
configureEdgeType(READ_WRITE, WebColors.DARK_GOLDENROD);
configureEdgeType(UNKNOWN_DATA, WebColors.BLACK);
configureEdgeType(EXTERNAL_REF, WebColors.PURPLE);
configureEdgeType(READ_INDIRECT, WebColors.DARK_GREEN);
configureEdgeType(WRITE_INDIRECT, WebColors.DARK_RED);
configureEdgeType(READ_WRITE_INDIRECT, WebColors.BROWN);
configureEdgeType(DATA_INDIRECT, WebColors.DARK_ORANGE);
configureEdgeType(PARAM, WebColors.CYAN);
configureEdgeType(THUNK, WebColors.BLUE);
}
}
@@ -0,0 +1,126 @@
/* ###
* 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.graph;
import java.util.*;
import org.apache.commons.text.WordUtils;
import ghidra.program.model.symbol.RefType;
import ghidra.service.graph.GraphType;
/**
* Defines a common set of vertex and edge types {@link GraphType} for program code and data flow
* graphs. Each specific type of program graph will use a subclass to specifically identify the
* graph type.
*/
public abstract class ProgramGraphType extends GraphType {
private static Map<RefType, String> refTypeToEdgeTypeMap = new HashMap<>();
private static List<String> vertexTypes = new ArrayList<>();
private static List<String> edgeTypes = new ArrayList<>();
//@formatter:off
// Vertex Types
public static final String BODY = vertex("Body");
public static final String ENTRY = vertex("Entry");
public static final String EXIT = vertex("Exit");
public static final String SWITCH = vertex("Switch");
public static final String EXTERNAL = vertex("External");
public static final String BAD = vertex("Bad");
public static final String INSTRUCTION = vertex("Instruction");
public static final String DATA = vertex("Data");
public static final String ENTRY_NEXUS = vertex("Entry-Nexus");
public static final String STACK = vertex("Stack");
// Edge Types - Flow
public static final String ENTRY_EDGE = edge("Entry"); // This edge if for adding an "Entry Nexus" Vertex
public static final String FALL_THROUGH = edge(map(RefType.FALL_THROUGH));
public static final String UNCONDITIONAL_JUMP = edge(map(RefType.UNCONDITIONAL_JUMP));
public static final String UNCONDITIONAL_CALL = edge(map(RefType.UNCONDITIONAL_CALL));
public static final String TERMINATOR = edge(map(RefType.TERMINATOR));
public static final String JUMP_TERMINATOR = edge(map(RefType.JUMP_TERMINATOR));
public static final String INDIRECTION = edge(map(RefType.INDIRECTION));
public static final String CONDITIONAL_JUMP = edge(map(RefType.CONDITIONAL_JUMP));
public static final String CONDITIONAL_CALL = edge(map(RefType.CONDITIONAL_CALL));
public static final String CONDITIONAL_TERMINATOR = edge(map(RefType.CONDITIONAL_TERMINATOR));
public static final String CONDITIONAL_CALL_TERMINATOR =edge(map(RefType.CONDITIONAL_CALL_TERMINATOR));
public static final String COMPUTED_JUMP = edge(map(RefType.COMPUTED_JUMP));
public static final String COMPUTED_CALL = edge(map(RefType.COMPUTED_CALL));
public static final String COMPUTED_CALL_TERMINATOR = edge(map(RefType.COMPUTED_CALL_TERMINATOR));
public static final String CONDITIONAL_COMPUTED_CALL = edge(map(RefType.CONDITIONAL_COMPUTED_CALL));
public static final String CONDITIONAL_COMPUTED_JUMP =edge(map(RefType.CONDITIONAL_COMPUTED_JUMP));
public static final String CALL_OVERRIDE_UNCONDITIONAL = edge(map(RefType.CALL_OVERRIDE_UNCONDITIONAL));
public static final String JUMP_OVERRIDE_UNCONDITIONAL = edge(map(RefType.CALL_OVERRIDE_UNCONDITIONAL));
public static final String CALLOTHER_OVERRIDE_CALL = edge(map(RefType.CALL_OVERRIDE_UNCONDITIONAL));
public static final String CALLOTHER_OVERRIDE_JUMP = edge(map(RefType.CALL_OVERRIDE_UNCONDITIONAL));
// Edge Types Data Refs
public static final String READ = edge(map(RefType.READ));
public static final String WRITE = edge(map(RefType.WRITE));
public static final String READ_WRITE = edge(map(RefType.READ_WRITE));
public static final String UNKNOWN_DATA = edge(map(RefType.DATA));
public static final String EXTERNAL_REF = edge(map(RefType.EXTERNAL_REF));
public static final String READ_INDIRECT = edge(map(RefType.READ_IND));
public static final String WRITE_INDIRECT = edge(map(RefType.WRITE_IND));
public static final String READ_WRITE_INDIRECT = edge(map(RefType.READ_WRITE_IND));
public static final String DATA_INDIRECT = edge(map(RefType.DATA_IND));
public static final String PARAM = edge(map(RefType.PARAM));
public static final String THUNK = edge(map(RefType.THUNK));
//@formatter:on
protected ProgramGraphType(String name, String description) {
super(name, description, vertexTypes, edgeTypes);
}
private static String vertex(String vertexType) {
vertexTypes.add(vertexType);
return vertexType;
}
private static String edge(String edgeType) {
edgeTypes.add(edgeType);
return edgeType;
}
private static String map(RefType refType) {
String edgeTypeName = fixup(refType.getName());
refTypeToEdgeTypeMap.put(refType, edgeTypeName);
return edgeTypeName;
}
private static String fixup(String name) {
name = name.replace('_', ' ');
return WordUtils.capitalizeFully(name);
}
public static String getEdgeType(RefType refType) {
return refTypeToEdgeTypeMap.get(refType);
}
@Override
public String getOptionsName() {
return "Program Graph Display Options";
}
}
@@ -160,11 +160,12 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
// get the options panel // get the options panel
ScrollableOptionsEditor simpleOptionsPanel = ScrollableOptionsEditor simpleOptionsPanel =
(ScrollableOptionsEditor) getEditorPanel(consoleNode); (ScrollableOptionsEditor) getEditorPanel(consoleNode);
assertNotNull(simpleOptionsPanel); JComponent comp = simpleOptionsPanel.getComponent();
assertTrue(simpleOptionsPanel.isShowing()); assertNotNull(comp);
assertTrue(comp.isShowing());
String optionName = (String) getInstanceField("MAXIMUM_CHARACTERS_OPTION_NAME", textPane); String optionName = (String) getInstanceField("MAXIMUM_CHARACTERS_OPTION_NAME", textPane);
final Component component = findPairedComponent(simpleOptionsPanel, optionName); final Component component = findPairedComponent(comp, optionName);
assertNotNull(component); assertNotNull(component);
// click the option to toggle its state // click the option to toggle its state
@@ -244,7 +245,8 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(parentNode); ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(parentNode);
assertNotNull("Did not find options editor for name: " + simpleName, editor); assertNotNull("Did not find options editor for name: " + simpleName, editor);
assertNotNull("simpleName = " + simpleName, findPairedComponent(editor, simpleName)); assertNotNull("simpleName = " + simpleName,
findPairedComponent(editor.getComponent(), simpleName));
} }
} }
@@ -273,7 +275,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
} }
ScrollableOptionsEditor p = (ScrollableOptionsEditor) getEditorPanel(parent); ScrollableOptionsEditor p = (ScrollableOptionsEditor) getEditorPanel(parent);
assertNotNull(p); assertNotNull(p);
assertNotNull(findPairedComponent(p, simpleName)); assertNotNull(findPairedComponent(p.getComponent(), simpleName));
} }
} }
@@ -331,9 +333,10 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor simpleOptionsPanel = ScrollableOptionsEditor simpleOptionsPanel =
(ScrollableOptionsEditor) getEditorPanel(toolNode); (ScrollableOptionsEditor) getEditorPanel(toolNode);
assertNotNull(simpleOptionsPanel); assertNotNull(simpleOptionsPanel);
assertTrue(simpleOptionsPanel.isShowing()); JComponent comp = simpleOptionsPanel.getComponent();
assertTrue(comp.isShowing());
Component component = findPairedComponent(simpleOptionsPanel, "Favorite Color"); Component component = findPairedComponent(comp, "Favorite Color");
assertNotNull(component); assertNotNull(component);
Rectangle rect = component.getBounds(); Rectangle rect = component.getBounds();
clickMouse(component, 1, rect.x, rect.y, 2, 0); clickMouse(component, 1, rect.x, rect.y, 2, 0);
@@ -366,10 +369,11 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor simpleOptionsPanel = ScrollableOptionsEditor simpleOptionsPanel =
(ScrollableOptionsEditor) getEditorPanel(buttonNode); (ScrollableOptionsEditor) getEditorPanel(buttonNode);
assertNotNull(simpleOptionsPanel); assertNotNull(simpleOptionsPanel);
assertTrue(simpleOptionsPanel.isShowing()); JComponent comp = simpleOptionsPanel.getComponent();
assertTrue(comp.isShowing());
PropertySelector ps = PropertySelector ps =
(PropertySelector) findPairedComponent(simpleOptionsPanel, "Mouse Button To Activate"); (PropertySelector) findPairedComponent(comp, "Mouse Button To Activate");
assertNotNull(ps); assertNotNull(ps);
runSwing(() -> ps.setSelectedIndex(0)); runSwing(() -> ps.setSelectedIndex(0));
assertEquals("LEFT", ps.getSelectedItem()); assertEquals("LEFT", ps.getSelectedItem());
@@ -513,10 +517,12 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor simpleOptionsPanel = ScrollableOptionsEditor simpleOptionsPanel =
(ScrollableOptionsEditor) getEditorPanel(buttonNode); (ScrollableOptionsEditor) getEditorPanel(buttonNode);
assertNotNull(simpleOptionsPanel); assertNotNull(simpleOptionsPanel);
assertTrue(simpleOptionsPanel.isShowing()); JComponent comp = simpleOptionsPanel.getComponent();
assertTrue(comp.isShowing());
PropertySelector ps = PropertySelector ps =
(PropertySelector) findPairedComponent(simpleOptionsPanel, "Mouse Button To Activate"); (PropertySelector) findPairedComponent(comp, "Mouse Button To Activate");
// change to "LEFT" // change to "LEFT"
runSwing(() -> ps.setSelectedIndex(0)); runSwing(() -> ps.setSelectedIndex(0));
@@ -545,11 +551,13 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor simpleOptionsPanel = ScrollableOptionsEditor simpleOptionsPanel =
(ScrollableOptionsEditor) getEditorPanel(buttonNode); (ScrollableOptionsEditor) getEditorPanel(buttonNode);
assertNotNull(simpleOptionsPanel); assertNotNull(simpleOptionsPanel);
assertTrue(simpleOptionsPanel.isShowing()); JComponent comp = simpleOptionsPanel.getComponent();
assertTrue(comp.isShowing());
PropertySelector ps = PropertySelector ps =
(PropertySelector) findPairedComponent(simpleOptionsPanel, "Mouse Button To Activate"); (PropertySelector) findPairedComponent(comp, "Mouse Button To Activate");
// change to "LEFT" // change to "LEFT"
runSwing(() -> ps.setSelectedIndex(0)); runSwing(() -> ps.setSelectedIndex(0));
@@ -592,15 +600,17 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor p = (ScrollableOptionsEditor) getEditorPanel(testNode); ScrollableOptionsEditor p = (ScrollableOptionsEditor) getEditorPanel(testNode);
assertNotNull(p); assertNotNull(p);
assertTrue(p.isShowing()); JComponent comp = p.getComponent();
JTextField field = (JTextField) findPairedComponent(p, "String Value 1"); assertTrue(comp.isShowing());
JTextField field = (JTextField) findPairedComponent(comp, "String Value 1");
assertNotNull(field); assertNotNull(field);
field = (JTextField) findPairedComponent(p, "String Value 2"); field = (JTextField) findPairedComponent(comp, "String Value 2");
assertNotNull(field); assertNotNull(field);
field = (JTextField) findPairedComponent(p, "String Value 3"); field = (JTextField) findPairedComponent(comp, "String Value 3");
assertNotNull(field); assertNotNull(field);
field = (JTextField) findPairedComponent(p, "Int Value"); field = (JTextField) findPairedComponent(comp, "Int Value");
assertNotNull(field); assertNotNull(field);
} }
@@ -614,9 +624,10 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor simpleOptionsPanel = ScrollableOptionsEditor simpleOptionsPanel =
(ScrollableOptionsEditor) getEditorPanel(toolNode); (ScrollableOptionsEditor) getEditorPanel(toolNode);
assertNotNull(simpleOptionsPanel); assertNotNull(simpleOptionsPanel);
assertTrue(simpleOptionsPanel.isShowing()); JComponent comp = simpleOptionsPanel.getComponent();
assertTrue(comp.isShowing());
Component component = findPairedComponent(simpleOptionsPanel, "Favorite Color"); Component component = findPairedComponent(comp, "Favorite Color");
assertNotNull(component); assertNotNull(component);
Rectangle rect = component.getBounds(); Rectangle rect = component.getBounds();
clickMouse(component, 1, rect.x, rect.y, 2, 0); clickMouse(component, 1, rect.x, rect.y, 2, 0);
@@ -659,9 +670,10 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor simpleOptionsPanel = ScrollableOptionsEditor simpleOptionsPanel =
(ScrollableOptionsEditor) getEditorPanel(toolNode); (ScrollableOptionsEditor) getEditorPanel(toolNode);
assertNotNull(simpleOptionsPanel); assertNotNull(simpleOptionsPanel);
assertTrue(simpleOptionsPanel.isShowing()); JComponent comp = simpleOptionsPanel.getComponent();
assertTrue(comp.isShowing());
Component canvas = findPairedComponent(simpleOptionsPanel, "Favorite Color"); Component canvas = findPairedComponent(comp, "Favorite Color");
assertNotNull(canvas); assertNotNull(canvas);
Rectangle rect = canvas.getBounds(); Rectangle rect = canvas.getBounds();
clickMouse(canvas, 1, rect.x, rect.y, 2, 0); clickMouse(canvas, 1, rect.x, rect.y, 2, 0);
@@ -846,7 +858,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
} }
private void pressBrowseButton(ScrollableOptionsEditor editor, String optionName) { private void pressBrowseButton(ScrollableOptionsEditor editor, String optionName) {
Component comp = findPairedComponent(editor, optionName); Component comp = findPairedComponent(editor.getComponent(), optionName);
assertNotNull(comp); assertNotNull(comp);
AbstractButton button = findAbstractButtonByName((Container) comp, "BrowseButton"); AbstractButton button = findAbstractButtonByName((Container) comp, "BrowseButton");
assertNotNull(button); assertNotNull(button);
@@ -856,7 +868,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
} }
private JTextField getEditorTextField(ScrollableOptionsEditor editor, String optionName) { private JTextField getEditorTextField(ScrollableOptionsEditor editor, String optionName) {
Component comp = findPairedComponent(editor, optionName); Component comp = findPairedComponent(editor.getComponent(), optionName);
assertNotNull(comp); assertNotNull(comp);
JTextField tf = findComponent((Container) comp, JTextField.class); JTextField tf = findComponent((Container) comp, JTextField.class);
@@ -877,7 +889,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(toolNode); ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(toolNode);
assertNotNull(editor); assertNotNull(editor);
assertTrue(editor.isShowing()); assertTrue(editor.getComponent().isShowing());
return editor; return editor;
} }
@@ -905,7 +917,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor editor = ScrollableOptionsEditor editor =
selectSubNodeWithDefaultEditor(parentNodeName, childNodeName); selectSubNodeWithDefaultEditor(parentNodeName, childNodeName);
JCheckBox checkBox = (JCheckBox) findPairedComponent(editor, optionName); JCheckBox checkBox = (JCheckBox) findPairedComponent(editor.getComponent(), optionName);
return checkBox.isSelected(); return checkBox.isSelected();
} }
@@ -914,7 +926,8 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor editor = ScrollableOptionsEditor editor =
selectSubNodeWithDefaultEditor(parentNodeName, childNodeName); selectSubNodeWithDefaultEditor(parentNodeName, childNodeName);
final JCheckBox checkBox = (JCheckBox) findPairedComponent(editor, optionName); final JCheckBox checkBox =
(JCheckBox) findPairedComponent(editor.getComponent(), optionName);
runSwing(() -> checkBox.setSelected(newValue)); runSwing(() -> checkBox.setSelected(newValue));
assertEquals(newValue, checkBox.isSelected()); assertEquals(newValue, checkBox.isSelected());
} }
@@ -923,7 +936,8 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
throws Exception { throws Exception {
ScrollableOptionsEditor editor = selectNodeWithDefaultEditor(parentNodeName); ScrollableOptionsEditor editor = selectNodeWithDefaultEditor(parentNodeName);
JTextField textField = (JTextField) findPairedComponent(editor, childNodeName); JTextField textField =
(JTextField) findPairedComponent(editor.getComponent(), childNodeName);
return getText(textField); return getText(textField);
} }
@@ -931,7 +945,8 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
String newValue) throws Exception { String newValue) throws Exception {
ScrollableOptionsEditor editor = selectNodeWithDefaultEditor(parentNodeName); ScrollableOptionsEditor editor = selectNodeWithDefaultEditor(parentNodeName);
JTextField textField = (JTextField) findPairedComponent(editor, childNodeName); JTextField textField =
(JTextField) findPairedComponent(editor.getComponent(), childNodeName);
setText(textField, newValue); setText(textField, newValue);
String updatedText = getText(textField); String updatedText = getText(textField);
@@ -952,7 +967,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(node); ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(node);
assertNotNull(editor); assertNotNull(editor);
assertTrue(editor.isShowing()); assertTrue(editor.getComponent().isShowing());
return editor; return editor;
} }
@@ -975,7 +990,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest {
ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(childNode); ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(childNode);
assertNotNull(editor); assertNotNull(editor);
assertTrue(editor.isShowing()); assertTrue(editor.getComponent().isShowing());
return editor; return editor;
} }
@@ -30,13 +30,21 @@ import ghidra.program.model.listing.Program;
import ghidra.program.model.pcode.*; import ghidra.program.model.pcode.*;
import ghidra.service.graph.*; import ghidra.service.graph.*;
import ghidra.util.Msg; import ghidra.util.Msg;
import java.util.*; import ghidra.util.WebColors;
import static ghidra.service.graph.GraphDisplay.*;
public class GraphAST extends GhidraScript { public class GraphAST extends GhidraScript {
protected static final String COLOR_ATTRIBUTE = "Color"; private static final String SHAPE_ATTRIBUTE = "Shape";
protected static final String ICON_ATTRIBUTE = "Icon";
protected static final String DEFAULT = "Default";
protected static final String CONSTANT = "Constant";
protected static final String REGISTER = "Register";
protected static final String UNIQUE = "Unique";
protected static final String PERSISTENT = "Persistent";
protected static final String ADDRESS_TIED = "Address Tied";
protected static final String OP = "Op";
protected static final String TYPE_OUTPUT = "Output";
protected static final String TYPE_INPUT = "Input";
private Function func; private Function func;
private AttributedGraph graph; private AttributedGraph graph;
protected HighFunction high; protected HighFunction high;
@@ -63,24 +71,48 @@ public class GraphAST extends GhidraScript {
buildAST(); buildAST();
graph = new AttributedGraph(); GraphType graphType = new GraphTypeBuilder("AST")
.vertexType(DEFAULT)
.vertexType(CONSTANT)
.vertexType(REGISTER)
.vertexType(UNIQUE)
.vertexType(PERSISTENT)
.vertexType(ADDRESS_TIED)
.vertexType(OP)
.edgeType(DEFAULT)
.edgeType(TYPE_OUTPUT)
.edgeType(TYPE_INPUT)
.build();
GraphDisplayOptions displayOptions = new GraphDisplayOptionsBuilder(graphType)
.vertexSelectionColor(WebColors.DEEP_PINK)
.edgeSelectionColor(WebColors.DEEP_PINK)
.defaultVertexColor(WebColors.RED)
.defaultEdgeColor(WebColors.NAVY)
.defaultVertexShape(VertexShape.ELLIPSE)
.defaultLayoutAlgorithm("Hierarchical MinCross Coffman Graham")
.useIcons(false)
.labelPosition(GraphLabelPosition.SOUTH)
.shapeOverrideAttribute(SHAPE_ATTRIBUTE)
.vertex(DEFAULT, VertexShape.ELLIPSE, WebColors.RED)
.vertex(CONSTANT, VertexShape.ELLIPSE, WebColors.DARK_GREEN)
.vertex(REGISTER, VertexShape.ELLIPSE, WebColors.NAVY)
.vertex(UNIQUE, VertexShape.ELLIPSE, WebColors.BLACK)
.vertex(PERSISTENT, VertexShape.ELLIPSE, WebColors.DARK_ORANGE)
.vertex(ADDRESS_TIED, VertexShape.ELLIPSE, WebColors.ORANGE)
.vertex(OP, VertexShape.RECTANGLE, WebColors.RED)
.edge(DEFAULT, WebColors.BLUE)
.edge(TYPE_OUTPUT, WebColors.BLACK)
.edge(TYPE_INPUT, WebColors.RED)
.build();
graph = new AttributedGraph("AST Graph", graphType);
buildGraph(); buildGraph();
Map<String, String> properties = new HashMap<>(); GraphDisplay graphDisplay = graphDisplayBroker.getDefaultGraphDisplay(false, monitor);
properties.put(SELECTED_VERTEX_COLOR, "0xFF1493");
properties.put(SELECTED_EDGE_COLOR, "0xFF1493");
properties.put(INITIAL_LAYOUT_ALGORITHM, "Hierarchical MinCross Coffman Graham");
properties.put(DISPLAY_VERTICES_AS_ICONS, "false");
properties.put(VERTEX_LABEL_POSITION, "S");
properties.put(ENABLE_EDGE_SELECTION, "true");
GraphDisplay graphDisplay =
graphDisplayBroker.getDefaultGraphDisplay(false, properties, monitor);
// graphDisplay.defineVertexAttribute(CODE_ATTRIBUTE); //
// graphDisplay.defineVertexAttribute(SYMBOLS_ATTRIBUTE);
// graphDisplay.defineEdgeAttribute(EDGE_TYPE_ATTRIBUTE);
String description = "AST Data Flow Graph For " + func.getName();
graphDisplay.setGraph(graph, description, false, monitor); String description = "AST Data Flow Graph For " + func.getName();
graphDisplay.setGraph(graph, displayOptions, description, false, monitor);
// Install a handler so the selection/location will map // Install a handler so the selection/location will map
graphDisplay.setGraphDisplayListener( graphDisplay.setGraphDisplayListener(
@@ -125,34 +157,33 @@ public class GraphAST extends GhidraScript {
protected AttributedVertex createVarnodeVertex(VarnodeAST vn) { protected AttributedVertex createVarnodeVertex(VarnodeAST vn) {
String name = vn.getAddress().toString(true); String name = vn.getAddress().toString(true);
String id = getVarnodeKey(vn); String id = getVarnodeKey(vn);
String colorattrib = "Red"; String vertexType = DEFAULT;
if (vn.isConstant()) { if (vn.isConstant()) {
colorattrib = "DarkGreen"; vertexType = CONSTANT;
} }
else if (vn.isRegister()) { else if (vn.isRegister()) {
colorattrib = "Blue"; vertexType = REGISTER;
Register reg = func.getProgram().getRegister(vn.getAddress(), vn.getSize()); Register reg = func.getProgram().getRegister(vn.getAddress(), vn.getSize());
if (reg != null) { if (reg != null) {
name = reg.getName(); name = reg.getName();
} }
} }
else if (vn.isUnique()) { else if (vn.isUnique()) {
colorattrib = "Black"; vertexType = UNIQUE;
} }
else if (vn.isPersistent()) { else if (vn.isPersistent()) {
colorattrib = "DarkOrange"; vertexType = PERSISTENT;
} }
else if (vn.isAddrTied()) { else if (vn.isAddrTied()) {
colorattrib = "Orange"; vertexType = ADDRESS_TIED;
} }
AttributedVertex vert = graph.addVertex(id, name); AttributedVertex vert = graph.addVertex(id, name);
vert.setVertexType(vertexType);
// if it is an input override the shape to be a triangle
if (vn.isInput()) { if (vn.isInput()) {
vert.setAttribute(ICON_ATTRIBUTE, "TriangleDown"); vert.setAttribute(SHAPE_ATTRIBUTE, VertexShape.TRIANGLE_DOWN.getName());
} }
else {
vert.setAttribute(ICON_ATTRIBUTE, "Circle");
}
vert.setAttribute(COLOR_ATTRIBUTE, colorattrib);
return vert; return vert;
} }
@@ -176,7 +207,7 @@ public class GraphAST extends GhidraScript {
} }
} }
AttributedVertex vert = graph.addVertex(id, name); AttributedVertex vert = graph.addVertex(id, name);
vert.setAttribute(ICON_ATTRIBUTE, "Square"); vert.setVertexType(OP);
return vert; return vert;
} }
@@ -192,7 +223,9 @@ public class GraphAST extends GhidraScript {
} }
protected AttributedEdge createEdge(AttributedVertex in, AttributedVertex out) { protected AttributedEdge createEdge(AttributedVertex in, AttributedVertex out) {
return graph.addEdge(in, out); AttributedEdge newEdge = graph.addEdge(in, out);
newEdge.setEdgeType(DEFAULT);
return newEdge;
} }
protected void buildGraph() { protected void buildGraph() {
@@ -77,7 +77,7 @@ public class GraphASTAndFlow extends GraphAST {
} }
if (prev != null && map.containsKey(prev) && map.containsKey(next)) { if (prev != null && map.containsKey(prev) && map.containsKey(next)) {
AttributedEdge edge = createEdge(map.get(prev), map.get(next)); AttributedEdge edge = createEdge(map.get(prev), map.get(next));
edge.setAttribute(COLOR_ATTRIBUTE, "Black"); edge.setEdgeType(TYPE_OUTPUT);
} }
prev = next; prev = next;
} }
@@ -92,7 +92,7 @@ public class GraphASTAndFlow extends GraphAST {
PcodeBlock in = block.getIn(i); PcodeBlock in = block.getIn(i);
if (last.containsKey(in)) { if (last.containsKey(in)) {
AttributedEdge edge = createEdge(last.get(in), first.get(block)); AttributedEdge edge = createEdge(last.get(in), first.get(block));
edge.setAttribute(COLOR_ATTRIBUTE, "Red"); edge.setEdgeType(TYPE_INPUT);
} }
} }
} }
@@ -334,7 +334,7 @@ public class RecoverClassesFromRTTIScript extends GhidraScript {
private AttributedGraph createGraph(List<RecoveredClass> recoveredClasses) private AttributedGraph createGraph(List<RecoveredClass> recoveredClasses)
throws CancelledException { throws CancelledException {
AttributedGraph g = new AttributedGraph(); AttributedGraph g = new AttributedGraph("Test Graph", new EmptyGraphType());
Iterator<RecoveredClass> recoveredClassIterator = recoveredClasses.iterator(); Iterator<RecoveredClass> recoveredClassIterator = recoveredClasses.iterator();
while (recoveredClassIterator.hasNext()) { while (recoveredClassIterator.hasNext()) {
@@ -15,11 +15,11 @@
*/ */
package ghidra.app.plugin.core.decompile.actions; package ghidra.app.plugin.core.decompile.actions;
import static ghidra.app.plugin.core.decompile.actions.ASTGraphTask.GraphType.*; import static ghidra.app.plugin.core.decompile.actions.ASTGraphTask.AstGraphSubType.*;
import java.util.*; import java.util.*;
import ghidra.app.plugin.core.decompile.actions.ASTGraphTask.GraphType; import ghidra.app.plugin.core.decompile.actions.ASTGraphTask.AstGraphSubType;
import ghidra.app.plugin.core.graph.AddressBasedGraphDisplayListener; import ghidra.app.plugin.core.graph.AddressBasedGraphDisplayListener;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
@@ -33,10 +33,10 @@ import ghidra.util.exception.AssertException;
*/ */
public class ASTGraphDisplayListener extends AddressBasedGraphDisplayListener { public class ASTGraphDisplayListener extends AddressBasedGraphDisplayListener {
private HighFunction hfunction; private HighFunction hfunction;
private GraphType graphType; private AstGraphSubType graphType;
ASTGraphDisplayListener(PluginTool tool, GraphDisplay display, HighFunction hfunction, ASTGraphDisplayListener(PluginTool tool, GraphDisplay display, HighFunction hfunction,
GraphType graphType) { AstGraphSubType graphType) {
super(tool, hfunction.getFunction().getProgram(), display); super(tool, hfunction.getFunction().getProgram(), display);
this.hfunction = hfunction; this.hfunction = hfunction;
this.graphType = graphType; this.graphType = graphType;
@@ -15,13 +15,15 @@
*/ */
package ghidra.app.plugin.core.decompile.actions; package ghidra.app.plugin.core.decompile.actions;
import java.util.HashMap; import static ghidra.graph.ProgramGraphType.*;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map;
import docking.widgets.EventTrigger; import docking.widgets.EventTrigger;
import ghidra.app.services.GraphDisplayBroker; import ghidra.app.services.GraphDisplayBroker;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.graph.ProgramGraphDisplayOptions;
import ghidra.graph.ProgramGraphType;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.lang.Register; import ghidra.program.model.lang.Register;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@@ -34,14 +36,12 @@ import ghidra.util.exception.GraphException;
import ghidra.util.task.Task; import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
import static ghidra.service.graph.GraphDisplay.*;
public class ASTGraphTask extends Task { public class ASTGraphTask extends Task {
enum GraphType { enum AstGraphSubType {
CONTROL_FLOW_GRAPH("AST Control Flow"), DATA_FLOW_GRAPH("AST Data Flow"); CONTROL_FLOW_GRAPH("AST Control Flow"), DATA_FLOW_GRAPH("AST Data Flow");
private String name; private String name;
GraphType(String name) { AstGraphSubType(String name) {
this.name = name; this.name = name;
} }
@@ -51,35 +51,19 @@ public class ASTGraphTask extends Task {
} }
private static final String CODE_ATTRIBUTE = "Code"; private static final String CODE_ATTRIBUTE = "Code";
private static final String SYMBOLS_ATTRIBUTE = "Symbols";
private static final String VERTEX_TYPE_ATTRIBUTE = "VertexType";
// Vertex Types
private final static String ENTRY_NODE = "Entry";
// "1"; // beginning of a block, someone calls it
private final static String BODY_NODE = "Body";
// "2"; // Body block, no flow
private final static String EXIT_NODE = "Exit";
// "3"; // Terminator
private final static String SWITCH_NODE = "Switch";
// "4"; // Switch/computed jump
private final static String BAD_NODE = "Bad";
// "5"; // Bad destination
private final static String DATA_NODE = "Data";
// "6"; // Data Node, used for indirection
private GraphDisplayBroker graphService; private GraphDisplayBroker graphService;
private boolean newGraph; private boolean newGraph;
private int codeLimitPerBlock; private int codeLimitPerBlock;
private Address location; private Address location;
private HighFunction hfunction; private HighFunction hfunction;
private GraphType graphType; private AstGraphSubType astGraphType;
private int uniqueNum = 0; private int uniqueNum = 0;
private PluginTool tool; private PluginTool tool;
public ASTGraphTask(GraphDisplayBroker graphService, boolean newGraph, int codeLimitPerBlock, public ASTGraphTask(GraphDisplayBroker graphService, boolean newGraph, int codeLimitPerBlock,
Address location, HighFunction hfunction, GraphType graphType, PluginTool tool) { Address location, HighFunction hfunction, AstGraphSubType graphType, PluginTool tool) {
super("Graph " + graphType.getName(), true, false, true); super("Graph " + graphType.getName(), true, false, true);
this.graphService = graphService; this.graphService = graphService;
@@ -87,32 +71,30 @@ public class ASTGraphTask extends Task {
this.codeLimitPerBlock = codeLimitPerBlock; this.codeLimitPerBlock = codeLimitPerBlock;
this.location = location; this.location = location;
this.hfunction = hfunction; this.hfunction = hfunction;
this.graphType = graphType; this.astGraphType = graphType;
this.tool = tool; this.tool = tool;
} }
@Override @Override
public void run(TaskMonitor monitor) { public void run(TaskMonitor monitor) {
GraphType graphType = new AstGraphType();
// get a new graph // get a new graph
AttributedGraph graph = new AttributedGraph(); AttributedGraph graph = new AttributedGraph(astGraphType.getName(), graphType);
try { try {
monitor.setMessage("Computing Graph..."); monitor.setMessage("Computing Graph...");
if (graphType == GraphType.DATA_FLOW_GRAPH) { if (astGraphType == AstGraphSubType.DATA_FLOW_GRAPH) {
createDataFlowGraph(graph, monitor); createDataFlowGraph(graph, monitor);
} }
else { else {
createControlFlowGraph(graph, monitor); createControlFlowGraph(graph, monitor);
} }
Map<String, String> properties = new HashMap<>();
properties.put(SELECTED_VERTEX_COLOR, "0xFF1493"); GraphDisplay display =
properties.put(SELECTED_EDGE_COLOR, "0xFF1493"); graphService.getDefaultGraphDisplay(!newGraph, monitor);
properties.put(INITIAL_LAYOUT_ALGORITHM, "Hierarchical MinCross Coffman Graham");
properties.put(ENABLE_EDGE_SELECTION, "true");
GraphDisplay display = graphService.getDefaultGraphDisplay(!newGraph, properties, monitor);
ASTGraphDisplayListener displayListener = ASTGraphDisplayListener displayListener =
new ASTGraphDisplayListener(tool, display, hfunction, graphType); new ASTGraphDisplayListener(tool, display, hfunction, astGraphType);
display.setGraphDisplayListener(displayListener); display.setGraphDisplayListener(displayListener);
monitor.setMessage("Obtaining handle to graph provider..."); monitor.setMessage("Obtaining handle to graph provider...");
@@ -122,20 +104,19 @@ public class ASTGraphTask extends Task {
monitor.setCancelEnabled(false); monitor.setCancelEnabled(false);
monitor.setMessage("Rendering Graph..."); monitor.setMessage("Rendering Graph...");
display.defineVertexAttribute(CODE_ATTRIBUTE);
display.defineVertexAttribute(SYMBOLS_ATTRIBUTE);
display.setVertexLabelAttribute(CODE_ATTRIBUTE, GraphDisplay.ALIGN_LEFT, 12, true,
graphType == GraphType.CONTROL_FLOW_GRAPH ? (codeLimitPerBlock + 1) : 1);
String description = String description =
graphType == GraphType.DATA_FLOW_GRAPH ? "AST Data Flow" : "AST Control Flow"; astGraphType == AstGraphSubType.DATA_FLOW_GRAPH ? "AST Data Flow" : "AST Control Flow";
description = description + " for " + hfunction.getFunction().getName(); description = description + " for " + hfunction.getFunction().getName();
display.setGraph(graph, description, false, monitor); GraphDisplayOptions graphDisplayOptions =
new ProgramGraphDisplayOptions(new AstGraphType(), tool);
graphDisplayOptions.setVertexLabelOverrideAttributeKey(CODE_ATTRIBUTE);
display.setGraph(graph, graphDisplayOptions, description, false, monitor);
setGraphLocation(display, displayListener); setGraphLocation(display, displayListener);
} }
catch (GraphException e) { catch (GraphException e) {
Msg.showError(this, null, "Graph Error", e.getMessage()); Msg.showError(this, null, "Graph Error",
"Can't create graph display: " + e.getMessage());
} }
catch (CancelledException e1) { catch (CancelledException e1) {
return; return;
@@ -230,20 +211,20 @@ public class ASTGraphTask extends Task {
vertex.setAttribute(CODE_ATTRIBUTE, formatOpMnemonic(op)); vertex.setAttribute(CODE_ATTRIBUTE, formatOpMnemonic(op));
String vertexType = BODY_NODE; String vertexType = BODY;
switch (op.getOpcode()) { switch (op.getOpcode()) {
case PcodeOp.BRANCH: case PcodeOp.BRANCH:
case PcodeOp.BRANCHIND: case PcodeOp.BRANCHIND:
case PcodeOp.CBRANCH: case PcodeOp.CBRANCH:
case PcodeOp.CALL: case PcodeOp.CALL:
case PcodeOp.CALLIND: case PcodeOp.CALLIND:
vertexType = SWITCH_NODE; vertexType = SWITCH;
break; break;
case PcodeOp.RETURN: case PcodeOp.RETURN:
vertexType = EXIT_NODE; vertexType = EXIT;
break; break;
} }
vertex.setAttribute(VERTEX_TYPE_ATTRIBUTE, vertexType); vertex.setVertexType(vertexType);
} }
private AttributedVertex getDataVertex(AttributedGraph graph, Varnode node, private AttributedVertex getDataVertex(AttributedGraph graph, Varnode node,
@@ -278,7 +259,7 @@ public class ASTGraphTask extends Task {
} }
label += translateVarnode(node, false); label += translateVarnode(node, false);
vertex.setAttribute(CODE_ATTRIBUTE, label); vertex.setAttribute(CODE_ATTRIBUTE, label);
vertex.setAttribute(VERTEX_TYPE_ATTRIBUTE, DATA_NODE); vertex.setVertexType(ProgramGraphType.DATA);
} }
protected void createControlFlowGraph(AttributedGraph graph, TaskMonitor monitor) protected void createControlFlowGraph(AttributedGraph graph, TaskMonitor monitor)
@@ -322,7 +303,7 @@ public class ASTGraphTask extends Task {
} }
else { else {
vertex.setAttribute(CODE_ATTRIBUTE, "<???>"); vertex.setAttribute(CODE_ATTRIBUTE, "<???>");
vertex.setAttribute(VERTEX_TYPE_ATTRIBUTE, BAD_NODE); vertex.setVertexType(BAD);
} }
} }
return vertex; return vertex;
@@ -348,23 +329,23 @@ public class ASTGraphTask extends Task {
vertex.setAttribute(CODE_ATTRIBUTE, buf.toString()); vertex.setAttribute(CODE_ATTRIBUTE, buf.toString());
// Establish vertex type // Establish vertex type
String vertexType = BODY_NODE; String vertexType = BODY;
if (basicBlk.getInSize() == 0) { if (basicBlk.getInSize() == 0) {
vertexType = ENTRY_NODE; vertexType = ENTRY;
} }
else { else {
switch (basicBlk.getOutSize()) { switch (basicBlk.getOutSize()) {
case 0: case 0:
vertexType = EXIT_NODE; vertexType = EXIT;
break; break;
case 1: case 1:
vertexType = BODY_NODE; vertexType = BODY;
break; break;
default: default:
vertexType = SWITCH_NODE; vertexType = SWITCH;
} }
} }
vertex.setAttribute(VERTEX_TYPE_ATTRIBUTE, vertexType); vertex.setVertexType(vertexType);
} }
private String formatOpMnemonic(PcodeOp op) { private String formatOpMnemonic(PcodeOp op) {
@@ -0,0 +1,26 @@
/* ###
* 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.decompile.actions;
import ghidra.graph.ProgramGraphType;
public class AstGraphType extends ProgramGraphType {
protected AstGraphType() {
super("AST", "Graph to show pcode for function");
}
}
@@ -15,7 +15,7 @@
*/ */
package ghidra.app.plugin.core.decompile.actions; package ghidra.app.plugin.core.decompile.actions;
import static ghidra.app.plugin.core.decompile.actions.ASTGraphTask.GraphType.*; import static ghidra.app.plugin.core.decompile.actions.ASTGraphTask.AstGraphSubType.*;
import docking.action.MenuData; import docking.action.MenuData;
import ghidra.app.plugin.core.decompile.DecompilerActionContext; import ghidra.app.plugin.core.decompile.DecompilerActionContext;
@@ -248,6 +248,41 @@
</UL> </UL>
</BLOCKQUOTE> </BLOCKQUOTE>
<H2><A name="Graph_Type_Display_Options">Graph Type Display Options</H2>
<BLOCKQUOTE>
<P>Graphs have a graph type which defines vertex types and edge types. Users can
configure the display properties for each vertex and edge type. These options have the
following subsections:</P>
<H3>Edge Colors</H3>
<BLOCKQUOTE>
<P>Allows setting the color for each edge type. Each Edge type will be listed with its
current color.</P>
</BLOCKQUOTE>
<H3>Miscellaneous</H3>
<UL>
<LI>Default Vertex Color - color for vertices with no defined vertex type</LI>
<LI>Default Vertex Shape - shape for vertices with no defined vertex type</LI>
<LI>Default Edge Color - color for edges with no defined edge type</LI>
<LI>Favored Edge - edge type to be favored by graph layout algorithms</LI>
</UL>
<H3>Vertex Colors</H3>
<BLOCKQUOTE>
<P>Allows setting the color for each vertex type. Each vertex type will be listed with
its current color.</P>
</BLOCKQUOTE>
<H3>Vertex Shapes</H3>
<BLOCKQUOTE>
<P>Allows setting the shape for each vertex type. Each vertex type will be listed with a
combo box for picking a supported shape. Supported shapes include Ellipse, Rectangle
Diamond, TriangleUp, TriangleDown, Star, Pentagon, Hexagon, and Octagon.</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<P class="providedbyplugin">Provided By:&nbsp; <I>GraphDisplayBrokerPlugin</I></P> <P class="providedbyplugin">Provided By:&nbsp; <I>GraphDisplayBrokerPlugin</I></P>
<P class="relatedtopic">Related Topics:</P> <P class="relatedtopic">Related Topics:</P>
@@ -155,7 +155,7 @@ public class AttributeFilters implements ItemSelectable {
// count up the unique attribute values (skipping the 'precluded names' we know we don't want) // count up the unique attribute values (skipping the 'precluded names' we know we don't want)
for (Attributed element : elements) { for (Attributed element : elements) {
Map<String, String> attributeMap = new HashMap<>(element.getAttributeMap()); Map<String, String> attributeMap = new HashMap<>(element.getAttributes());
for (Map.Entry<String, String> entry : attributeMap.entrySet()) { for (Map.Entry<String, String> entry : attributeMap.entrySet()) {
if (!precludedNames.contains(entry.getKey())) { if (!precludedNames.contains(entry.getKey())) {
multiset.add(entry.getValue()); multiset.add(entry.getValue());
@@ -24,6 +24,7 @@ import docking.widgets.EventTrigger;
import ghidra.app.services.GraphDisplayBroker; import ghidra.app.services.GraphDisplayBroker;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.service.graph.*; import ghidra.service.graph.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor; import ghidra.util.task.TaskMonitor;
/** /**
@@ -77,23 +78,6 @@ class ExportAttributedGraphDisplay implements GraphDisplay {
return Collections.emptyList(); return Collections.emptyList();
} }
@Override
public void defineVertexAttribute(String attributeName) {
// no effect
}
@Override
public void defineEdgeAttribute(String attributeName) {
// no effect
}
@Override
public void setVertexLabelAttribute(String attributeName, int alignment, int size,
boolean monospace,
int maxLines) {
// no effect
}
@Override @Override
public void setGraph(AttributedGraph graph, String title, boolean append, public void setGraph(AttributedGraph graph, String title, boolean append,
TaskMonitor monitor) { TaskMonitor monitor) {
@@ -102,6 +86,12 @@ class ExportAttributedGraphDisplay implements GraphDisplay {
doSetGraphData(graph); doSetGraphData(graph);
} }
@Override
public void setGraph(AttributedGraph graph, GraphDisplayOptions options, String title,
boolean append, TaskMonitor monitor) throws CancelledException {
this.setGraph(graph, title, append, monitor);
}
/** /**
* remove all vertices and edges from the {@link Graph} * remove all vertices and edges from the {@link Graph}
*/ */
@@ -149,5 +139,4 @@ class ExportAttributedGraphDisplay implements GraphDisplay {
public void selectVertices(Set<AttributedVertex> vertexList, EventTrigger eventTrigger) { public void selectVertices(Set<AttributedVertex> vertexList, EventTrigger eventTrigger) {
// not interactive, so N/A // not interactive, so N/A
} }
} }
@@ -50,13 +50,13 @@ public class GraphMlGraphExporter extends AbstractAttributedGraphExporter {
entry -> new DefaultAttribute<>(entry.getValue(), AttributeType.STRING)))); entry -> new DefaultAttribute<>(entry.getValue(), AttributeType.STRING))));
graph.vertexSet().stream() graph.vertexSet().stream()
.map(Attributed::getAttributeMap) .map(Attributed::getAttributes)
.flatMap(m -> m.entrySet().stream()) .flatMap(m -> m.entrySet().stream())
.map(Map.Entry::getKey) .map(Map.Entry::getKey)
.forEach(key -> exporter.registerAttribute(key, GraphMLExporter.AttributeCategory.NODE, AttributeType.STRING)); .forEach(key -> exporter.registerAttribute(key, GraphMLExporter.AttributeCategory.NODE, AttributeType.STRING));
graph.edgeSet().stream() graph.edgeSet().stream()
.map(Attributed::getAttributeMap) .map(Attributed::getAttributes)
.flatMap(m -> m.entrySet().stream()) .flatMap(m -> m.entrySet().stream())
.map(Map.Entry::getKey) .map(Map.Entry::getKey)
.forEach(key -> exporter.registerAttribute(key, GraphMLExporter.AttributeCategory.EDGE, AttributeType.STRING)); .forEach(key -> exporter.registerAttribute(key, GraphMLExporter.AttributeCategory.EDGE, AttributeType.STRING));
@@ -0,0 +1,137 @@
/* ###
* 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.graph.visualization;
import java.awt.event.MouseEvent;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.swing.JComponent;
import javax.swing.JToolTip;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import com.google.common.base.Splitter;
import ghidra.graph.viewer.popup.ToolTipInfo;
import ghidra.service.graph.*;
/**
* Generates tool tips for an {@link AttributedVertex} or {@link AttributedEdge} in
* an {@link AttributedGraph}
*/
public class AttributedToolTipInfo extends ToolTipInfo<Attributed> {
AttributedToolTipInfo(Attributed graphObject, MouseEvent event) {
super(event, graphObject);
}
@Override
public JComponent createToolTipComponent() {
if (graphObject == null) {
return null;
}
String toolTip = getToolTipText();
if (StringUtils.isBlank(toolTip)) {
return null;
}
JToolTip jToolTip = new JToolTip();
jToolTip.setTipText(toolTip);
return jToolTip;
}
@Override
protected void emphasize() {
// this graph display does not have a notion of emphasizing
}
@Override
protected void deEmphasize() {
// this graph display does not have a notion of emphasizing
}
/**
* Returns the tool tip for the graphObject this object manages
* @return the tool tip for the graphObject this object manages
*/
public String getToolTipText() {
String tooltipText = graphObject.getDescription();
if (tooltipText != null) {
return tooltipText;
}
StringBuilder buf = new StringBuilder();
buf.append("<HTML>");
if (graphObject instanceof AttributedVertex) {
addToolTipTextForVertex(buf, (AttributedVertex) graphObject);
}
else if (graphObject instanceof AttributedEdge) {
addToolTipTextForEdge(buf, (AttributedEdge) graphObject);
}
return buf.toString();
}
private void addToolTipTextForVertex(StringBuilder buf, AttributedVertex vertex) {
String vertexType = vertex.getVertexType();
buf.append("<H4>");
buf.append(vertex.getName());
if (vertexType != null) {
buf.append("<br>");
buf.append("Type: &nbsp;" + vertexType);
}
buf.append("</H4>");
addAttributes(buf, AttributedVertex.NAME_KEY, AttributedVertex.VERTEX_TYPE_KEY);
}
private void addToolTipTextForEdge(StringBuilder buf, AttributedEdge edge) {
String edgeType = edge.getEdgeType();
if (edgeType != null) {
buf.append("<H4>");
buf.append("Type: &nbsp;" + edgeType);
buf.append("</H4>");
}
addAttributes(buf, AttributedEdge.EDGE_TYPE_KEY);
}
private void addAttributes(StringBuilder buf, String...excludedKeys) {
Set<Entry<String, String>> entries = graphObject.entrySet();
for (Map.Entry<String, String> entry : entries) {
String key = entry.getKey();
if (ArrayUtils.contains(excludedKeys, key)) {
continue; // skip keys handled in header
}
buf.append(key);
buf.append(": ");
String value = entry.getValue();
value = StringEscapeUtils.escapeHtml4(value);
String split = String.join("<br>", Splitter.on('\n').split(value));
split = split.replaceAll("\\s", "&nbsp;");
buf.append(split);
buf.append("<br>");
}
}
}
@@ -1,288 +0,0 @@
/* ###
* 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.graph.visualization;
import static java.util.Map.*;
import java.awt.Color;
import java.awt.Paint;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ghidra.service.graph.Attributed;
/**
* support for coercing colors from attributes or color names
*/
public abstract class Colors {
private static final Pattern HEX_PATTERN = Pattern.compile("(0x|#)[0-9A-Fa-f]{6}");
// cannot instantiate nor extend
private Colors() {
}
/**
* a map of well-known 'web' color names to colors
*/
static Map<String, Color> WEB_COLOR_MAP = Map.ofEntries(
entry("Black", Color.decode("0x000000")),
entry("Navy", Color.decode("0x000080")),
entry("DarkBlue", Color.decode("0x00008B")),
entry("MediumBlue", Color.decode("0x0000CD")),
entry("Blue", Color.decode("0x0000FF")),
entry("DarkGreen", Color.decode("0x006400")),
entry("Green", Color.decode("0x008000")),
entry("Teal", Color.decode("0x008080")),
entry("DarkCyan", Color.decode("0x008B8B")),
entry("DeepSkyBlue", Color.decode("0x00BFFF")),
entry("DarkTurquoise", Color.decode("0x00CED1")),
entry("MediumSpringGreen", Color.decode("0x00FA9A")),
entry("Lime", Color.decode("0x00FF00")),
entry("SpringGreen", Color.decode("0x00FF7F")),
entry("Aqua", Color.decode("0x00FFFF")),
entry("Cyan", Color.decode("0x00FFFF")),
entry("MidnightBlue", Color.decode("0x191970")),
entry("DodgerBlue", Color.decode("0x1E90FF")),
entry("LightSeaGreen", Color.decode("0x20B2AA")),
entry("ForestGreen", Color.decode("0x228B22")),
entry("SeaGreen", Color.decode("0x2E8B57")),
entry("DarkSlateGray", Color.decode("0x2F4F4F")),
entry("DarkSlateGrey", Color.decode("0x2F4F4F")),
entry("LimeGreen", Color.decode("0x32CD32")),
entry("MediumSeaGreen", Color.decode("0x3CB371")),
entry("Turquoise", Color.decode("0x40E0D0")),
entry("RoyalBlue", Color.decode("0x4169E1")),
entry("SteelBlue", Color.decode("0x4682B4")),
entry("DarkSlateBlue", Color.decode("0x483D8B")),
entry("MediumTurquoise", Color.decode("0x48D1CC")),
entry("Indigo", Color.decode("0x4B0082")),
entry("DarkOliveGreen", Color.decode("0x556B2F")),
entry("CadetBlue", Color.decode("0x5F9EA0")),
entry("CornflowerBlue", Color.decode("0x6495ED")),
entry("RebeccaPurple", Color.decode("0x663399")),
entry("MediumAquaMarine", Color.decode("0x66CDAA")),
entry("DimGray", Color.decode("0x696969")),
entry("DimGrey", Color.decode("0x696969")),
entry("SlateBlue", Color.decode("0x6A5ACD")),
entry("OliveDrab", Color.decode("0x6B8E23")),
entry("SlateGray", Color.decode("0x708090")),
entry("SlateGrey", Color.decode("0x708090")),
entry("LightSlateGray", Color.decode("0x778899")),
entry("LightSlateGrey", Color.decode("0x778899")),
entry("MediumSlateBlue", Color.decode("0x7B68EE")),
entry("LawnGreen", Color.decode("0x7CFC00")),
entry("Chartreuse", Color.decode("0x7FFF00")),
entry("Aquamarine", Color.decode("0x7FFFD4")),
entry("Maroon", Color.decode("0x800000")),
entry("Purple", Color.decode("0x800080")),
entry("Olive", Color.decode("0x808000")),
entry("Gray", Color.decode("0x808080")),
entry("Grey", Color.decode("0x808080")),
entry("SkyBlue", Color.decode("0x87CEEB")),
entry("LightSkyBlue", Color.decode("0x87CEFA")),
entry("BlueViolet", Color.decode("0x8A2BE2")),
entry("DarkRed", Color.decode("0x8B0000")),
entry("DarkMagenta", Color.decode("0x8B008B")),
entry("SaddleBrown", Color.decode("0x8B4513")),
entry("DarkSeaGreen", Color.decode("0x8FBC8F")),
entry("LightGreen", Color.decode("0x90EE90")),
entry("MediumPurple", Color.decode("0x9370DB")),
entry("DarkViolet", Color.decode("0x9400D3")),
entry("PaleGreen", Color.decode("0x98FB98")),
entry("DarkOrchid", Color.decode("0x9932CC")),
entry("YellowGreen", Color.decode("0x9ACD32")),
entry("Sienna", Color.decode("0xA0522D")),
entry("Brown", Color.decode("0xA52A2A")),
entry("DarkGray", Color.decode("0xA9A9A9")),
entry("DarkGrey", Color.decode("0xA9A9A9")),
entry("LightBlue", Color.decode("0xADD8E6")),
entry("GreenYellow", Color.decode("0xADFF2F")),
entry("PaleTurquoise", Color.decode("0xAFEEEE")),
entry("LightSteelBlue", Color.decode("0xB0C4DE")),
entry("PowderBlue", Color.decode("0xB0E0E6")),
entry("FireBrick", Color.decode("0xB22222")),
entry("DarkGoldenRod", Color.decode("0xB8860B")),
entry("MediumOrchid", Color.decode("0xBA55D3")),
entry("RosyBrown", Color.decode("0xBC8F8F")),
entry("DarkKhaki", Color.decode("0xBDB76B")),
entry("Silver", Color.decode("0xC0C0C0")),
entry("MediumVioletRed", Color.decode("0xC71585")),
entry("IndianRed", Color.decode("0xCD5C5C")),
entry("Peru", Color.decode("0xCD853F")),
entry("Chocolate", Color.decode("0xD2691E")),
entry("Tan", Color.decode("0xD2B48C")),
entry("LightGray", Color.decode("0xD3D3D3")),
entry("LightGrey", Color.decode("0xD3D3D3")),
entry("Thistle", Color.decode("0xD8BFD8")),
entry("Orchid", Color.decode("0xDA70D6")),
entry("GoldenRod", Color.decode("0xDAA520")),
entry("PaleVioletRed", Color.decode("0xDB7093")),
entry("Crimson", Color.decode("0xDC143C")),
entry("Gainsboro", Color.decode("0xDCDCDC")),
entry("Plum", Color.decode("0xDDA0DD")),
entry("BurlyWood", Color.decode("0xDEB887")),
entry("LightCyan", Color.decode("0xE0FFFF")),
entry("Lavender", Color.decode("0xE6E6FA")),
entry("DarkSalmon", Color.decode("0xE9967A")),
entry("Violet", Color.decode("0xEE82EE")),
entry("PaleGoldenRod", Color.decode("0xEEE8AA")),
entry("LightCoral", Color.decode("0xF08080")),
entry("Khaki", Color.decode("0xF0E68C")),
entry("AliceBlue", Color.decode("0xF0F8FF")),
entry("HoneyDew", Color.decode("0xF0FFF0")),
entry("Azure", Color.decode("0xF0FFFF")),
entry("SandyBrown", Color.decode("0xF4A460")),
entry("Wheat", Color.decode("0xF5DEB3")),
entry("Beige", Color.decode("0xF5F5DC")),
entry("WhiteSmoke", Color.decode("0xF5F5F5")),
entry("MintCream", Color.decode("0xF5FFFA")),
entry("GhostWhite", Color.decode("0xF8F8FF")),
entry("Salmon", Color.decode("0xFA8072")),
entry("AntiqueWhite", Color.decode("0xFAEBD7")),
entry("Linen", Color.decode("0xFAF0E6")),
entry("LightGoldenRodYellow", Color.decode("0xFAFAD2")),
entry("OldLace", Color.decode("0xFDF5E6")),
entry("Red", Color.decode("0xFF0000")),
entry("Fuchsia", Color.decode("0xFF00FF")),
entry("Magenta", Color.decode("0xFF00FF")),
entry("DeepPink", Color.decode("0xFF1493")),
entry("OrangeRed", Color.decode("0xFF4500")),
entry("Tomato", Color.decode("0xFF6347")),
entry("HotPink", Color.decode("0xFF69B4")),
entry("Coral", Color.decode("0xFF7F50")),
entry("DarkOrange", Color.decode("0xFF8C00")),
entry("LightSalmon", Color.decode("0xFFA07A")),
entry("Orange", Color.decode("0xFFA500")),
entry("LightPink", Color.decode("0xFFB6C1")),
entry("Pink", Color.decode("0xFFC0CB")),
entry("Gold", Color.decode("0xFFD700")),
entry("PeachPuff", Color.decode("0xFFDAB9")),
entry("NavajoWhite", Color.decode("0xFFDEAD")),
entry("Moccasin", Color.decode("0xFFE4B5")),
entry("Bisque", Color.decode("0xFFE4C4")),
entry("MistyRose", Color.decode("0xFFE4E1")),
entry("BlanchedAlmond", Color.decode("0xFFEBCD")),
entry("PapayaWhip", Color.decode("0xFFEFD5")),
entry("LavenderBlush", Color.decode("0xFFF0F5")),
entry("SeaShell", Color.decode("0xFFF5EE")),
entry("Cornsilk", Color.decode("0xFFF8DC")),
entry("LemonChiffon", Color.decode("0xFFFACD")),
entry("FloralWhite", Color.decode("0xFFFAF0")),
entry("Snow", Color.decode("0xFFFAFA")),
entry("Yellow", Color.decode("0xFFFF00")),
entry("LightYellow", Color.decode("0xFFFFE0")),
entry("Ivory", Color.decode("0xFFFFF0")),
entry("White", Color.decode("0xFFFFFF"))
);
/**
* a blue that is not as dark as {@code Color.blue}
*/
private static Color blue = new Color(100, 100, 255);
/**
* a yellow that is darker than {@code Color.yellow}
*/
private static Color darkerYellow = new Color(225, 225, 0);
/**
* these are vertex or edge types that have defined colors
* (the keys are the property values for the vertex/edge keys:
* VertexType and EdgeType)
*/
public static Map<String,Paint> VERTEX_TYPE_TO_COLOR_MAP =
Map.ofEntries(
entry("Body", blue),
entry("Entry", WEB_COLOR_MAP.get("DarkOrange")),
entry("Exit", Color.magenta),
entry("Switch", Color.cyan),
entry("Bad",Color.red),
entry("Entry-Nexus",Color.white),
entry("External",Color.green),
entry("Folder",WEB_COLOR_MAP.get("DarkOrange")),
entry("Fragment",WEB_COLOR_MAP.get("Purple")),
entry("Data",Color.pink)
);
/**
* these are vertex or edge types that have defined colors
* (the keys are the property values for the vertex/edge keys:
* VertexType and EdgeType)
*/
public static Map<String,Paint> EDGE_TYPE_TO_COLOR_MAP =
Map.ofEntries(
entry("Entry", Color.gray), // white??
entry("Fall-Through", Color.blue),
entry("Conditional-Call", WEB_COLOR_MAP.get("DarkOrange")),
entry("Unconditional-Call", WEB_COLOR_MAP.get("DarkOrange")),
entry("Computed",Color.cyan),
entry("Indirection",Color.pink),
entry("Unconditional-Jump", Color.green),
entry("Conditional-Jump", darkerYellow),
entry("Terminator", WEB_COLOR_MAP.get("Purple")),
entry("Conditional-Return", WEB_COLOR_MAP.get("Purple"))
);
/**
* Determine a color for the given {@link Attributed} object.
* <P>
* The attributed object can be an vertex or an edge. This method examines the attributes
* and tries to find an attribute that has a color mapping. Otherwise it returns a default
* color
* @param attributed the vertex or edge for which to determine a color
* @return the color to paint the given Attributed
*/
public static Paint getColor(Attributed attributed) {
Map<String, String> map = attributed.getAttributeMap();
// if there is a 'VertexType' attribute key, use its value to choose a predefined color
if (map.containsKey("VertexType")) {
String typeValue = map.get("VertexType");
return VERTEX_TYPE_TO_COLOR_MAP.getOrDefault(typeValue, Color.blue);
}
// if there is an 'EdgeType' attribute key, use its value to choose a predefined color
if (map.containsKey("EdgeType")) {
String typeValue = map.get("EdgeType");
return EDGE_TYPE_TO_COLOR_MAP.getOrDefault(typeValue, Color.green);
}
// if there is a 'Color' attribute key, use its value (either a color name or an RGB hex string)
// to choose a color
if (map.containsKey("Color")) {
String colorName = map.get("Color");
if (WEB_COLOR_MAP.containsKey(colorName)) {
return WEB_COLOR_MAP.get(colorName);
}
// if the value matches an RGB hex string, turn that into a color
Color c = getHexColor(colorName);
if (c != null) {
return c;
}
}
// default value when nothing else matches
return Color.green;
}
public static Color getHexColor(String hexString) {
Matcher matcher = HEX_PATTERN.matcher(hexString);
if (matcher.matches()) {
return Color.decode(hexString);
}
return null;
}
}
@@ -15,9 +15,7 @@
*/ */
package ghidra.graph.visualization; package ghidra.graph.visualization;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import ghidra.framework.options.Options; import ghidra.framework.options.Options;
@@ -54,13 +52,7 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider {
} }
@Override @Override
public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) { public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) {
return getGraphDisplay(reuseGraph, Collections.emptyMap(), monitor);
}
@Override
public GraphDisplay getGraphDisplay(boolean reuseGraph, Map<String, String> properties,
TaskMonitor monitor) {
if (reuseGraph && !displays.isEmpty()) { if (reuseGraph && !displays.isEmpty()) {
DefaultGraphDisplay visibleGraph = getVisibleGraph(); DefaultGraphDisplay visibleGraph = getVisibleGraph();
@@ -69,7 +61,7 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider {
} }
DefaultGraphDisplay display = DefaultGraphDisplay display =
Swing.runNow(() -> new DefaultGraphDisplay(this, properties, displayCounter++)); Swing.runNow(() -> new DefaultGraphDisplay(this, displayCounter++));
displays.add(display); displays.add(display);
return display; return display;
} }
@@ -0,0 +1,326 @@
/* ###
* 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.graph.visualization;
import static org.jungrapht.visualization.renderers.BiModalRenderer.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import javax.swing.*;
import javax.swing.border.Border;
import org.jungrapht.visualization.RenderContext;
import org.jungrapht.visualization.VisualizationViewer;
import org.jungrapht.visualization.decorators.*;
import org.jungrapht.visualization.layout.algorithms.util.InitialDimensionFunction;
import org.jungrapht.visualization.renderers.*;
import org.jungrapht.visualization.renderers.Renderer;
import org.jungrapht.visualization.renderers.Renderer.VertexLabel.Position;
import org.jungrapht.visualization.util.RectangleUtils;
import generic.util.image.ImageUtils;
import ghidra.service.graph.*;
/**
* Handles the rendering of graphs for the {@link DefaultGraphDisplay}
*/
public class DefaultGraphRenderer implements GraphRenderer {
private static final int DEFAULT_MARGIN_BORDER_SIZE = 4;
private static final int DEFAULT_STROKE_THICKNESS = 6;
private static final int ICON_ZOOM = 5;
private int labelBorderSize = DEFAULT_MARGIN_BORDER_SIZE;
private int strokeThickness = DEFAULT_STROKE_THICKNESS;
private JLabel label;
private GraphDisplayOptions options;
private final Map<AttributedVertex, Icon> iconCache = new ConcurrentHashMap<>();
private final Map<RenderingHints.Key, Object> renderingHints = new HashMap<>();
private Stroke edgeStroke = new BasicStroke(4.0f);
public DefaultGraphRenderer() {
this(new DefaultGraphDisplayOptions());
}
public DefaultGraphRenderer(GraphDisplayOptions options) {
this.options = options;
renderingHints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
label = new JLabel();
label.setForeground(Color.black);
label.setBackground(Color.white);
label.setOpaque(false);
Border marginBorder = BorderFactory.createEmptyBorder(labelBorderSize, 2 * labelBorderSize,
labelBorderSize, 2 * labelBorderSize);
label.setBorder(marginBorder);
}
@Override
public void setGraphTypeDisplayOptions(GraphDisplayOptions options) {
this.options = options;
clearCache();
}
@Override
public GraphDisplayOptions getGraphDisplayOptions() {
return options;
}
@Override
public void clearCache() {
iconCache.clear();
}
@Override
public void initializeViewer(VisualizationViewer<AttributedVertex, AttributedEdge> viewer) {
RenderContext<AttributedVertex, AttributedEdge> renderContext = viewer.getRenderContext();
Function<Shape, org.jungrapht.visualization.layout.model.Rectangle> toRectangle =
s -> RectangleUtils.convert(s.getBounds2D());
if (options.usesIcons()) {
// set up the shape and color functions
IconShapeFunction<AttributedVertex> nodeShaper =
new IconShapeFunction<>(new EllipseShapeFunction<>());
nodeShaper.setIconFunction(this::getIcon);
renderContext.setVertexShapeFunction(nodeShaper);
renderContext.setVertexIconFunction(this::getIcon);
int arrowLength = options.getArrowLength() * ICON_ZOOM;
int arrowWidth = (int) (arrowLength * 1.3);
renderContext.setEdgeArrowWidth(arrowWidth);
renderContext.setEdgeArrowLength(arrowLength);
renderContext.setVertexLabelFunction(v -> "");
viewer.setInitialDimensionFunction(
InitialDimensionFunction.builder(nodeShaper.andThen(toRectangle)).build());
}
else {
renderContext.setVertexIconFunction(null);
renderContext.setVertexShapeFunction(this::getVertexShape);
viewer.setInitialDimensionFunction(InitialDimensionFunction
.builder(renderContext.getVertexShapeFunction().andThen(toRectangle))
.build());
renderContext.setVertexLabelFunction(Object::toString);
GraphLabelPosition labelPosition = options.getLabelPosition();
renderContext.setVertexLabelPosition(getJungraphTPosition(labelPosition));
}
// assign the shapes to the modal renderer
// the modal renderer optimizes rendering for large graphs by removing detail
ModalRenderer<AttributedVertex, AttributedEdge> modalRenderer = viewer.getRenderer();
Renderer.Vertex<AttributedVertex, AttributedEdge> lightWeightRenderer =
modalRenderer.getVertexRenderer(LIGHTWEIGHT);
// set the lightweight (optimized) renderer to use the vertex shapes instead
// of using default shapes.
if (lightWeightRenderer instanceof LightweightVertexRenderer) {
LightweightVertexRenderer<AttributedVertex, AttributedEdge> lightweightVertexRenderer =
(LightweightVertexRenderer<AttributedVertex, AttributedEdge>) lightWeightRenderer;
Function<AttributedVertex, Shape> vertexShapeFunction =
renderContext.getVertexShapeFunction();
lightweightVertexRenderer.setVertexShapeFunction(vertexShapeFunction);
}
renderContext.setVertexFontFunction(this::getFont);
renderContext.setVertexLabelRenderer(new JLabelVertexLabelRenderer(Color.black));
renderContext.setVertexDrawPaintFunction(this::getVertexColor);
renderContext.setVertexFillPaintFunction(this::getVertexColor);
renderContext.setVertexStrokeFunction(n -> new BasicStroke(3.0f));
renderContext.setEdgeStrokeFunction(this::getEdgeStroke);
renderContext.setEdgeDrawPaintFunction(this::getEdgeColor);
renderContext.setArrowDrawPaintFunction(this::getEdgeColor);
renderContext.setArrowFillPaintFunction(this::getEdgeColor);
renderContext.setEdgeShapeFunction(EdgeShape.line());
}
private Shape getVertexShape(AttributedVertex vertex) {
if (vertex instanceof GroupVertex) {
return VertexShape.STAR.getShape();
}
VertexShape vertexShape = options.getVertexShape(vertex);
return vertexShape != null ? vertexShape.getShape() : VertexShape.RECTANGLE.getShape();
}
private Position getJungraphTPosition(GraphLabelPosition labelPosition) {
switch (labelPosition) {
case CENTER:
return Position.CNTR;
case EAST:
return Position.E;
case NORTH:
return Position.N;
case NORTHEAST:
return Position.NE;
case NORTHWEST:
return Position.NW;
case SOUTH:
return Position.S;
case SOUTHEAST:
return Position.SE;
case SOUTHWEST:
return Position.SW;
case WEST:
return Position.W;
default:
return Position.AUTO;
}
}
private Color getVertexColor(AttributedVertex vertex) {
return options.getVertexColor(vertex);
}
private Color getEdgeColor(AttributedEdge edge) {
return options.getEdgeColor(edge);
}
private Icon getIcon(AttributedVertex vertex) {
// WARNING: very important to not use map's computeIfAbsent() method
// because the map is synchronized and the createIcon() method will
// attempt to acquire the AWT lock. That combination will cause a deadlock
// if computeIfAbsent() is used and this method is called from non-swing thread.
Icon icon = iconCache.get(vertex);
if (icon == null) {
icon = createIcon(vertex);
iconCache.put(vertex, icon);
}
return icon;
}
private Icon createIcon(AttributedVertex vertex) {
VertexShape vertexShape = options.getVertexShape(vertex);
Color vertexColor = options.getVertexColor(vertex);
String labelText = options.getVertexLabel(vertex);
return createImage(vertexShape, labelText, vertexColor);
}
@Override
public void vertexChanged(AttributedVertex vertex) {
iconCache.remove(vertex);
}
private ImageIcon createImage(VertexShape vertexShape, String vertexName, Color vertexColor) {
prepareLabel(vertexName, vertexColor);
Shape unitShape = vertexShape.getShape();
Rectangle bounds = unitShape.getBounds();
int maxWidthToHeightRatio = vertexShape.getMaxWidthToHeightRatio();
double sizeFactor = vertexShape.getShapeToLabelRatio();
int labelWidth = label.getWidth();
int labelHeight = label.getHeight();
int iconWidth =
(int) (Math.max(labelWidth, labelHeight * 2.0) * sizeFactor) + strokeThickness;
int iconHeight =
(int) (Math.max(label.getHeight(), labelWidth / maxWidthToHeightRatio) * sizeFactor) +
strokeThickness;
double scalex = iconWidth / bounds.getWidth();
double scaley = iconHeight / bounds.getHeight();
Shape scaledShape =
AffineTransform.getScaleInstance(scalex, scaley).createTransformedShape(unitShape);
double labelOffsetRatio = vertexShape.getLabelPosition();
bounds = scaledShape.getBounds();
int width = bounds.width + 2 * strokeThickness;
int height = bounds.height + strokeThickness;
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = bufferedImage.createGraphics();
graphics.setRenderingHints(renderingHints);
AffineTransform graphicsTransform = graphics.getTransform();
graphics.translate(-bounds.x + strokeThickness, -bounds.y + strokeThickness / 2);
graphics.setPaint(Color.WHITE);
graphics.fill(scaledShape);
graphics.setPaint(vertexColor);
graphics.setStroke(new BasicStroke(strokeThickness));
graphics.draw(scaledShape);
graphics.setTransform(graphicsTransform);
int xOffset = (width - label.getWidth()) / 2;
int yOffset = (int) ((height - label.getHeight()) * labelOffsetRatio);
graphics.translate(xOffset, yOffset);
graphics.setPaint(Color.black);
label.paint(graphics);
graphics.setTransform(graphicsTransform); // restore the original transform
graphics.dispose();
Image scaledImage =
ImageUtils.createScaledImage(bufferedImage, width * ICON_ZOOM, height * ICON_ZOOM,
Image.SCALE_FAST);
ImageIcon imageIcon = new ImageIcon(scaledImage);
return imageIcon;
}
private void prepareLabel(String vertexName, Color vertexColor) {
label.setFont(options.getFont());
label.setText(vertexName);
Dimension labelSize = label.getPreferredSize();
label.setSize(labelSize);
}
@Override
public String getFavoredEdgeType() {
return options.getFavoredEdgeType();
}
@Override
public Integer getEdgePriority(String edgeType) {
return options.getEdgePriority(edgeType);
}
private Stroke getEdgeStroke(AttributedEdge edge) {
return edgeStroke;
}
@Override
public Color getVertexSelectionColor() {
return options.getVertexSelectionColor();
}
@Override
public Color getEdgeSelectionColor() {
return options.getEdgeSelectionColor();
}
private Font getFont(AttributedVertex attributedvertex1) {
return options.getFont();
}
}
@@ -15,44 +15,42 @@
*/ */
package ghidra.graph.visualization; package ghidra.graph.visualization;
import ghidra.service.graph.AttributedEdge;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap;
import java.util.List; import ghidra.service.graph.AttributedEdge;
import java.util.Map; import ghidra.service.graph.GraphType;
/** /**
* {@code Comparator} to order {@code AttributedEdge}s based on their position in a * Edge comparator that compares edges based on their edge type. The default renderer will use
* supplied {@code List}. * the order in which the edge types were defined in the {@link GraphType}.
*
*/ */
public class EdgeComparator implements Comparator<AttributedEdge> { public class EdgeComparator implements Comparator<AttributedEdge> {
/** private GraphRenderer renderer;
* {@code Map} of EdgeType attribute value to integer priority
*/
private Map<String, Integer> edgePriorityMap = new HashMap();
/** public EdgeComparator(GraphRenderer renderer) {
* Create an instance and place the list values into the {@code edgePriorityMap} this.renderer = renderer;
* with a one-up counter expressing their relative priority
* @param edgePriorityList
*/
public EdgeComparator(List<String> edgePriorityList) {
edgePriorityList.forEach(s -> edgePriorityMap.put(s, edgePriorityList.indexOf(s)));
} }
/**
* {@inheritdoc}
* Compares the {@code AttributedEdge}s using their priority in the supplied {@code edgePriorityMap}
*/
@Override @Override
public int compare(AttributedEdge edgeOne, AttributedEdge edgeTwo) { public int compare(AttributedEdge edge1, AttributedEdge edge2) {
return priority(edgeOne).compareTo(priority(edgeTwo)); String edgeType1 = edge1.getEdgeType();
String edgeType2 = edge2.getEdgeType();
if (edgeType1 == null && edgeType2 == null) {
return 0;
}
if (edgeType1 == null) {
return 1;
}
if (edgeType2 == null) {
return -1;
}
Integer priority1 = renderer.getEdgePriority(edgeType1);
Integer priority2 = renderer.getEdgePriority(edgeType2);
return priority1.compareTo(priority2);
} }
private Integer priority(AttributedEdge e) {
return edgePriorityMap.getOrDefault(e.getAttribute("EdgeType"), 0);
}
} }
@@ -1,210 +0,0 @@
/* ###
* 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.graph.visualization;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import ghidra.service.graph.AttributedVertex;
public class GhidraIconCache {
private static final int DEFAULT_STROKE_THICKNESS = 12;
private static final int DEFAULT_FONT_SIZE = 12;
private static final String DEFAULT_FONT_NAME = "Dialog";
private static final int DEFAULT_MARGIN_BORDER_SIZE = 8;
private static final float LABEL_TO_ICON_PROPORTION = 1.1f;
private final JLabel rendererLabel = new JLabel();
private final Map<RenderingHints.Key, Object> renderingHints = new HashMap<>();
private int strokeThickness = DEFAULT_STROKE_THICKNESS;
private final Map<AttributedVertex, Icon> map = new ConcurrentHashMap<>();
private final IconShape.Function iconShapeFunction = new IconShape.Function();
private String preferredVeretxLabelAttribute = null;
Icon get(AttributedVertex vertex) {
// WARNING: very important to not use map's computeIfAbsent() method
// because the map is synchronized and the createIcon() method will
// attempt to acquire the AWT lock. That combination will cause a deadlock
// if computeIfAbsent() is used and this method is called from non-swing thread.
Icon icon = map.get(vertex);
if (icon == null) {
icon = createIcon(vertex);
map.put(vertex, icon);
}
return icon;
}
GhidraIconCache() {
renderingHints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
}
private Icon createIcon(AttributedVertex vertex) {
rendererLabel
.setText(ProgramGraphFunctions.getLabel(vertex, preferredVeretxLabelAttribute));
rendererLabel.setFont(new Font(DEFAULT_FONT_NAME, Font.BOLD, DEFAULT_FONT_SIZE));
rendererLabel.setForeground(Color.black);
rendererLabel.setBackground(Color.white);
rendererLabel.setOpaque(true);
Border lineBorder = BorderFactory.createLineBorder((Color) Colors.getColor(vertex), 2);
Border marginBorder = BorderFactory.createEmptyBorder(DEFAULT_MARGIN_BORDER_SIZE,
DEFAULT_MARGIN_BORDER_SIZE, DEFAULT_MARGIN_BORDER_SIZE, DEFAULT_MARGIN_BORDER_SIZE);
rendererLabel.setBorder(new CompoundBorder(lineBorder, marginBorder));
Dimension labelSize = rendererLabel.getPreferredSize();
rendererLabel.setSize(labelSize);
Shape shape = ProgramGraphFunctions.getVertexShape(vertex);
IconShape.Type shapeType = iconShapeFunction.apply(shape);
return createImageIcon(vertex, shapeType, rendererLabel, labelSize, shape);
}
/**
* Based on the shape and characteristics of the vertex label (color, text) create and cache an ImageIcon
* that will be used to draw the vertex
*
* @param vertex the vertex to draw (and the key for the cache)
* @param vertexShapeCategory the type of Ghidra vertex shape
* @param label the {@link JLabel} used to draw the label. Note that it will parse html for formatting.
* @param labelSize the dimensions of the JLabel after it has been parsed
* @param vertexShape the primitive {@link Shape} used to represent the vertex
*/
private Icon createImageIcon(AttributedVertex vertex, IconShape.Type vertexShapeCategory,
JLabel label, Dimension labelSize, Shape vertexShape) {
int offset = 0;
double scalex;
double scaley;
switch (vertexShapeCategory) {
// triangles have a non-zero +/- yoffset instead of centering the label
case TRIANGLE:
// scale the vertex shape
scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth() *
LABEL_TO_ICON_PROPORTION;
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() *
LABEL_TO_ICON_PROPORTION;
vertexShape = AffineTransform.getScaleInstance(scalex, scaley)
.createTransformedShape(vertexShape);
offset = -(int) ((vertexShape.getBounds().getHeight() - labelSize.getHeight()) / 2);
break;
case INVERTED_TRIANGLE:
scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth() *
LABEL_TO_ICON_PROPORTION;
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() *
LABEL_TO_ICON_PROPORTION;
vertexShape = AffineTransform.getScaleInstance(scalex, scaley)
.createTransformedShape(vertexShape);
offset = (int) ((vertexShape.getBounds().getHeight() - labelSize.getHeight()) / 2);
break;
// rectangles can fit a full-sized label
case RECTANGLE:
scalex = labelSize.getWidth() / vertexShape.getBounds().getWidth();
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight();
vertexShape = AffineTransform.getScaleInstance(scalex, scaley)
.createTransformedShape(vertexShape);
break;
// diamonds and ellipses reduce the label size to fit
case DIAMOND:
default: // ELLIPSE
scalex =
labelSize.getWidth() / vertexShape.getBounds().getWidth() * 1.1;
scaley = labelSize.getHeight() / vertexShape.getBounds().getHeight() * 1.1;
vertexShape = AffineTransform.getScaleInstance(scalex, scaley)
.createTransformedShape(vertexShape);
break;
}
Rectangle vertexBounds = vertexShape.getBounds();
BufferedImage bufferedImage = new BufferedImage(vertexBounds.width + (2 * strokeThickness),
vertexBounds.height + (2 * strokeThickness), BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = bufferedImage.createGraphics();
graphics.setRenderingHints(renderingHints);
AffineTransform graphicsTransform = graphics.getTransform();
// draw the shape, offset by 1/2 its width and the strokeThickness
AffineTransform offsetTransform =
AffineTransform.getTranslateInstance(strokeThickness + vertexBounds.width / 2.0,
strokeThickness + vertexBounds.height / 2.0);
offsetTransform.preConcatenate(graphicsTransform);
graphics.setTransform(offsetTransform);
graphics.setPaint(Color.white);
graphics.fill(vertexShape);
graphics.setPaint(Colors.getColor(vertex));
graphics.setStroke(new BasicStroke(strokeThickness));
graphics.draw(vertexShape);
// draw the JLabel, offset by 1/2 its width and the strokeThickness
int xoffset = strokeThickness + (vertexBounds.width - labelSize.width) / 2;
int yoffset = strokeThickness + (vertexBounds.height - labelSize.height) / 2;
offsetTransform = AffineTransform.getTranslateInstance(xoffset, yoffset + offset);
offsetTransform.preConcatenate(graphicsTransform);
graphics.setPaint(Color.black);
graphics.setTransform(offsetTransform);
label.paint(graphics);
// draw the shape again, but lighter (on top of the label)
offsetTransform =
AffineTransform.getTranslateInstance(strokeThickness + vertexBounds.width / 2.0,
strokeThickness + vertexBounds.height / 2.0);
offsetTransform.preConcatenate(graphicsTransform);
graphics.setTransform(offsetTransform);
Paint paint = Colors.getColor(vertex);
if (paint instanceof Color) {
Color color = (Color) paint;
Color transparent = new Color(color.getRed(), color.getGreen(), color.getBlue(), 50);
graphics.setPaint(transparent);
graphics.setStroke(new BasicStroke(strokeThickness));
graphics.draw(vertexShape);
}
graphics.setTransform(graphicsTransform); // restore the original transform
graphics.dispose();
return new ImageIcon(bufferedImage);
}
public void clear() {
map.clear();
}
/**
* evict the passed vertex from the cache so that it will be recomputed
* with presumably changed values
* @param vertex to remove from the cache
*/
public void evict(AttributedVertex vertex) {
map.remove(vertex);
}
/**
* Sets the vertex label to the value of the passed attribute name
* @param attributeName the attribute key for the vertex label value to be displayed
*/
public void setPreferredVertexLabelAttribute(String attributeName) {
this.preferredVeretxLabelAttribute = attributeName;
}
}
@@ -0,0 +1,90 @@
/* ###
* 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.graph.visualization;
import java.awt.Color;
import org.jungrapht.visualization.VisualizationViewer;
import ghidra.service.graph.*;
/**
* Interface for GraphRenderer used by the {@link DefaultGraphDisplay}. Developers can add new
* implementations to change the graph rendering
*/
public interface GraphRenderer {
/**
* Initializes the {@link VisualizationViewer}. When a new {@link DefaultGraphDisplay} is
* created, it uses a JungraphT {@link VisualizationViewer} to display a graph. That viewer
* has many configuration settings. The GraphRender needs to initialize the viewer so that
* it calls back to this renderer to get the vertex and edge data/functions that it needs
* to render a graph. This is how the GraphRender can inject is display style into the graph
* display.
* <P>
* @param viewer the {@link VisualizationViewer}
*/
public void initializeViewer(VisualizationViewer<AttributedVertex, AttributedEdge> viewer);
/**
* Sets the graph display options that are specific to a particular graph type
* @param options the {@link GraphDisplayOptions} which are options for a specific graph type
*/
public void setGraphTypeDisplayOptions(GraphDisplayOptions options);
/**
* Returns the current {@link GraphDisplayOptions} being used
* @return the current {@link GraphDisplayOptions} being used
*/
public GraphDisplayOptions getGraphDisplayOptions();
/**
* Tells this renderer that the given vertex changed and needs to be redrawn
* @param vertex the vertex that changed
*/
public void vertexChanged(AttributedVertex vertex);
/**
* Returns the favored edge type
* @return the favored edge type
*/
public String getFavoredEdgeType();
/**
* Returns the edge priority for the edge type
* @param edgeType the edge type to get priority for
* @return the edge priority for the edge type
*/
public Integer getEdgePriority(String edgeType);
/**
* Clears any cached renderings
*/
public void clearCache();
/**
* Returns the vertex selection color
* @return the vertex selection color
*/
public Color getVertexSelectionColor();
/**
* Returns the edge selection color
* @return the edge selection color
*/
public Color getEdgeSelectionColor();
}
@@ -46,8 +46,7 @@ public class GroupVertex extends AttributedVertex {
super(id); super(id);
this.first = first; this.first = first;
this.children = children; this.children = children;
setAttribute("VertexType", "Collapsed"); setVertexType("Collapsed Group");
setAttribute("Icon", "Star");
} }
/** /**
@@ -1,104 +0,0 @@
/* ###
* 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.graph.visualization;
import java.awt.Shape;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
/**
* Holds the enum for shape type and the Function to categorize the archetype Shapes into
* IconShape.Types. Note that the archetype shapes are centered at the origin
*/
public class IconShape {
public enum Type {
TRIANGLE, INVERTED_TRIANGLE, RECTANGLE, DIAMOND, ELLIPSE
}
/**
* Categorize the supplied Shape into one of several simple types.
*
*/
static class Function implements java.util.function.Function<Shape, Type> {
@Override
public Type apply(Shape shape) {
List<Point2D> points = getShapePoints(shape);
if (points.size() == 3) {
if (isInvertedTriangle(points)) {
return Type.INVERTED_TRIANGLE;
} else {
return Type.TRIANGLE;
}
}
// there are 5 points because the final point is the same as the first
// and closes the shape.
if (points.size() == 5) {
if (isDiamond(points)) {
return Type.DIAMOND;
} else {
return Type.RECTANGLE;
}
}
// default to ellipse for anything with more that 4 sides
return Type.ELLIPSE;
}
/**
*
* Note that for awt drawing, the origin is at the upper left so positive y extends downwards.
* @param threePoints odd number of points bounding a {@link Shape} centered at the origin
* @return true it there are fewer points with y below 0
*/
boolean isInvertedTriangle(List<Point2D> threePoints) {
if (threePoints.size() != 3) {
throw new IllegalArgumentException("Shape from " + threePoints + " is not a triangle");
}
return threePoints.stream().filter(p -> p.getY() < 0).count() <= threePoints.size() / 2;
}
/**
*
* @param fivePoints odd number of points bounding a {@link Shape} centered at the origin
* @return true it there are 2 points with y value 0
*/
boolean isDiamond(List<Point2D> fivePoints) {
if (fivePoints.size() != 5) {
throw new IllegalArgumentException(
"Shape from " + fivePoints + " is not a quadrilateral");
}
return fivePoints.stream().filter(p -> (int) p.getY() == 0).count() == 2;
}
List<Point2D> getShapePoints(Shape shape) {
float[] seg = new float[6];
List<Point2D> points = new ArrayList<>();
for (PathIterator i = shape.getPathIterator(null, 1); !i.isDone(); i.next()) {
int ret = i.currentSegment(seg);
if (ret == PathIterator.SEG_MOVETO) {
points.add(new Point2D.Float(seg[0], seg[1]));
}
else if (ret == PathIterator.SEG_LINETO) {
points.add(new Point2D.Float(seg[0], seg[1]));
}
}
return points;
}
}
}
@@ -15,6 +15,8 @@
*/ */
package ghidra.graph.visualization; package ghidra.graph.visualization;
import static ghidra.service.graph.LayoutAlgorithmNames.*;
import java.util.Comparator; import java.util.Comparator;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
@@ -23,6 +25,8 @@ import org.jungrapht.visualization.layout.algorithms.*;
import org.jungrapht.visualization.layout.algorithms.repulsion.BarnesHutFRRepulsion; import org.jungrapht.visualization.layout.algorithms.repulsion.BarnesHutFRRepulsion;
import org.jungrapht.visualization.layout.algorithms.sugiyama.Layering; import org.jungrapht.visualization.layout.algorithms.sugiyama.Layering;
import com.google.common.base.Objects;
import ghidra.service.graph.AttributedEdge; import ghidra.service.graph.AttributedEdge;
import ghidra.service.graph.AttributedVertex; import ghidra.service.graph.AttributedVertex;
@@ -36,61 +40,32 @@ import ghidra.service.graph.AttributedVertex;
class LayoutFunction class LayoutFunction
implements Function<String, LayoutAlgorithm.Builder<AttributedVertex, ?, ?>> { implements Function<String, LayoutAlgorithm.Builder<AttributedVertex, ?, ?>> {
static final String KAMADA_KAWAI = "Force Balanced";
static final String FRUCTERMAN_REINGOLD = "Force Directed";
static final String CIRCLE = "Circle";
static final String TIDIER_TREE = "Compact Hierarchical";
static final String TIDIER_RADIAL_TREE = "Compact Radial";
static final String MIN_CROSS = "Hierarchical MinCross"; //not an alg, just a parent category
static final String MIN_CROSS_TOP_DOWN = "Hierarchical MinCross Top Down";
static final String MIN_CROSS_LONGEST_PATH = "Hierarchical MinCross Longest Path";
static final String MIN_CROSS_NETWORK_SIMPLEX = "Hierarchical MinCross Network Simplex";
static final String MIN_CROSS_COFFMAN_GRAHAM = "Hierarchical MinCross Coffman Graham";
static final String VERT_MIN_CROSS = "Vertical Hierarchical MinCross"; //not an alg, just a parent category
static final String VERT_MIN_CROSS_TOP_DOWN = "Vertical Hierarchical MinCross Top Down";
static final String VERT_MIN_CROSS_LONGEST_PATH = "Vertical Hierarchical MinCross Longest Path";
static final String VERT_MIN_CROSS_NETWORK_SIMPLEX = "Vertical Hierarchical MinCross Network Simplex";
static final String VERT_MIN_CROSS_COFFMAN_GRAHAM = "Vertical Hierarchical MinCross Coffman Graham";
static final String TREE = "Hierarchical";
static final String RADIAL = "Radial";
static final String BALLOON = "Balloon";
static final String GEM = "GEM";
Predicate<AttributedEdge> favoredEdgePredicate; Predicate<AttributedEdge> favoredEdgePredicate;
Comparator<AttributedEdge> edgeTypeComparator; Comparator<AttributedEdge> edgeTypeComparator;
LayoutFunction(Comparator<AttributedEdge> edgeTypeComparator, Predicate<AttributedEdge> favoredEdgePredicate) { LayoutFunction(GraphRenderer renderer) {
this.edgeTypeComparator = edgeTypeComparator; this.edgeTypeComparator = new EdgeComparator(renderer);
this.favoredEdgePredicate = favoredEdgePredicate; this.favoredEdgePredicate =
edge -> Objects.equal(edge.getEdgeType(), renderer.getFavoredEdgeType());
} }
public String[] getNames() {
return new String[] { TIDIER_TREE, TREE,
TIDIER_RADIAL_TREE, MIN_CROSS_TOP_DOWN, MIN_CROSS_LONGEST_PATH,
MIN_CROSS_NETWORK_SIMPLEX, MIN_CROSS_COFFMAN_GRAHAM, CIRCLE,
VERT_MIN_CROSS_TOP_DOWN,
VERT_MIN_CROSS_LONGEST_PATH,
VERT_MIN_CROSS_NETWORK_SIMPLEX,
VERT_MIN_CROSS_COFFMAN_GRAHAM,
KAMADA_KAWAI, FRUCTERMAN_REINGOLD, RADIAL, BALLOON, GEM
};
}
@Override @Override
public LayoutAlgorithm.Builder<AttributedVertex, ?, ?> apply(String name) { public LayoutAlgorithm.Builder<AttributedVertex, ?, ?> apply(String name) {
switch(name) { switch(name) {
case GEM: case GEM:
return GEMLayoutAlgorithm.edgeAwareBuilder(); return GEMLayoutAlgorithm.edgeAwareBuilder();
case KAMADA_KAWAI: case FORCED_BALANCED:
return KKLayoutAlgorithm.<AttributedVertex> builder() return KKLayoutAlgorithm.<AttributedVertex> builder()
.preRelaxDuration(1000); .preRelaxDuration(1000);
case FRUCTERMAN_REINGOLD: case FORCE_DIRECTED:
return FRLayoutAlgorithm.<AttributedVertex> builder() return FRLayoutAlgorithm.<AttributedVertex> builder()
.repulsionContractBuilder(BarnesHutFRRepulsion.builder()); .repulsionContractBuilder(BarnesHutFRRepulsion.builder());
case CIRCLE: case CIRCLE:
return CircleLayoutAlgorithm.<AttributedVertex> builder() return CircleLayoutAlgorithm.<AttributedVertex> builder()
.reduceEdgeCrossing(false); .reduceEdgeCrossing(false);
case TIDIER_RADIAL_TREE: case COMPACT_RADIAL:
return TidierRadialTreeLayoutAlgorithm return TidierRadialTreeLayoutAlgorithm
.<AttributedVertex, AttributedEdge> edgeAwareBuilder() .<AttributedVertex, AttributedEdge> edgeAwareBuilder()
.edgeComparator(edgeTypeComparator); .edgeComparator(edgeTypeComparator);
@@ -146,10 +121,10 @@ class LayoutFunction
return BalloonLayoutAlgorithm return BalloonLayoutAlgorithm
.<AttributedVertex> builder() .<AttributedVertex> builder()
.verticalVertexSpacing(300); .verticalVertexSpacing(300);
case TREE: case HIERACHICAL:
return EdgeAwareTreeLayoutAlgorithm return EdgeAwareTreeLayoutAlgorithm
.<AttributedVertex, AttributedEdge>edgeAwareBuilder(); .<AttributedVertex, AttributedEdge>edgeAwareBuilder();
case TIDIER_TREE: case COMPACT_HIERACHICAL:
default: default:
return TidierTreeLayoutAlgorithm return TidierTreeLayoutAlgorithm
.<AttributedVertex, AttributedEdge> edgeAwareBuilder() .<AttributedVertex, AttributedEdge> edgeAwareBuilder()
@@ -15,10 +15,6 @@
*/ */
package ghidra.graph.visualization; package ghidra.graph.visualization;
import static ghidra.graph.visualization.LayoutFunction.*;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
@@ -30,8 +26,7 @@ import org.jungrapht.visualization.layout.model.Rectangle;
import org.jungrapht.visualization.util.LayoutAlgorithmTransition; import org.jungrapht.visualization.util.LayoutAlgorithmTransition;
import org.jungrapht.visualization.util.LayoutPaintable; import org.jungrapht.visualization.util.LayoutPaintable;
import ghidra.service.graph.AttributedEdge; import ghidra.service.graph.*;
import ghidra.service.graph.AttributedVertex;
/** /**
* Manages the selection and transition from one {@link LayoutAlgorithm} to another * Manages the selection and transition from one {@link LayoutAlgorithm} to another
@@ -73,14 +68,13 @@ class LayoutTransitionManager {
public LayoutTransitionManager( public LayoutTransitionManager(
VisualizationServer<AttributedVertex, AttributedEdge> visualizationServer, VisualizationServer<AttributedVertex, AttributedEdge> visualizationServer,
Predicate<AttributedVertex> rootPredicate, Predicate<AttributedVertex> rootPredicate,
List<String> edgeTypePriorityList, GraphRenderer renderer) {
Predicate<AttributedEdge> favoredEdgePredicate) {
this.visualizationServer = visualizationServer; this.visualizationServer = visualizationServer;
this.rootPredicate = rootPredicate; this.rootPredicate = rootPredicate;
this.renderContext = visualizationServer.getRenderContext(); this.renderContext = visualizationServer.getRenderContext();
this.vertexBoundsFunction = visualizationServer.getRenderContext().getVertexBoundsFunction(); this.vertexBoundsFunction = visualizationServer.getRenderContext().getVertexBoundsFunction();
this.layoutFunction = new LayoutFunction(new EdgeComparator(edgeTypePriorityList), this.layoutFunction = new LayoutFunction(renderer);
favoredEdgePredicate);
} }
/** /**
@@ -144,7 +138,7 @@ class LayoutTransitionManager {
*/ */
public LayoutAlgorithm<AttributedVertex> getInitialLayoutAlgorithm() { public LayoutAlgorithm<AttributedVertex> getInitialLayoutAlgorithm() {
LayoutAlgorithm<AttributedVertex> initialLayoutAlgorithm = LayoutAlgorithm<AttributedVertex> initialLayoutAlgorithm =
layoutFunction.apply(TIDIER_TREE).build(); layoutFunction.apply(LayoutAlgorithmNames.COMPACT_HIERACHICAL).build();
if (initialLayoutAlgorithm instanceof TreeLayout) { if (initialLayoutAlgorithm instanceof TreeLayout) {
((TreeLayout<AttributedVertex>) initialLayoutAlgorithm) ((TreeLayout<AttributedVertex>) initialLayoutAlgorithm)
@@ -157,11 +151,4 @@ class LayoutTransitionManager {
return initialLayoutAlgorithm; return initialLayoutAlgorithm;
} }
/**
* Supplies a {@code String[]} array of the supported layout names
* @return
*/
public String[] getLayoutNames() {
return layoutFunction.getNames();
}
} }
@@ -1,140 +0,0 @@
/* ###
* 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.graph.visualization;
import static org.jungrapht.visualization.layout.util.PropertyLoader.*;
import java.awt.*;
import java.util.Map;
import org.apache.commons.text.StringEscapeUtils;
import org.jungrapht.visualization.util.ShapeFactory;
import com.google.common.base.Splitter;
import ghidra.service.graph.Attributed;
import ghidra.service.graph.AttributedEdge;
/**
* a container for various functions used by ProgramGraph
*/
abstract class ProgramGraphFunctions {
static float edgeWidth = Float.parseFloat(System.getProperty(PREFIX + "edgeWidth", "4.0f"));
// cannot instantiate nor extend
private ProgramGraphFunctions() {
}
/**
* a default implementation of a {@link ShapeFactory} to supply shapes for attributed vertices and edges
*/
private static ShapeFactory<Attributed> shapeFactory = new ShapeFactory<>(n -> 50, n -> 1.0f);
/**
* return various 'Shapes' based on an attribute name
*
* @param n the attributed key (a vertex or edge)
* @param name the attribute name
* @return a Shape for the passed 'n' with attribute 'name'
*/
private static Shape byShapeName(Attributed n, String name) {
if (name == null) {
return null;
}
switch (name) {
case "Square":
return shapeFactory.getRectangle(n);
case "Circle":
return shapeFactory.getEllipse(n);
case "Triangle":
return shapeFactory.getRegularPolygon(n, 3);
case "TriangleDown":
return shapeFactory.getRegularPolygon(n, 3, Math.PI);
case "Diamond":
return shapeFactory.getRectangle(n, Math.PI / 4);
case "Star":
return shapeFactory.getRegularStar(n, 5);
case "Pentagon":
return shapeFactory.getRegularPolygon(n, 5);
case "Hexagon":
return shapeFactory.getRegularPolygon(n, 6);
case "Octagon":
return shapeFactory.getRegularPolygon(n, 8);
default:
return null;
}
}
/*
* Gets the Shape object to use when drawing this vertex. If "Icon" attribute
* is set it will use that, otherwise "VertexType" to will translate a code flow
* name to a shape
*
* @param vertex the Attributed object to get a shape for
* @return a Shape object to use when displaying the object
*/
public static Shape getVertexShape(Attributed vertex) {
Shape shape = byShapeName(vertex, vertex.getAttribute("Icon"));
if (shape != null) {
return shape;
}
String vertexType = vertex.getAttribute("VertexType");
if (vertexType == null) {
return shapeFactory.getRectangle(vertex);
}
switch (vertexType) {
case "Entry":
return shapeFactory.getRegularPolygon(vertex, 3, Math.PI);
case "Exit":
return shapeFactory.getRegularPolygon(vertex, 3);
case "Switch":
return shapeFactory.getRectangle(vertex, Math.PI / 4);
case "Body":
case "External":
return shapeFactory.getRectangle(vertex);
default:
return shapeFactory.getEllipse(vertex);
}
}
/**
* Provides a {@link Stroke} (line width and style) for an attributed edge
* @param edge the edge to get a stroke value
* @return the stroke for the edge
*/
public static Stroke getEdgeStroke(AttributedEdge edge) {
String edgeType = edge.getAttribute("EdgeType");
if (edgeType != null && edgeType.equals("Fall-Through")) {
return new BasicStroke(edgeWidth * 2);
}
return new BasicStroke(edgeWidth);
}
/**
* gets a display label from an {@link Attributed} object (vertex)
* @param attributed the attributed object to get a label for
* @param preferredLabelAttribute the attribute to use for the label, if available
* @return the label for the given {@link Attributed}
*/
public static String getLabel(Attributed attributed, String preferredLabelAttribute) {
Map<String, String> map = attributed.getAttributeMap();
String name = StringEscapeUtils.escapeHtml4(map.get("Name"));
if (map.containsKey(preferredLabelAttribute)) {
name = StringEscapeUtils.escapeHtml4(map.get(preferredLabelAttribute));
}
return "<html>" + String.join("<p>", Splitter.on('\n').split(name));
}
}
@@ -73,4 +73,8 @@ public class JgtGraphMouse extends DefaultGraphMouse<AttributedVertex, Attribute
setPluginsLoaded(); setPluginsLoaded();
} }
public boolean allowsEdgeSelection() {
return allowEdgeSelection;
}
} }
@@ -32,7 +32,7 @@ jungrapht.edgeWidth=4.0f
# stroke size for the magnifier lens # stroke size for the magnifier lens
jungrapht.lensStrokeWidth=10.0 jungrapht.lensStrokeWidth=10.0
# when scale is < .1, switch to lightweight rendering # when scale is < .1, switch to lightweight rendering
jungrapht.lightweightScaleThreshold=.1 jungrapht.lightweightScaleThreshold=.01
# under 50 vertices will use heavyweight rendering all the time # under 50 vertices will use heavyweight rendering all the time
jungrapht.lightweightCountThreshold=80 jungrapht.lightweightCountThreshold=80
@@ -277,7 +277,7 @@ public class AttributedGraphExportersTest extends AbstractGenericTest {
} }
private AttributedGraph createGraph() { private AttributedGraph createGraph() {
AttributedGraph g = new AttributedGraph(); AttributedGraph g = new AttributedGraph("Test", new EmptyGraphType());
AttributedVertex vA = g.addVertex("A"); AttributedVertex vA = g.addVertex("A");
AttributedVertex vB = g.addVertex("B"); AttributedVertex vB = g.addVertex("B");
AttributedVertex vC = g.addVertex("C"); AttributedVertex vC = g.addVertex("C");
@@ -311,7 +311,7 @@ public class GraphExportTest extends AbstractGhidraHeadedIntegrationTest {
} }
private AttributedGraph createGraph() { private AttributedGraph createGraph() {
AttributedGraph graph = new AttributedGraph(); AttributedGraph graph = new AttributedGraph("Test", new EmptyGraphType());
AttributedVertex vA = graph.addVertex("A"); AttributedVertex vA = graph.addVertex("A");
AttributedVertex vB = graph.addVertex("B"); AttributedVertex vB = graph.addVertex("B");
AttributedVertex vC = graph.addVertex("C"); AttributedVertex vC = graph.addVertex("C");
@@ -1,75 +0,0 @@
/* ###
* 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.graph.visualization;
import static org.junit.Assert.*;
import java.awt.Color;
import org.junit.Test;
import ghidra.service.graph.AttributedEdge;
import ghidra.service.graph.AttributedVertex;
public class ColorsTest {
@Test
public void testParseHashHexColor() {
Color hexColor = Colors.getHexColor("#ff0000");
assertEquals(Color.RED, hexColor);
}
@Test
public void testParseHexColor() {
Color hexColor = Colors.getHexColor("0xff0000");
assertEquals(Color.RED, hexColor);
}
@Test
public void testGetColorFromVertexType() {
AttributedVertex vertex = new AttributedVertex("A");
vertex.setAttribute("VertexType", "Exit");
vertex.setAttribute("Color", "0xffffff");
assertEquals(Color.MAGENTA, Colors.getColor(vertex));
}
@Test
public void testGetColorFromVertexNoVertexType() {
AttributedVertex vertex = new AttributedVertex("A");
vertex.setAttribute("Color", "0xffffff");
assertEquals(Color.WHITE, Colors.getColor(vertex));
}
@Test
public void testGetColorFromVertexNoAttributes() {
AttributedVertex vertex = new AttributedVertex("A");
assertEquals(Color.GREEN, Colors.getColor(vertex));
}
@Test
public void testGetColorFromEdgeType() {
AttributedEdge edge = new AttributedEdge("A");
edge.setAttribute("EdgeType", "Computed");
edge.setAttribute("Color", "0xffffff");
assertEquals(Color.CYAN, Colors.getColor(edge));
}
@Test
public void testGetColorFromEdgeNoEdgeType() {
AttributedEdge edge = new AttributedEdge("A");
edge.setAttribute("Color", "0xffffff");
assertEquals(Color.WHITE, Colors.getColor(edge));
}
}
@@ -1,70 +0,0 @@
/* ###
* 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.graph.visualization;
import ghidra.service.graph.AttributedVertex;
import org.junit.Assert;
import org.junit.Test;
import java.awt.geom.Rectangle2D;
public class IconShapeTest {
private IconShape.Function iconShapeFunction = new IconShape.Function();
@Test
public void testShapes() {
Rectangle2D rectangle = new Rectangle2D.Double(-10, -10, 20, 20);
Assert.assertEquals(IconShape.Type.RECTANGLE, iconShapeFunction.apply(rectangle));
AttributedVertex v = new AttributedVertex("id", "name");
// by vertex type
v.setAttribute("VertexType", "Entry");
Assert.assertEquals(IconShape.Type.TRIANGLE,
iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
v.setAttribute("VertexType", "Exit");
Assert.assertEquals(IconShape.Type.INVERTED_TRIANGLE,
iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
v.setAttribute("VertexType", "Switch");
Assert.assertEquals(IconShape.Type.DIAMOND, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
v.setAttribute("VertexType", "Body");
Assert.assertEquals(IconShape.Type.RECTANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
v.setAttribute("VertexType", "External");
Assert.assertEquals(IconShape.Type.RECTANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
v.setAttribute("VertexType", "Foo");
Assert.assertEquals(IconShape.Type.ELLIPSE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
// by vertex icon shape name
v.removeAttribute("VertexType");
v.setAttribute("Icon", "Square");
Assert.assertEquals(IconShape.Type.RECTANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
v.setAttribute("Icon", "TriangleDown");
Assert.assertEquals(IconShape.Type.TRIANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
v.setAttribute("Icon", "Triangle");
Assert.assertEquals(IconShape.Type.INVERTED_TRIANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
v.setAttribute("Icon", "Diamond");
Assert.assertEquals(IconShape.Type.DIAMOND, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
v.setAttribute("Icon", "Circle");
Assert.assertEquals(IconShape.Type.ELLIPSE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
v.setAttribute("Icon", "Foo");
Assert.assertEquals(IconShape.Type.RECTANGLE, iconShapeFunction.apply(ProgramGraphFunctions.getVertexShape(v)));
}
}
@@ -489,16 +489,18 @@
</BLOCKQUOTE> </BLOCKQUOTE>
</BLOCKQUOTE> </BLOCKQUOTE>
<BR>
<BR>
<BR>
<BR>
<BR>
</ul>
<P class="providedbyplugin">Provided by: <I>Program Graph Plugin</I></P>
<P class="relatedtopic">Related Topics</P> <H2><A NAME="Program_Graphs_Display_Options">Program Graphs Display Options</H2>
<BLOCKQUOTE>
<P>These are the display options for graphs that are types of "Program Graphs" such as
Call graphs, Block graphs, etc. These types of graphs
use program elements as vertices and reference types as edges. See
<A href="help/topics/GraphServices/GraphDisplay.htm#Graph_Type_Display_Options">Graph Type Display Options</A> for
general help on graph type display options.</P>
</BLOCKQUOTE>
<P class="providedbyplugin">Provided by: <I>Program Graph Plugin</I></P>
<P class="relatedtopic">Related Topics</P>
<UL> <UL>
<LI><A href="help/topics/GraphServices/GraphDisplay.htm">Default Graph Display</A></LI> <LI><A href="help/topics/GraphServices/GraphDisplay.htm">Default Graph Display</A></LI>
<LI><A href="help/topics/GraphServices/GraphExport.htm">Graph Export</A></LI> <LI><A href="help/topics/GraphServices/GraphExport.htm">Graph Export</A></LI>
@@ -15,7 +15,8 @@
*/ */
package ghidra.graph.program; package ghidra.graph.program;
import java.awt.Color; import static ghidra.graph.ProgramGraphType.*;
import java.util.*; import java.util.*;
import docking.action.builder.ActionBuilder; import docking.action.builder.ActionBuilder;
@@ -23,6 +24,7 @@ import docking.widgets.EventTrigger;
import ghidra.app.plugin.core.colorizer.ColorizingService; import ghidra.app.plugin.core.colorizer.ColorizingService;
import ghidra.app.util.AddEditDialog; import ghidra.app.util.AddEditDialog;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.graph.*;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
import ghidra.program.model.block.*; import ghidra.program.model.block.*;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
@@ -30,7 +32,8 @@ import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection; import ghidra.program.util.ProgramSelection;
import ghidra.service.graph.*; import ghidra.service.graph.*;
import ghidra.util.*; import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
import ghidra.util.exception.GraphException; import ghidra.util.exception.GraphException;
import ghidra.util.task.Task; import ghidra.util.task.Task;
@@ -53,55 +56,6 @@ public class BlockGraphTask extends Task {
private ColorizingService colorizingService; private ColorizingService colorizingService;
/**
* Edge flow tags
*/
protected final static int FALLTHROUGH = 0;
protected final static int CONDITIONAL_RETURN = 1;
protected final static int UNCONDITIONAL_JUMP = 2;
protected final static int CONDITIONAL_JUMP = 3;
protected final static int UNCONDITIONAL_CALL = 4;
protected final static int CONDITIONAL_CALL = 5;
protected final static int TERMINATOR = 6;
protected final static int COMPUTED = 7;
protected final static int INDIRECTION = 8;
protected final static int ENTRY = 9; // from Entry Nexus
protected final static String[] edgeNames =
{ "1", "2", "3", "4", "5", "6", "7", "13", "14", "15" };
// @formatter:off
protected final static String[] edgeTypes = {
"Fall-Through",
"Conditional-Return",
"Unconditional-Jump",
"Conditional-Jump",
"Unconditional-Call",
"Conditional-Call",
"Terminator",
"Computed",
"Indirection",
"Entry"
};
// @formatter:on
private final static String ENTRY_NODE = "Entry";
// "1"; // beginning of a block, someone calls it
private final static String BODY_NODE = "Body";
// "2"; // Body block, no flow
private final static String EXIT_NODE = "Exit";
// "3"; // Terminator
private final static String SWITCH_NODE = "Switch";
// "4"; // Switch/computed jump
private final static String BAD_NODE = "Bad";
// "5"; // Bad destination
private final static String DATA_NODE = "Data";
// "6"; // Data Node, used for indirection
private final static String ENTRY_NEXUS = "Entry-Nexus";
// "7"; //
private final static String EXTERNAL_NODE = "External";
// "8"; // node is external to program
private final static String ENTRY_NEXUS_NAME = "Entry Points"; private final static String ENTRY_NEXUS_NAME = "Entry Points";
private CodeBlockModel blockModel; private CodeBlockModel blockModel;
private AddressSetView selection; private AddressSetView selection;
@@ -113,15 +67,17 @@ public class BlockGraphTask extends Task {
private Program program; private Program program;
private AddressSetView graphScope; private AddressSetView graphScope;
private String graphTitle; private String graphTitle;
private ProgramGraphType graphType;
public BlockGraphTask(String actionName, boolean graphEntryPointNexus, boolean showCode, public BlockGraphTask(ProgramGraphType graphType,
boolean reuseGraph, boolean appendGraph, PluginTool tool, ProgramSelection selection, boolean graphEntryPointNexus, boolean reuseGraph, boolean appendGraph,
ProgramLocation location, CodeBlockModel blockModel, PluginTool tool, ProgramSelection selection, ProgramLocation location,
GraphDisplayProvider graphProvider) { CodeBlockModel blockModel, GraphDisplayProvider graphProvider) {
super("Graph Program", true, false, true); super("Graph Program", true, false, true);
this.graphType = graphType;
this.graphEntryPointNexus = graphEntryPointNexus; this.graphEntryPointNexus = graphEntryPointNexus;
this.showCode = showCode; this.showCode = graphType instanceof CodeFlowGraphType;
this.reuseGraph = reuseGraph; this.reuseGraph = reuseGraph;
this.appendGraph = appendGraph; this.appendGraph = appendGraph;
this.tool = tool; this.tool = tool;
@@ -131,7 +87,7 @@ public class BlockGraphTask extends Task {
this.selection = selection; this.selection = selection;
this.location = location; this.location = location;
this.program = blockModel.getProgram(); this.program = blockModel.getProgram();
this.graphTitle = actionName + ": "; this.graphTitle = graphType.getName() + ": ";
} }
/** /**
@@ -140,22 +96,25 @@ public class BlockGraphTask extends Task {
@Override @Override
public void run(TaskMonitor monitor) throws CancelledException { public void run(TaskMonitor monitor) throws CancelledException {
this.graphScope = getGraphScopeAndGenerateGraphTitle(); this.graphScope = getGraphScopeAndGenerateGraphTitle();
AttributedGraph graph = createGraph(); AttributedGraph graph = createGraph(graphTitle);
monitor.setMessage("Generating Graph..."); monitor.setMessage("Generating Graph...");
try { try {
GraphDisplay display = graphProvider.getGraphDisplay(reuseGraph, monitor); GraphDisplay display =
graphProvider.getGraphDisplay(reuseGraph, monitor);
GraphDisplayOptions graphOptions = new ProgramGraphDisplayOptions(graphType, tool);
if (showCode) { // arrows need to be bigger as this generates larger vertices
graphOptions.setArrowLength(30);
}
BlockModelGraphDisplayListener listener = BlockModelGraphDisplayListener listener =
new BlockModelGraphDisplayListener(tool, blockModel, display); new BlockModelGraphDisplayListener(tool, blockModel, display);
addActions(display, v -> listener.getAddress(v)); addActions(display, v -> listener.getAddress(v));
display.setGraphDisplayListener(listener); display.setGraphDisplayListener(listener);
if (showCode) { if (showCode) {
display.defineVertexAttribute(CODE_ATTRIBUTE); graphOptions.setVertexLabelOverrideAttributeKey(CODE_ATTRIBUTE);
display.defineVertexAttribute(SYMBOLS_ATTRIBUTE);
display.setVertexLabelAttribute(CODE_ATTRIBUTE, GraphDisplay.ALIGN_LEFT, 12, true,
codeLimitPerBlock + 1);
} }
display.setGraph(graph, graphTitle, appendGraph, monitor); display.setGraph(graph, graphOptions, graphTitle, appendGraph, monitor);
if (location != null) { if (location != null) {
// initialize the graph location, but don't have the graph send an event // initialize the graph location, but don't have the graph send an event
@@ -217,9 +176,9 @@ public class BlockGraphTask extends Task {
codeLimitPerBlock = maxLines; codeLimitPerBlock = maxLines;
} }
protected AttributedGraph createGraph() throws CancelledException { protected AttributedGraph createGraph(String name) throws CancelledException {
int blockCount = 0; int blockCount = 0;
AttributedGraph graph = new AttributedGraph(); AttributedGraph graph = new AttributedGraph(name, graphType);
CodeBlockIterator it = getBlockIterator(); CodeBlockIterator it = getBlockIterator();
List<AttributedVertex> entryPoints = new ArrayList<>(); List<AttributedVertex> entryPoints = new ArrayList<>();
@@ -334,8 +293,7 @@ public class BlockGraphTask extends Task {
AttributedVertex entryNexusVertex = getEntryNexusVertex(graph); AttributedVertex entryNexusVertex = getEntryNexusVertex(graph);
for (AttributedVertex vertex : entries) { for (AttributedVertex vertex : entries) {
AttributedEdge edge = graph.addEdge(entryNexusVertex, vertex); AttributedEdge edge = graph.addEdge(entryNexusVertex, vertex);
edge.setAttribute("Name", edgeNames[ENTRY]); edge.setAttribute("EdgeType", ENTRY_NEXUS);
edge.setAttribute("EdgeType", edgeTypes[ENTRY]);
} }
} }
@@ -517,99 +475,36 @@ public class BlockGraphTask extends Task {
} }
protected void setEdgeAttributes(AttributedEdge edge, CodeBlockReference ref) { protected void setEdgeAttributes(AttributedEdge edge, CodeBlockReference ref) {
edge.setEdgeType(ProgramGraphType.getEdgeType(ref.getFlowType()));
int edgeType;
FlowType flowType = ref.getFlowType();
if (flowType == RefType.FALL_THROUGH) {
edgeType = FALLTHROUGH;
}
else if (flowType == RefType.UNCONDITIONAL_JUMP) {
edgeType = UNCONDITIONAL_JUMP;
}
else if (flowType == RefType.CONDITIONAL_JUMP) {
edgeType = CONDITIONAL_JUMP;
}
else if (flowType == RefType.UNCONDITIONAL_CALL) {
edgeType = UNCONDITIONAL_CALL;
}
else if (flowType == RefType.CONDITIONAL_CALL) {
edgeType = CONDITIONAL_CALL;
}
else if (flowType.isComputed()) {
edgeType = COMPUTED;
}
else if (flowType.isIndirect()) {
edgeType = INDIRECTION;
}
else if (flowType == RefType.TERMINATOR) {
edgeType = TERMINATOR;
}
else { // only FlowType.CONDITIONAL_TERMINATOR remains unchecked
edgeType = CONDITIONAL_RETURN;
}
// set attributes on this edge
edge.setAttribute("Name", edgeNames[edgeType]);
edge.setAttribute("EdgeType", edgeTypes[edgeType]);
} }
protected void setVertexAttributes(AttributedVertex vertex, CodeBlock bb, boolean isEntry) { protected void setVertexAttributes(AttributedVertex vertex, CodeBlock bb, boolean isEntry) {
String vertexType = BODY_NODE; String vertexType = BODY;
Address firstStartAddress = bb.getFirstStartAddress(); Address firstStartAddress = bb.getFirstStartAddress();
if (firstStartAddress.isExternalAddress()) { if (firstStartAddress.isExternalAddress()) {
vertexType = EXTERNAL_NODE; vertexType = EXTERNAL;
} }
else if (isEntry) { else if (isEntry) {
vertexType = ENTRY_NODE; vertexType = ENTRY;
} }
else { else {
FlowType flowType = bb.getFlowType(); FlowType flowType = bb.getFlowType();
if (flowType.isTerminal()) { if (flowType.isTerminal()) {
vertexType = EXIT_NODE; vertexType = EXIT;
} }
else if (flowType.isComputed()) { else if (flowType.isComputed()) {
vertexType = SWITCH_NODE; vertexType = SWITCH;
} }
else if (flowType == RefType.INDIRECTION) { else if (flowType == RefType.INDIRECTION) {
vertexType = DATA_NODE; vertexType = DATA;
} }
else if (flowType == RefType.INVALID) { else if (flowType == RefType.INVALID) {
vertexType = BAD_NODE; vertexType = BAD;
} }
} }
vertex.setVertexType(vertexType);
vertex.setAttribute("VertexType", vertexType);
setVertexColor(vertex, vertexType, firstStartAddress);
}
private void setVertexColor(AttributedVertex vertex, String vertexType, Address address) {
if (colorizingService == null) {
return;
}
Color color = colorizingService.getBackgroundColor(address);
if (color == null) {
return;
}
// color format: RGBrrrgggbbb
// -where rrr/ggg/bbb is a three digit int value for each respective color range
String rgb = "RGB" + HTMLUtilities.toRGBString(color);
vertex.setAttribute("Color", rgb); // sets the vertex color
// This value triggers the vertex to be painted with its color and not a
// while background.
if (showCode) {
// our own custom override of Labels/Icons
vertex.setAttribute("VertexType", "ColorFilled");
}
else {
// the default preferences for VertexType
vertex.setAttribute("VertexType", vertexType + ".Filled");
}
} }
private AttributedVertex getEntryNexusVertex(AttributedGraph graph) { private AttributedVertex getEntryNexusVertex(AttributedGraph graph) {
@@ -15,6 +15,8 @@
*/ */
package ghidra.graph.program; package ghidra.graph.program;
import ghidra.graph.DataFlowGraphType;
import ghidra.graph.ProgramGraphType;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Reference; import ghidra.program.model.symbol.Reference;
@@ -53,6 +55,7 @@ public class DataReferenceGraph extends AttributedGraph {
* @param depth the number of hops to graph per call (0 for recursion until no more hops) * @param depth the number of hops to graph per call (0 for recursion until no more hops)
*/ */
public DataReferenceGraph(Program program, int depth) { public DataReferenceGraph(Program program, int depth) {
super("Data Reference", new DataFlowGraphType());
this.program = program; this.program = program;
this.depthPerStep = depth; this.depthPerStep = depth;
} }
@@ -106,13 +109,14 @@ public class DataReferenceGraph extends AttributedGraph {
private void setupEdge(AttributedEdge edge, Reference ref) { private void setupEdge(AttributedEdge edge, Reference ref) {
edge.setAttribute(REF_SOURCE_ATTRIBUTE, ref.getSource().getDisplayString()); edge.setAttribute(REF_SOURCE_ATTRIBUTE, ref.getSource().getDisplayString());
edge.setAttribute(REF_TYPE_ATTRIBUTE, ref.getReferenceType().toString()); edge.setEdgeType(ProgramGraphType.getEdgeType(ref.getReferenceType()));
if (ref.getSymbolID() != -1) { if (ref.getSymbolID() != -1) {
edge.setAttribute(REF_SYMBOL_ATTRIBUTE, edge.setAttribute(REF_SYMBOL_ATTRIBUTE,
program.getSymbolTable().getSymbol(ref.getSymbolID()).getName()); program.getSymbolTable().getSymbol(ref.getSymbolID()).getName());
} }
} }
private void setupVertex(AttributedVertex vertex) { private void setupVertex(AttributedVertex vertex) {
Address address = Address address =
program.getAddressFactory().getAddress(vertex.getAttribute(ADDRESS_ATTRIBUTE)); program.getAddressFactory().getAddress(vertex.getAttribute(ADDRESS_ATTRIBUTE));
@@ -122,9 +126,13 @@ public class DataReferenceGraph extends AttributedGraph {
CodeUnit unit = program.getListing().getCodeUnitContaining(address); CodeUnit unit = program.getListing().getCodeUnitContaining(address);
if (unit instanceof Data) { if (unit instanceof Data) {
vertex.setAttribute(DATA_ATTRIBUTE, ((Data) unit).getBaseDataType().getName()); vertex.setAttribute(DATA_ATTRIBUTE, ((Data) unit).getBaseDataType().getName());
vertex.setVertexType(ProgramGraphType.DATA);
} }
else if (unit instanceof Instruction) { else if (unit instanceof Instruction) {
vertex.setAttribute("Icon", "TriangleDown"); vertex.setVertexType(ProgramGraphType.INSTRUCTION);
}
else {
vertex.setVertexType(ProgramGraphType.STACK);
} }
} }
@@ -17,6 +17,8 @@ package ghidra.graph.program;
import docking.widgets.EventTrigger; import docking.widgets.EventTrigger;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.graph.DataFlowGraphType;
import ghidra.graph.ProgramGraphDisplayOptions;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.CodeUnit; import ghidra.program.model.listing.CodeUnit;
@@ -35,6 +37,7 @@ import ghidra.util.task.TaskMonitor;
*/ */
public class DataReferenceGraphTask extends Task { public class DataReferenceGraphTask extends Task {
private static final String VERTEX_COLOR_OVERRIDE = "Color";
private String graphTitle; private String graphTitle;
private GraphDisplayProvider graphProvider; private GraphDisplayProvider graphProvider;
private boolean reuseGraph; private boolean reuseGraph;
@@ -117,7 +120,7 @@ public class DataReferenceGraphTask extends Task {
/* TODO /* TODO
* Want to make initial vertex easy to find, is this the best way? * Want to make initial vertex easy to find, is this the best way?
*/ */
centerVertex.setAttribute("Color", "Orange"); centerVertex.setAttribute(VERTEX_COLOR_OVERRIDE, "Orange");
} }
} }
catch (CancelledException e) { catch (CancelledException e) {
@@ -128,19 +131,16 @@ public class DataReferenceGraphTask extends Task {
try { try {
if (display == null) { if (display == null) {
display = graphProvider.getGraphDisplay(reuseGraph, monitor); display = graphProvider.getGraphDisplay(reuseGraph, monitor);
display.defineEdgeAttribute(DataReferenceGraph.REF_SOURCE_ATTRIBUTE);
display.defineEdgeAttribute(DataReferenceGraph.REF_TYPE_ATTRIBUTE);
display.defineEdgeAttribute(DataReferenceGraph.REF_SYMBOL_ATTRIBUTE);
display.defineVertexAttribute(DataReferenceGraph.DATA_ATTRIBUTE);
display.setVertexLabelAttribute(DataReferenceGraph.LABEL_ATTRIBUTE,
GraphDisplay.ALIGN_LEFT, 12, true, maxLabelLength);
DataReferenceGraphDisplayListener listener = DataReferenceGraphDisplayListener listener =
new DataReferenceGraphDisplayListener(tool, display, program, totalMaxDepth); new DataReferenceGraphDisplayListener(tool, display, program, totalMaxDepth);
display.setGraphDisplayListener(listener); display.setGraphDisplayListener(listener);
} }
GraphDisplayOptions graphDisplayOptions =
display.setGraph(graph, graphTitle, appendGraph, monitor); new ProgramGraphDisplayOptions(new DataFlowGraphType(), tool);
// set the vertex color override so that we can color "initial" vertices differently
graphDisplayOptions.setVertexColorOverrideAttributeKey(VERTEX_COLOR_OVERRIDE);
display.setGraph(graph, graphDisplayOptions, graphTitle, appendGraph, monitor);
if (location != null) { if (location != null) {
// initialize the graph location, but don't have the graph send an event // initialize the graph location, but don't have the graph send an event

Some files were not shown because too many files have changed in this diff Show More