From 210cc0bca02f0ed578167ba77d83e851326b78af Mon Sep 17 00:00:00 2001 From: ghidravore Date: Mon, 9 Aug 2021 13:18:23 -0400 Subject: [PATCH] Created the concept of graph types and display options for those graph types. --- .../objects/actions/DisplayAsGraphAction.java | 2 +- .../actions/DisplayFilteredGraphAction.java | 2 +- .../GenerateBrandesKopfGraphScript.java | 2 +- .../GenerateTestGraphScript.java | 2 +- .../ExampleGraphServiceScript.java | 2 +- .../ghidra_scripts/GraphClassesScript.java | 2 +- .../core/datamgr/actions/TypeGraphTask.java | 32 +- .../core/graph/GraphDisplayBrokerPlugin.java | 7 +- .../app/services/GraphDisplayBroker.java | 22 +- .../java/ghidra/graph/BlockFlowGraphType.java | 24 + .../main/java/ghidra/graph/CallGraphType.java | 24 + .../java/ghidra/graph/CodeFlowGraphType.java | 25 + .../java/ghidra/graph/DataFlowGraphType.java | 24 + .../graph/ProgramGraphDisplayOptions.java | 98 +++ .../java/ghidra/graph/ProgramGraphType.java | 126 +++ .../bean/opteditor/OptionsDialogTest.java | 77 +- .../Decompiler/ghidra_scripts/GraphAST.java | 97 +- .../ghidra_scripts/GraphASTAndFlow.java | 4 +- .../RecoverClassesFromRTTIScript.java | 2 +- .../actions/ASTGraphDisplayListener.java | 8 +- .../core/decompile/actions/ASTGraphTask.java | 91 +- .../core/decompile/actions/AstGraphType.java | 26 + .../actions/GraphASTControlFlowAction.java | 2 +- .../topics/GraphServices/GraphDisplay.htm | 35 + .../java/ghidra/graph/AttributeFilters.java | 2 +- .../export/ExportAttributedGraphDisplay.java | 25 +- .../graph/exporter/GraphMlGraphExporter.java | 4 +- .../visualization/AttributedToolTipInfo.java | 137 +++ .../ghidra/graph/visualization/Colors.java | 288 ------ .../visualization/DefaultGraphDisplay.java | 374 +++----- .../DefaultGraphDisplayProvider.java | 12 +- .../visualization/DefaultGraphRenderer.java | 326 +++++++ .../graph/visualization/EdgeComparator.java | 54 +- .../graph/visualization/GhidraIconCache.java | 210 ----- .../graph/visualization/GraphRenderer.java | 90 ++ .../graph/visualization/GroupVertex.java | 3 +- .../ghidra/graph/visualization/IconShape.java | 104 --- .../graph/visualization/LayoutFunction.java | 53 +- .../LayoutTransitionManager.java | 23 +- .../visualization/ProgramGraphFunctions.java | 140 --- .../visualization/mouse/JgtGraphMouse.java | 4 + .../src/main/resources/jungrapht.properties | 2 +- .../export/AttributedGraphExportersTest.java | 2 +- .../ghidra/graph/export/GraphExportTest.java | 2 +- .../graph/visualization/ColorsTest.java | 75 -- .../graph/visualization/IconShapeTest.java | 70 -- .../ProgramGraphPlugin/ProgramGraph.htm | 20 +- .../ghidra/graph/program/BlockGraphTask.java | 175 +--- .../graph/program/DataReferenceGraph.java | 12 +- .../graph/program/DataReferenceGraphTask.java | 18 +- .../graph/program/ProgramGraphPlugin.java | 24 +- .../feature/vt/gui/wizard/OptionsPanel.java | 37 +- .../options/editor/OptionsEditorPanel.java | 36 +- .../docking/options/editor/OptionsPanel.java | 6 +- .../editor/ScrollableOptionsEditor.java | 65 +- .../main/java/ghidra/util/HTMLUtilities.java | 11 +- .../src/main/java/ghidra/util/WebColors.java | 249 ++++++ .../test/java/ghidra/util/WebColorsTest.java | 65 ++ .../java/ghidra/service/graph/Attributed.java | 44 +- .../ghidra/service/graph/AttributedEdge.java | 18 + .../ghidra/service/graph/AttributedGraph.java | 61 +- .../service/graph/AttributedVertex.java | 24 +- .../graph/DefaultGraphDisplayOptions.java | 28 + .../ghidra/service/graph/EmptyGraphType.java | 30 + .../service/graph/GraphDisplayOptions.java | 831 ++++++++++++++++++ .../graph/GraphDisplayOptionsBuilder.java | 193 ++++ .../service/graph/GraphLabelPosition.java | 23 + .../java/ghidra/service/graph/GraphType.java | 140 +++ .../service/graph/GraphTypeBuilder.java | 76 ++ .../service/graph/LayoutAlgorithmNames.java | 56 ++ .../ghidra/service/graph/VertexShape.java | 339 +++++++ .../service/graph/AttributedGraphTest.java | 4 +- .../graph/GraphDisplayOptionsTest.java | 281 ++++++ .../service/graph/GraphTypeBuilderTest.java | 71 ++ .../ghidra/service/graph/GraphTypeTest.java | 80 ++ .../ghidra/service/graph/GraphDisplay.java | 48 +- .../service/graph/GraphDisplayProvider.java | 16 - .../screenshot/GraphServicesScreenShots.java | 15 +- .../java/ghidra/graph/GraphActionsTest.java | 2 +- .../datamgr/actions/TypeGraphTaskTest.java | 12 +- .../java/ghidra/graph/TestGraphDisplay.java | 30 +- .../graph/program/BlockGraphEventTest.java | 5 +- .../graph/program/BlockGraphTaskTest.java | 41 +- .../program/DataReferenceGraphTaskTest.java | 2 +- 84 files changed, 4102 insertions(+), 1822 deletions(-) create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/graph/BlockFlowGraphType.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/graph/CallGraphType.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/graph/CodeFlowGraphType.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/graph/DataFlowGraphType.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/graph/ProgramGraphDisplayOptions.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/graph/ProgramGraphType.java create mode 100644 Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/AstGraphType.java create mode 100644 Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/AttributedToolTipInfo.java delete mode 100644 Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/Colors.java create mode 100644 Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphRenderer.java delete mode 100644 Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraIconCache.java create mode 100644 Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GraphRenderer.java delete mode 100644 Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/IconShape.java delete mode 100644 Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/ProgramGraphFunctions.java delete mode 100644 Ghidra/Features/GraphServices/src/test/java/ghidra/graph/visualization/ColorsTest.java delete mode 100644 Ghidra/Features/GraphServices/src/test/java/ghidra/graph/visualization/IconShapeTest.java create mode 100644 Ghidra/Framework/Generic/src/main/java/ghidra/util/WebColors.java create mode 100644 Ghidra/Framework/Generic/src/test/java/ghidra/util/WebColorsTest.java create mode 100644 Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/DefaultGraphDisplayOptions.java create mode 100644 Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/EmptyGraphType.java create mode 100644 Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/GraphDisplayOptions.java create mode 100644 Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/GraphDisplayOptionsBuilder.java create mode 100644 Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/GraphLabelPosition.java create mode 100644 Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/GraphType.java create mode 100644 Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/GraphTypeBuilder.java create mode 100644 Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/LayoutAlgorithmNames.java create mode 100644 Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/VertexShape.java create mode 100644 Ghidra/Framework/Graph/src/test/java/ghidra/service/graph/GraphDisplayOptionsTest.java create mode 100644 Ghidra/Framework/Graph/src/test/java/ghidra/service/graph/GraphTypeBuilderTest.java create mode 100644 Ghidra/Framework/Graph/src/test/java/ghidra/service/graph/GraphTypeTest.java diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/actions/DisplayAsGraphAction.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/actions/DisplayAsGraphAction.java index 59be0d8132..94aab1d014 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/actions/DisplayAsGraphAction.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/actions/DisplayAsGraphAction.java @@ -63,7 +63,7 @@ public class DisplayAsGraphAction extends DisplayAsAction { public void addGraph(ObjectContainer container) { GraphDisplayProvider graphProvider = graphBroker.getDefaultGraphDisplayProvider(); - AttributedGraph graph = new AttributedGraph(); + AttributedGraph graph = new AttributedGraph(container.getName(), new EmptyGraphType()); AttributedVertex start = graph.addVertex(container.toString(), container.getName()); graphContainer(container, graph, start); try { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/actions/DisplayFilteredGraphAction.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/actions/DisplayFilteredGraphAction.java index 5222bd0e92..c6c1435bf1 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/actions/DisplayFilteredGraphAction.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/actions/DisplayFilteredGraphAction.java @@ -81,7 +81,7 @@ public class DisplayFilteredGraphAction extends DisplayFilteredAction { @Override protected void finishGetOffspring(ObjectContainer container, final List path) { GraphDisplayProvider graphProvider = graphBroker.getDefaultGraphDisplayProvider(); - AttributedGraph graph = new AttributedGraph(); + AttributedGraph graph = new AttributedGraph(container.getName(), new EmptyGraphType()); AttributedVertex start = graph.addVertex(container.getName(), container.toString()); graphContainer(container, graph, start); try { diff --git a/Ghidra/Features/Base/developer_scripts/GenerateBrandesKopfGraphScript.java b/Ghidra/Features/Base/developer_scripts/GenerateBrandesKopfGraphScript.java index d2b3a1edeb..004c7fcf5d 100644 --- a/Ghidra/Features/Base/developer_scripts/GenerateBrandesKopfGraphScript.java +++ b/Ghidra/Features/Base/developer_scripts/GenerateBrandesKopfGraphScript.java @@ -22,7 +22,7 @@ import ghidra.service.graph.*; * Script to generate graph to test BrandesKopf algorithm */ public class GenerateBrandesKopfGraphScript extends GhidraScript { - private AttributedGraph graph = new AttributedGraph(); + private AttributedGraph graph = new AttributedGraph("test", new EmptyGraphType()); private int nextEdgeID = 1; @Override diff --git a/Ghidra/Features/Base/developer_scripts/GenerateTestGraphScript.java b/Ghidra/Features/Base/developer_scripts/GenerateTestGraphScript.java index 2de9fb2dad..9f64c3f299 100644 --- a/Ghidra/Features/Base/developer_scripts/GenerateTestGraphScript.java +++ b/Ghidra/Features/Base/developer_scripts/GenerateTestGraphScript.java @@ -22,7 +22,7 @@ import ghidra.service.graph.*; * Sample script to test graph service */ public class GenerateTestGraphScript extends GhidraScript { - private AttributedGraph graph = new AttributedGraph(); + private AttributedGraph graph = new AttributedGraph("Test", new EmptyGraphType()); private int nextEdgeID = 1; @Override diff --git a/Ghidra/Features/Base/ghidra_scripts/ExampleGraphServiceScript.java b/Ghidra/Features/Base/ghidra_scripts/ExampleGraphServiceScript.java index 40909ad766..6530813dd6 100644 --- a/Ghidra/Features/Base/ghidra_scripts/ExampleGraphServiceScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/ExampleGraphServiceScript.java @@ -22,7 +22,7 @@ import ghidra.service.graph.*; * Example script for creating and displaying a graph in ghidra */ public class ExampleGraphServiceScript extends GhidraScript { - private AttributedGraph graph = new AttributedGraph(); + private AttributedGraph graph = new AttributedGraph("Test", new EmptyGraphType()); private int nextEdgeID = 1; @Override diff --git a/Ghidra/Features/Base/ghidra_scripts/GraphClassesScript.java b/Ghidra/Features/Base/ghidra_scripts/GraphClassesScript.java index cf88d951f9..ba9fcb0c4f 100644 --- a/Ghidra/Features/Base/ghidra_scripts/GraphClassesScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/GraphClassesScript.java @@ -107,7 +107,7 @@ public class GraphClassesScript extends GhidraScript { */ private AttributedGraph createGraph() throws Exception { - AttributedGraph g = new AttributedGraph(); + AttributedGraph g = new AttributedGraph("Test Graph", new EmptyGraphType()); for (Structure classStructure : classStructures) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/TypeGraphTask.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/TypeGraphTask.java index f962c01f26..2f82ea310d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/TypeGraphTask.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/TypeGraphTask.java @@ -15,6 +15,8 @@ */ package ghidra.app.plugin.core.datamgr.actions; +import java.awt.Color; + import ghidra.app.util.ToolTipUtils; import ghidra.program.model.data.*; import ghidra.service.graph.*; @@ -35,9 +37,8 @@ public class TypeGraphTask extends Task { private String graphTitle; private GraphDisplayProvider graphService; - public static final String TYPE_ATTRIBUTE = "Type"; - public static final String EMBEDDED = "Composite"; - public static final String POINTER = "Reference"; + public static final String COMPOSITE = "Composite"; + public static final String REFERENCE = "Reference"; /* * Constructor @@ -57,8 +58,18 @@ public class TypeGraphTask extends Task { @Override 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 { if (type instanceof Pointer) { recursePointer((Pointer) type, graph, null, monitor); @@ -75,7 +86,7 @@ public class TypeGraphTask extends Task { GraphDisplay display; try { display = graphService.getGraphDisplay(false, monitor); - display.setGraph(graph, graphTitle, false, monitor); + display.setGraph(graph, options, graphTitle, false, monitor); } catch (GraphException e) { Msg.showError(this, null, "Data Type Graph Error", @@ -94,11 +105,8 @@ public class TypeGraphTask extends Task { } else { AttributedEdge edge = graph.addEdge(lastVertex, newVertex); - if (edgeType == POINTER) { - edge.setAttribute("Color", "Blue"); - } - edge.setAttribute(TYPE_ATTRIBUTE, edgeType); - if (edge.hasAttribute("Weight")) { + edge.setEdgeType(edgeType); + if (edge.hasAttribute(AttributedGraph.WEIGHT)) { //did this already, don't cycle return; } @@ -115,7 +123,7 @@ public class TypeGraphTask extends Task { recursePointer((Pointer) dt, graph, newVertex, monitor); } 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); } else if (ptrType instanceof Composite) { - recurseComposite((Composite) ptrType, graph, lastVertex, POINTER, monitor); + recurseComposite((Composite) ptrType, graph, lastVertex, REFERENCE, monitor); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/graph/GraphDisplayBrokerPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/graph/GraphDisplayBrokerPlugin.java index 251002d61e..0a65a0f828 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/graph/GraphDisplayBrokerPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/graph/GraphDisplayBrokerPlugin.java @@ -148,10 +148,11 @@ public class GraphDisplayBrokerPlugin extends Plugin } @Override - public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, Map properties, - TaskMonitor monitor) throws GraphException { + public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, TaskMonitor monitor) + throws GraphException { + if (defaultGraphDisplayProvider != null) { - return defaultGraphDisplayProvider.getGraphDisplay(reuseGraph, properties, monitor); + return defaultGraphDisplayProvider.getGraphDisplay(reuseGraph, monitor); } return null; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/GraphDisplayBroker.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/GraphDisplayBroker.java index a3156f3047..47e41e8cad 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/GraphDisplayBroker.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/GraphDisplayBroker.java @@ -15,7 +15,7 @@ */ package ghidra.app.services; -import java.util.*; +import java.util.List; import ghidra.app.plugin.core.graph.GraphDisplayBrokerListener; 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. * @throws GraphException thrown if an error occurs trying to get a graph display */ - public default 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 - * - *

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 properties, - TaskMonitor monitor) + public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, TaskMonitor monitor) throws GraphException; /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/graph/BlockFlowGraphType.java b/Ghidra/Features/Base/src/main/java/ghidra/graph/BlockFlowGraphType.java new file mode 100644 index 0000000000..884bb627d4 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/graph/BlockFlowGraphType.java @@ -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"); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/graph/CallGraphType.java b/Ghidra/Features/Base/src/main/java/ghidra/graph/CallGraphType.java new file mode 100644 index 0000000000..5385acef9d --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/graph/CallGraphType.java @@ -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"); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/graph/CodeFlowGraphType.java b/Ghidra/Features/Base/src/main/java/ghidra/graph/CodeFlowGraphType.java new file mode 100644 index 0000000000..54ea3f7979 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/graph/CodeFlowGraphType.java @@ -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)"); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/graph/DataFlowGraphType.java b/Ghidra/Features/Base/src/main/java/ghidra/graph/DataFlowGraphType.java new file mode 100644 index 0000000000..2f01e419f1 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/graph/DataFlowGraphType.java @@ -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"); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/graph/ProgramGraphDisplayOptions.java b/Ghidra/Features/Base/src/main/java/ghidra/graph/ProgramGraphDisplayOptions.java new file mode 100644 index 0000000000..6fe3728ae1 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/graph/ProgramGraphDisplayOptions.java @@ -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); + + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/graph/ProgramGraphType.java b/Ghidra/Features/Base/src/main/java/ghidra/graph/ProgramGraphType.java new file mode 100644 index 0000000000..8a88c608bf --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/graph/ProgramGraphType.java @@ -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 refTypeToEdgeTypeMap = new HashMap<>(); + private static List vertexTypes = new ArrayList<>(); + private static List 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"; + } + +} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/util/bean/opteditor/OptionsDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/util/bean/opteditor/OptionsDialogTest.java index 285aa5ec23..0c535363a6 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/util/bean/opteditor/OptionsDialogTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/util/bean/opteditor/OptionsDialogTest.java @@ -160,11 +160,12 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest { // get the options panel ScrollableOptionsEditor simpleOptionsPanel = (ScrollableOptionsEditor) getEditorPanel(consoleNode); - assertNotNull(simpleOptionsPanel); - assertTrue(simpleOptionsPanel.isShowing()); + JComponent comp = simpleOptionsPanel.getComponent(); + assertNotNull(comp); + assertTrue(comp.isShowing()); String optionName = (String) getInstanceField("MAXIMUM_CHARACTERS_OPTION_NAME", textPane); - final Component component = findPairedComponent(simpleOptionsPanel, optionName); + final Component component = findPairedComponent(comp, optionName); assertNotNull(component); // click the option to toggle its state @@ -244,7 +245,8 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest { ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(parentNode); 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); assertNotNull(p); - assertNotNull(findPairedComponent(p, simpleName)); + assertNotNull(findPairedComponent(p.getComponent(), simpleName)); } } @@ -331,9 +333,10 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest { ScrollableOptionsEditor simpleOptionsPanel = (ScrollableOptionsEditor) getEditorPanel(toolNode); 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); Rectangle rect = component.getBounds(); clickMouse(component, 1, rect.x, rect.y, 2, 0); @@ -366,10 +369,11 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest { ScrollableOptionsEditor simpleOptionsPanel = (ScrollableOptionsEditor) getEditorPanel(buttonNode); assertNotNull(simpleOptionsPanel); - assertTrue(simpleOptionsPanel.isShowing()); + JComponent comp = simpleOptionsPanel.getComponent(); + assertTrue(comp.isShowing()); PropertySelector ps = - (PropertySelector) findPairedComponent(simpleOptionsPanel, "Mouse Button To Activate"); + (PropertySelector) findPairedComponent(comp, "Mouse Button To Activate"); assertNotNull(ps); runSwing(() -> ps.setSelectedIndex(0)); assertEquals("LEFT", ps.getSelectedItem()); @@ -513,10 +517,12 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest { ScrollableOptionsEditor simpleOptionsPanel = (ScrollableOptionsEditor) getEditorPanel(buttonNode); assertNotNull(simpleOptionsPanel); - assertTrue(simpleOptionsPanel.isShowing()); + JComponent comp = simpleOptionsPanel.getComponent(); + + assertTrue(comp.isShowing()); PropertySelector ps = - (PropertySelector) findPairedComponent(simpleOptionsPanel, "Mouse Button To Activate"); + (PropertySelector) findPairedComponent(comp, "Mouse Button To Activate"); // change to "LEFT" runSwing(() -> ps.setSelectedIndex(0)); @@ -545,11 +551,13 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest { ScrollableOptionsEditor simpleOptionsPanel = (ScrollableOptionsEditor) getEditorPanel(buttonNode); + assertNotNull(simpleOptionsPanel); - assertTrue(simpleOptionsPanel.isShowing()); + JComponent comp = simpleOptionsPanel.getComponent(); + assertTrue(comp.isShowing()); PropertySelector ps = - (PropertySelector) findPairedComponent(simpleOptionsPanel, "Mouse Button To Activate"); + (PropertySelector) findPairedComponent(comp, "Mouse Button To Activate"); // change to "LEFT" runSwing(() -> ps.setSelectedIndex(0)); @@ -592,15 +600,17 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest { ScrollableOptionsEditor p = (ScrollableOptionsEditor) getEditorPanel(testNode); 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); - field = (JTextField) findPairedComponent(p, "String Value 2"); + field = (JTextField) findPairedComponent(comp, "String Value 2"); assertNotNull(field); - field = (JTextField) findPairedComponent(p, "String Value 3"); + field = (JTextField) findPairedComponent(comp, "String Value 3"); assertNotNull(field); - field = (JTextField) findPairedComponent(p, "Int Value"); + field = (JTextField) findPairedComponent(comp, "Int Value"); assertNotNull(field); } @@ -614,9 +624,10 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest { ScrollableOptionsEditor simpleOptionsPanel = (ScrollableOptionsEditor) getEditorPanel(toolNode); 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); Rectangle rect = component.getBounds(); clickMouse(component, 1, rect.x, rect.y, 2, 0); @@ -659,9 +670,10 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest { ScrollableOptionsEditor simpleOptionsPanel = (ScrollableOptionsEditor) getEditorPanel(toolNode); 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); Rectangle rect = canvas.getBounds(); 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) { - Component comp = findPairedComponent(editor, optionName); + Component comp = findPairedComponent(editor.getComponent(), optionName); assertNotNull(comp); AbstractButton button = findAbstractButtonByName((Container) comp, "BrowseButton"); assertNotNull(button); @@ -856,7 +868,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest { } private JTextField getEditorTextField(ScrollableOptionsEditor editor, String optionName) { - Component comp = findPairedComponent(editor, optionName); + Component comp = findPairedComponent(editor.getComponent(), optionName); assertNotNull(comp); JTextField tf = findComponent((Container) comp, JTextField.class); @@ -877,7 +889,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest { ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(toolNode); assertNotNull(editor); - assertTrue(editor.isShowing()); + assertTrue(editor.getComponent().isShowing()); return editor; } @@ -905,7 +917,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest { ScrollableOptionsEditor editor = selectSubNodeWithDefaultEditor(parentNodeName, childNodeName); - JCheckBox checkBox = (JCheckBox) findPairedComponent(editor, optionName); + JCheckBox checkBox = (JCheckBox) findPairedComponent(editor.getComponent(), optionName); return checkBox.isSelected(); } @@ -914,7 +926,8 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest { ScrollableOptionsEditor editor = selectSubNodeWithDefaultEditor(parentNodeName, childNodeName); - final JCheckBox checkBox = (JCheckBox) findPairedComponent(editor, optionName); + final JCheckBox checkBox = + (JCheckBox) findPairedComponent(editor.getComponent(), optionName); runSwing(() -> checkBox.setSelected(newValue)); assertEquals(newValue, checkBox.isSelected()); } @@ -923,7 +936,8 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest { throws Exception { ScrollableOptionsEditor editor = selectNodeWithDefaultEditor(parentNodeName); - JTextField textField = (JTextField) findPairedComponent(editor, childNodeName); + JTextField textField = + (JTextField) findPairedComponent(editor.getComponent(), childNodeName); return getText(textField); } @@ -931,7 +945,8 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest { String newValue) throws Exception { ScrollableOptionsEditor editor = selectNodeWithDefaultEditor(parentNodeName); - JTextField textField = (JTextField) findPairedComponent(editor, childNodeName); + JTextField textField = + (JTextField) findPairedComponent(editor.getComponent(), childNodeName); setText(textField, newValue); String updatedText = getText(textField); @@ -952,7 +967,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest { ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(node); assertNotNull(editor); - assertTrue(editor.isShowing()); + assertTrue(editor.getComponent().isShowing()); return editor; } @@ -975,7 +990,7 @@ public class OptionsDialogTest extends AbstractGhidraHeadedIntegrationTest { ScrollableOptionsEditor editor = (ScrollableOptionsEditor) getEditorPanel(childNode); assertNotNull(editor); - assertTrue(editor.isShowing()); + assertTrue(editor.getComponent().isShowing()); return editor; } diff --git a/Ghidra/Features/Decompiler/ghidra_scripts/GraphAST.java b/Ghidra/Features/Decompiler/ghidra_scripts/GraphAST.java index aa610ac733..6ede310cb3 100644 --- a/Ghidra/Features/Decompiler/ghidra_scripts/GraphAST.java +++ b/Ghidra/Features/Decompiler/ghidra_scripts/GraphAST.java @@ -30,13 +30,21 @@ import ghidra.program.model.listing.Program; import ghidra.program.model.pcode.*; import ghidra.service.graph.*; import ghidra.util.Msg; -import java.util.*; -import static ghidra.service.graph.GraphDisplay.*; +import ghidra.util.WebColors; public class GraphAST extends GhidraScript { - protected static final String COLOR_ATTRIBUTE = "Color"; - protected static final String ICON_ATTRIBUTE = "Icon"; + private static final String SHAPE_ATTRIBUTE = "Shape"; + 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 AttributedGraph graph; protected HighFunction high; @@ -63,24 +71,48 @@ public class GraphAST extends GhidraScript { 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(); - Map properties = new HashMap<>(); - 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 graphDisplay = graphDisplayBroker.getDefaultGraphDisplay(false, monitor); - 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 graphDisplay.setGraphDisplayListener( @@ -125,34 +157,33 @@ public class GraphAST extends GhidraScript { protected AttributedVertex createVarnodeVertex(VarnodeAST vn) { String name = vn.getAddress().toString(true); String id = getVarnodeKey(vn); - String colorattrib = "Red"; + String vertexType = DEFAULT; if (vn.isConstant()) { - colorattrib = "DarkGreen"; + vertexType = CONSTANT; } else if (vn.isRegister()) { - colorattrib = "Blue"; + vertexType = REGISTER; Register reg = func.getProgram().getRegister(vn.getAddress(), vn.getSize()); if (reg != null) { name = reg.getName(); } } else if (vn.isUnique()) { - colorattrib = "Black"; + vertexType = UNIQUE; } else if (vn.isPersistent()) { - colorattrib = "DarkOrange"; + vertexType = PERSISTENT; } else if (vn.isAddrTied()) { - colorattrib = "Orange"; + vertexType = ADDRESS_TIED; } AttributedVertex vert = graph.addVertex(id, name); + vert.setVertexType(vertexType); + + // if it is an input override the shape to be a triangle 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; } @@ -176,7 +207,7 @@ public class GraphAST extends GhidraScript { } } AttributedVertex vert = graph.addVertex(id, name); - vert.setAttribute(ICON_ATTRIBUTE, "Square"); + vert.setVertexType(OP); return vert; } @@ -192,7 +223,9 @@ public class GraphAST extends GhidraScript { } 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() { diff --git a/Ghidra/Features/Decompiler/ghidra_scripts/GraphASTAndFlow.java b/Ghidra/Features/Decompiler/ghidra_scripts/GraphASTAndFlow.java index 64b45e3f57..6d96408040 100644 --- a/Ghidra/Features/Decompiler/ghidra_scripts/GraphASTAndFlow.java +++ b/Ghidra/Features/Decompiler/ghidra_scripts/GraphASTAndFlow.java @@ -77,7 +77,7 @@ public class GraphASTAndFlow extends GraphAST { } if (prev != null && map.containsKey(prev) && map.containsKey(next)) { AttributedEdge edge = createEdge(map.get(prev), map.get(next)); - edge.setAttribute(COLOR_ATTRIBUTE, "Black"); + edge.setEdgeType(TYPE_OUTPUT); } prev = next; } @@ -92,7 +92,7 @@ public class GraphASTAndFlow extends GraphAST { PcodeBlock in = block.getIn(i); if (last.containsKey(in)) { AttributedEdge edge = createEdge(last.get(in), first.get(block)); - edge.setAttribute(COLOR_ATTRIBUTE, "Red"); + edge.setEdgeType(TYPE_INPUT); } } } diff --git a/Ghidra/Features/Decompiler/ghidra_scripts/RecoverClassesFromRTTIScript.java b/Ghidra/Features/Decompiler/ghidra_scripts/RecoverClassesFromRTTIScript.java index 17f2fa611b..96be46375a 100644 --- a/Ghidra/Features/Decompiler/ghidra_scripts/RecoverClassesFromRTTIScript.java +++ b/Ghidra/Features/Decompiler/ghidra_scripts/RecoverClassesFromRTTIScript.java @@ -334,7 +334,7 @@ public class RecoverClassesFromRTTIScript extends GhidraScript { private AttributedGraph createGraph(List recoveredClasses) throws CancelledException { - AttributedGraph g = new AttributedGraph(); + AttributedGraph g = new AttributedGraph("Test Graph", new EmptyGraphType()); Iterator recoveredClassIterator = recoveredClasses.iterator(); while (recoveredClassIterator.hasNext()) { diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ASTGraphDisplayListener.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ASTGraphDisplayListener.java index 26d207076d..c8cecbf527 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ASTGraphDisplayListener.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ASTGraphDisplayListener.java @@ -15,11 +15,11 @@ */ 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 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.framework.plugintool.PluginTool; import ghidra.program.model.address.*; @@ -33,10 +33,10 @@ import ghidra.util.exception.AssertException; */ public class ASTGraphDisplayListener extends AddressBasedGraphDisplayListener { private HighFunction hfunction; - private GraphType graphType; + private AstGraphSubType graphType; ASTGraphDisplayListener(PluginTool tool, GraphDisplay display, HighFunction hfunction, - GraphType graphType) { + AstGraphSubType graphType) { super(tool, hfunction.getFunction().getProgram(), display); this.hfunction = hfunction; this.graphType = graphType; diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ASTGraphTask.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ASTGraphTask.java index 0a6a4e7db3..48d1826352 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ASTGraphTask.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/ASTGraphTask.java @@ -15,13 +15,15 @@ */ package ghidra.app.plugin.core.decompile.actions; -import java.util.HashMap; +import static ghidra.graph.ProgramGraphType.*; + import java.util.Iterator; -import java.util.Map; import docking.widgets.EventTrigger; import ghidra.app.services.GraphDisplayBroker; import ghidra.framework.plugintool.PluginTool; +import ghidra.graph.ProgramGraphDisplayOptions; +import ghidra.graph.ProgramGraphType; import ghidra.program.model.address.Address; import ghidra.program.model.lang.Register; import ghidra.program.model.listing.Program; @@ -34,14 +36,12 @@ import ghidra.util.exception.GraphException; import ghidra.util.task.Task; import ghidra.util.task.TaskMonitor; -import static ghidra.service.graph.GraphDisplay.*; - public class ASTGraphTask extends Task { - enum GraphType { + enum AstGraphSubType { CONTROL_FLOW_GRAPH("AST Control Flow"), DATA_FLOW_GRAPH("AST Data Flow"); private String name; - GraphType(String name) { + AstGraphSubType(String name) { this.name = name; } @@ -51,35 +51,19 @@ public class ASTGraphTask extends Task { } 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 boolean newGraph; private int codeLimitPerBlock; private Address location; private HighFunction hfunction; - private GraphType graphType; + private AstGraphSubType astGraphType; private int uniqueNum = 0; private PluginTool tool; 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); this.graphService = graphService; @@ -87,32 +71,30 @@ public class ASTGraphTask extends Task { this.codeLimitPerBlock = codeLimitPerBlock; this.location = location; this.hfunction = hfunction; - this.graphType = graphType; + this.astGraphType = graphType; this.tool = tool; } @Override public void run(TaskMonitor monitor) { + GraphType graphType = new AstGraphType(); // get a new graph - AttributedGraph graph = new AttributedGraph(); - + AttributedGraph graph = new AttributedGraph(astGraphType.getName(), graphType); try { monitor.setMessage("Computing Graph..."); - if (graphType == GraphType.DATA_FLOW_GRAPH) { + if (astGraphType == AstGraphSubType.DATA_FLOW_GRAPH) { createDataFlowGraph(graph, monitor); } else { createControlFlowGraph(graph, monitor); } - Map properties = new HashMap<>(); - properties.put(SELECTED_VERTEX_COLOR, "0xFF1493"); - properties.put(SELECTED_EDGE_COLOR, "0xFF1493"); - properties.put(INITIAL_LAYOUT_ALGORITHM, "Hierarchical MinCross Coffman Graham"); - properties.put(ENABLE_EDGE_SELECTION, "true"); - GraphDisplay display = graphService.getDefaultGraphDisplay(!newGraph, properties, monitor); + + GraphDisplay display = + graphService.getDefaultGraphDisplay(!newGraph, monitor); + ASTGraphDisplayListener displayListener = - new ASTGraphDisplayListener(tool, display, hfunction, graphType); + new ASTGraphDisplayListener(tool, display, hfunction, astGraphType); display.setGraphDisplayListener(displayListener); monitor.setMessage("Obtaining handle to graph provider..."); @@ -122,20 +104,19 @@ public class ASTGraphTask extends Task { monitor.setCancelEnabled(false); 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 = - 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(); - 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); } 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) { return; @@ -230,20 +211,20 @@ public class ASTGraphTask extends Task { vertex.setAttribute(CODE_ATTRIBUTE, formatOpMnemonic(op)); - String vertexType = BODY_NODE; + String vertexType = BODY; switch (op.getOpcode()) { case PcodeOp.BRANCH: case PcodeOp.BRANCHIND: case PcodeOp.CBRANCH: case PcodeOp.CALL: case PcodeOp.CALLIND: - vertexType = SWITCH_NODE; + vertexType = SWITCH; break; case PcodeOp.RETURN: - vertexType = EXIT_NODE; + vertexType = EXIT; break; } - vertex.setAttribute(VERTEX_TYPE_ATTRIBUTE, vertexType); + vertex.setVertexType(vertexType); } private AttributedVertex getDataVertex(AttributedGraph graph, Varnode node, @@ -278,7 +259,7 @@ public class ASTGraphTask extends Task { } label += translateVarnode(node, false); vertex.setAttribute(CODE_ATTRIBUTE, label); - vertex.setAttribute(VERTEX_TYPE_ATTRIBUTE, DATA_NODE); + vertex.setVertexType(ProgramGraphType.DATA); } protected void createControlFlowGraph(AttributedGraph graph, TaskMonitor monitor) @@ -322,7 +303,7 @@ public class ASTGraphTask extends Task { } else { vertex.setAttribute(CODE_ATTRIBUTE, ""); - vertex.setAttribute(VERTEX_TYPE_ATTRIBUTE, BAD_NODE); + vertex.setVertexType(BAD); } } return vertex; @@ -348,23 +329,23 @@ public class ASTGraphTask extends Task { vertex.setAttribute(CODE_ATTRIBUTE, buf.toString()); // Establish vertex type - String vertexType = BODY_NODE; + String vertexType = BODY; if (basicBlk.getInSize() == 0) { - vertexType = ENTRY_NODE; + vertexType = ENTRY; } else { switch (basicBlk.getOutSize()) { case 0: - vertexType = EXIT_NODE; + vertexType = EXIT; break; case 1: - vertexType = BODY_NODE; + vertexType = BODY; break; default: - vertexType = SWITCH_NODE; + vertexType = SWITCH; } } - vertex.setAttribute(VERTEX_TYPE_ATTRIBUTE, vertexType); + vertex.setVertexType(vertexType); } private String formatOpMnemonic(PcodeOp op) { diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/AstGraphType.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/AstGraphType.java new file mode 100644 index 0000000000..1d60cef4cd --- /dev/null +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/AstGraphType.java @@ -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"); + } + +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/GraphASTControlFlowAction.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/GraphASTControlFlowAction.java index 67c647eaf8..202dac3eb6 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/GraphASTControlFlowAction.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/GraphASTControlFlowAction.java @@ -15,7 +15,7 @@ */ 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 ghidra.app.plugin.core.decompile.DecompilerActionContext; diff --git a/Ghidra/Features/GraphServices/src/main/help/help/topics/GraphServices/GraphDisplay.htm b/Ghidra/Features/GraphServices/src/main/help/help/topics/GraphServices/GraphDisplay.htm index fc4b513635..c752ad90a6 100644 --- a/Ghidra/Features/GraphServices/src/main/help/help/topics/GraphServices/GraphDisplay.htm +++ b/Ghidra/Features/GraphServices/src/main/help/help/topics/GraphServices/GraphDisplay.htm @@ -248,6 +248,41 @@ +

Graph Type Display Options

+ +
+

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:

+ +

Edge Colors

+
+

Allows setting the color for each edge type. Each Edge type will be listed with its + current color.

+
+

Miscellaneous

+
    +
  • Default Vertex Color - color for vertices with no defined vertex type
  • +
  • Default Vertex Shape - shape for vertices with no defined vertex type
  • +
  • Default Edge Color - color for edges with no defined edge type
  • +
  • Favored Edge - edge type to be favored by graph layout algorithms
  • +
+ +

Vertex Colors

+
+

Allows setting the color for each vertex type. Each vertex type will be listed with + its current color.

+
+

Vertex Shapes

+
+

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.

+
+
+ + +

Provided By:  GraphDisplayBrokerPlugin

Related Topics:

diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/AttributeFilters.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/AttributeFilters.java index 4d174ffbad..8112de9a9c 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/AttributeFilters.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/AttributeFilters.java @@ -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) for (Attributed element : elements) { - Map attributeMap = new HashMap<>(element.getAttributeMap()); + Map attributeMap = new HashMap<>(element.getAttributes()); for (Map.Entry entry : attributeMap.entrySet()) { if (!precludedNames.contains(entry.getKey())) { multiset.add(entry.getValue()); diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplay.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplay.java index 85b415c096..5683faeacb 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplay.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplay.java @@ -24,6 +24,7 @@ import docking.widgets.EventTrigger; import ghidra.app.services.GraphDisplayBroker; import ghidra.framework.plugintool.PluginTool; import ghidra.service.graph.*; +import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; /** @@ -77,23 +78,6 @@ class ExportAttributedGraphDisplay implements GraphDisplay { 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 public void setGraph(AttributedGraph graph, String title, boolean append, TaskMonitor monitor) { @@ -102,6 +86,12 @@ class ExportAttributedGraphDisplay implements GraphDisplay { 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} */ @@ -149,5 +139,4 @@ class ExportAttributedGraphDisplay implements GraphDisplay { public void selectVertices(Set vertexList, EventTrigger eventTrigger) { // not interactive, so N/A } - } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/exporter/GraphMlGraphExporter.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/exporter/GraphMlGraphExporter.java index 2b5826ca48..f81549b4d5 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/exporter/GraphMlGraphExporter.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/exporter/GraphMlGraphExporter.java @@ -50,13 +50,13 @@ public class GraphMlGraphExporter extends AbstractAttributedGraphExporter { entry -> new DefaultAttribute<>(entry.getValue(), AttributeType.STRING)))); graph.vertexSet().stream() - .map(Attributed::getAttributeMap) + .map(Attributed::getAttributes) .flatMap(m -> m.entrySet().stream()) .map(Map.Entry::getKey) .forEach(key -> exporter.registerAttribute(key, GraphMLExporter.AttributeCategory.NODE, AttributeType.STRING)); graph.edgeSet().stream() - .map(Attributed::getAttributeMap) + .map(Attributed::getAttributes) .flatMap(m -> m.entrySet().stream()) .map(Map.Entry::getKey) .forEach(key -> exporter.registerAttribute(key, GraphMLExporter.AttributeCategory.EDGE, AttributeType.STRING)); diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/AttributedToolTipInfo.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/AttributedToolTipInfo.java new file mode 100644 index 0000000000..5d7374c168 --- /dev/null +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/AttributedToolTipInfo.java @@ -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 { + + 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(""); + + 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("

"); + buf.append(vertex.getName()); + if (vertexType != null) { + buf.append("
"); + buf.append("Type:  " + vertexType); + } + buf.append("

"); + + 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("

"); + buf.append("Type:  " + edgeType); + buf.append("

"); + } + addAttributes(buf, AttributedEdge.EDGE_TYPE_KEY); + } + + private void addAttributes(StringBuilder buf, String...excludedKeys) { + + Set> entries = graphObject.entrySet(); + + for (Map.Entry 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("
", Splitter.on('\n').split(value)); + split = split.replaceAll("\\s", " "); + buf.append(split); + buf.append("
"); + } + } + +} diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/Colors.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/Colors.java deleted file mode 100644 index 9153a8a54f..0000000000 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/Colors.java +++ /dev/null @@ -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 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 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 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. - *

- * 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 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; - } -} diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java index 61c07b36d2..fbcaa49e77 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplay.java @@ -16,46 +16,32 @@ package ghidra.graph.visualization; import static org.jungrapht.visualization.MultiLayerTransformer.Layer.*; -import static org.jungrapht.visualization.renderers.BiModalRenderer.*; import java.awt.*; -import java.awt.Dimension; import java.awt.event.*; import java.awt.geom.Point2D; import java.util.*; import java.util.List; -import java.util.function.Function; import java.util.function.Predicate; -import java.util.logging.Logger; import java.util.stream.Collectors; import javax.swing.*; -import javax.swing.event.AncestorEvent; -import javax.swing.event.AncestorListener; +import javax.swing.event.*; -import org.apache.commons.lang3.StringUtils; import org.jgrapht.Graph; import org.jgrapht.graph.AsSubgraph; import org.jungrapht.visualization.*; -import org.jungrapht.visualization.annotations.MultiSelectedVertexPaintable; -import org.jungrapht.visualization.annotations.SelectedEdgePaintable; -import org.jungrapht.visualization.annotations.SingleSelectedVertexPaintable; +import org.jungrapht.visualization.annotations.*; import org.jungrapht.visualization.control.*; -import org.jungrapht.visualization.decorators.*; import org.jungrapht.visualization.layout.algorithms.LayoutAlgorithm; import org.jungrapht.visualization.layout.algorithms.util.InitialDimensionFunction; import org.jungrapht.visualization.layout.model.LayoutModel; import org.jungrapht.visualization.layout.model.Point; -import org.jungrapht.visualization.layout.model.Rectangle; -import org.jungrapht.visualization.renderers.*; -import org.jungrapht.visualization.renderers.Renderer; -import org.jungrapht.visualization.renderers.Renderer.VertexLabel; import org.jungrapht.visualization.renderers.Renderer.VertexLabel.Position; import org.jungrapht.visualization.selection.MutableSelectedState; import org.jungrapht.visualization.transform.*; import org.jungrapht.visualization.transform.shape.MagnifyImageLensSupport; import org.jungrapht.visualization.transform.shape.MagnifyShapeTransformer; -import org.jungrapht.visualization.util.RectangleUtils; import docking.ActionContext; import docking.DockingActionProxy; @@ -63,10 +49,15 @@ import docking.action.DockingActionIf; import docking.action.ToggleDockingAction; import docking.action.builder.*; import docking.menu.ActionState; +import docking.menu.MultiStateDockingAction; +import docking.options.editor.OptionsDialog; import docking.widgets.EventTrigger; import docking.widgets.OptionDialog; import generic.util.WindowUtilities; +import ghidra.framework.options.Options; +import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.PluginTool; +import ghidra.framework.plugintool.util.OptionsService; import ghidra.graph.AttributeFilters; import ghidra.graph.job.GraphJobRunner; import ghidra.graph.viewer.popup.*; @@ -107,29 +98,16 @@ public class DefaultGraphDisplay implements GraphDisplay { private static final Dimension PREFERRED_VIEW_SIZE = new Dimension(1000, 1000); private static final Dimension PREFERRED_LAYOUT_SIZE = new Dimension(3000, 3000); - private Logger log = Logger.getLogger(DefaultGraphDisplay.class.getName()); + // layout algorithm categories + static final String MIN_CROSS = "Hierarchical MinCross"; + static final String VERT_MIN_CROSS = "Vertical Hierarchical MinCross"; - private Map displayProperties; private Set addedActions = new LinkedHashSet<>(); private GraphDisplayListener listener = new DummyGraphDisplayListener(); private String title; private AttributedGraph graph; - private static String DEFAULT_EDGE_TYPE_PRIORITY_LIST = - "Fall-Through,"+ - "Conditional-Return,"+ - "Unconditional-Jump,"+ - "Conditional-Jump,"+ - "Unconditional-Call,"+ - "Conditional-Call,"+ - "Terminator,"+ - "Computed,"+ - "Indirection,"+ - "Entry"; - - private static String DEFAULT_FAVORED_EDGES = "Fall-Through"; - /** * a unique id for this {@link GraphDisplay} */ @@ -143,7 +121,7 @@ public class DefaultGraphDisplay implements GraphDisplay { /** * The {@link PluginTool} */ - private final PluginTool pluginTool; + private final PluginTool tool; private final DefaultGraphDisplayComponentProvider componentProvider; @@ -182,13 +160,13 @@ public class DefaultGraphDisplay implements GraphDisplay { private AttributeFilters edgeFilters; private AttributeFilters vertexFilters; - private GhidraIconCache iconCache; + private GraphRenderer graphRenderer = new DefaultGraphRenderer(); /** * Multi-selection is done in a free-form traced shape instead of a rectangle */ private boolean freeFormSelection; - + /** * Handles all mouse interaction */ @@ -209,31 +187,32 @@ public class DefaultGraphDisplay implements GraphDisplay { private SelectedEdgePaintable selectedEdgePaintable; + private GraphDisplayOptions graphDisplayOptions = GraphDisplayOptions.DEFAULT; + + private ChangeListener graphDisplayOptionsChangeListener; + + private MultiStateDockingAction layoutAction; + /** * Create the initial display, the graph-less visualization viewer, and its controls * @param displayProvider provides a {@link PluginTool} for Docking features - * @param displayProperties graph properties that will override the default graph properties * @param id the unique display id */ - DefaultGraphDisplay(DefaultGraphDisplayProvider displayProvider, - Map displayProperties, int id) { + DefaultGraphDisplay(DefaultGraphDisplayProvider displayProvider, int id) { this.graphDisplayProvider = displayProvider; this.displayId = id; - this.pluginTool = graphDisplayProvider.getPluginTool(); - this.displayProperties = displayProperties; + this.tool = graphDisplayProvider.getPluginTool(); this.viewer = createViewer(); buildHighlighers(); - componentProvider = new DefaultGraphDisplayComponentProvider(this, pluginTool); + componentProvider = new DefaultGraphDisplayComponentProvider(this, tool); componentProvider.addToTool(); satelliteViewer = createSatelliteViewer(viewer); if (graphDisplayProvider.getDefaultSatelliteState()) { viewer.getComponent().add(satelliteViewer.getComponent()); } layoutTransitionManager = - new LayoutTransitionManager(viewer, this::isRoot, - getEdgeTypePriorityList(), - getFavoredEdgePredicate()); + new LayoutTransitionManager(viewer, this::isRoot, graphRenderer); viewer.getComponent().addComponentListener(new ComponentAdapter() { @Override @@ -254,29 +233,26 @@ public class DefaultGraphDisplay implements GraphDisplay { createToolbarActions(); createPopupActions(); connectSelectionStateListeners(); + graphDisplayOptionsChangeListener = e -> refreshViewer(); + } + + private void refreshViewer() { + graphRenderer.clearCache(); + + graphRenderer.initializeViewer(viewer); + + // bug in jungraphT library where vertex selection color doesn't update, but edge selection + // color does, so just rebuild the highlighter + buildHighlighers(); + viewer.repaint(); } private Color getSelectedVertexColor() { - String property = displayProperties.getOrDefault(SELECTED_VERTEX_COLOR, "0xFF0000"); - return Colors.getHexColor(property); + return graphRenderer.getVertexSelectionColor(); } private Color getSelectedEdgeColor() { - String property = displayProperties.getOrDefault(SELECTED_EDGE_COLOR, "0xFF0000"); - return Colors.getHexColor(property); - } - - private List getEdgeTypePriorityList() { - return Arrays.asList(displayProperties - .getOrDefault(EDGE_TYPE_PRIORITY_LIST, DEFAULT_EDGE_TYPE_PRIORITY_LIST) - .split(",")); - } - - private Predicate getFavoredEdgePredicate() { - String[] favoredEdges = displayProperties.getOrDefault(FAVORED_EDGES, DEFAULT_FAVORED_EDGES) - .split(","); - return attributedEdge -> Arrays.stream(favoredEdges) - .anyMatch(s -> s.equals(attributedEdge.getAttribute("EdgeType"))); + return graphRenderer.getEdgeSelectionColor(); } JComponent getComponent() { @@ -420,7 +396,7 @@ public class DefaultGraphDisplay implements GraphDisplay { // create a menu with graph layout algorithm selections List> layoutActionStates = getLayoutActionStates(); - new MultiStateActionBuilder("Arrangement", ACTION_OWNER) + layoutAction = new MultiStateActionBuilder("Arrangement", ACTION_OWNER) .description("Arrangement: " + layoutActionStates.get(0).getName()) .toolBarIcon(DefaultDisplayGraphIcons.LAYOUT_ALGORITHM_ICON) .useCheckboxForIcons(true) @@ -565,6 +541,14 @@ public class DefaultGraphDisplay implements GraphDisplay { .onAction(c -> ungroupSelectedVertices()) .buildAndInstallLocal(componentProvider); + new ActionBuilder("Graph Type Display Options", ACTION_OWNER) + .popupMenuPath("Graph Type Options ...") + .popupMenuGroup("zzz") + .menuPath("Graph Type Options ...") + .description("Brings up option editor for configuring vertex and edge types.") + .onAction(c -> editGraphDisplayOptions()) + .buildAndInstallLocal(componentProvider); + togglePopupsAction = new ToggleActionBuilder("Display Popup Windows", ACTION_OWNER) .popupMenuPath("Display Popup Windows") .popupMenuGroup("zz", "1") @@ -576,6 +560,32 @@ public class DefaultGraphDisplay implements GraphDisplay { } + private void editGraphDisplayOptions() { + String rootOptionsName = graphDisplayOptions.getRootOptionsName(); + String relativePath = rootOptionsName + ".Vertex Colors"; + + // if the options are registered in the tool options, just show them + // otherwise, create a transient options and create an options dialog. This will + // allow the user to edit the options for the current graph instance. + + if (graphDisplayOptions.isRegisteredWithTool()) { + OptionsService service = tool.getService(OptionsService.class); + service.showOptionsDialog("Graph." + relativePath, ""); + } + else { + ToolOptions transientOptions = new ToolOptions("Graph"); + HelpLocation help = new HelpLocation("GraphServices", "Graph Type Display Options"); + graphDisplayOptions.registerOptions(transientOptions, help); + transientOptions.addOptionsChangeListener(graphDisplayOptions); + Options[] optionsArray = new Options[] { transientOptions }; + String dialogTitle = "Graph Instance Settings (Not Saved in Tool Options)"; + OptionsDialog dialog = new OptionsDialog(dialogTitle, "Graph", optionsArray, null); + // we have one less level for these transient tool options, so no need to prepend "graph." + dialog.displayCategory(relativePath, ""); + tool.showDialog(dialog, componentProvider); + } + } + /** * Group the selected vertices into one vertex that represents them all */ @@ -629,7 +639,8 @@ public class DefaultGraphDisplay implements GraphDisplay { private void createAndDisplaySubGraph() { GraphDisplay display = graphDisplayProvider.getGraphDisplay(false, TaskMonitor.DUMMY); try { - display.setGraph(createSubGraph(), title + " - Sub-graph", false, TaskMonitor.DUMMY); + display.setGraph(createSubGraph(), graphRenderer.getGraphDisplayOptions(), + title + " - Sub-graph", false, TaskMonitor.DUMMY); display.setGraphDisplayListener(listener.cloneWith(display)); copyActionsToNewGraph((DefaultGraphDisplay) display); } @@ -642,7 +653,8 @@ public class DefaultGraphDisplay implements GraphDisplay { Set selected = viewer.getSelectedVertices(); Graph subGraph = new AsSubgraph<>(graph, selected); - AttributedGraph newGraph = new AttributedGraph(); + AttributedGraph newGraph = + new AttributedGraph(graph.getName() + ": subgraph", graph.getGraphType()); subGraph.vertexSet().forEach(newGraph::addVertex); for (AttributedEdge e : subGraph.edgeSet()) { AttributedVertex source = subGraph.getEdgeSource(e); @@ -658,17 +670,17 @@ public class DefaultGraphDisplay implements GraphDisplay { // select all the edges that connect the supplied vertices private void selectEdgesConnecting(Collection vertices) { - viewer.getSelectedEdgeState().select( - graph.edgeSet() - .stream() - .filter( - e -> { - AttributedVertex source = graph.getEdgeSource(e); - AttributedVertex target = graph.getEdgeTarget(e); - return vertices.contains(source) - && vertices.contains(target); - }) - .collect(Collectors.toSet())); + viewer.getSelectedEdgeState() + .select( + graph.edgeSet() + .stream() + .filter( + e -> { + AttributedVertex source = graph.getEdgeSource(e); + AttributedVertex target = graph.getEdgeTarget(e); + return vertices.contains(source) && vertices.contains(target); + }) + .collect(Collectors.toSet())); } @@ -773,7 +785,7 @@ public class DefaultGraphDisplay implements GraphDisplay { } private List> getLayoutActionStates() { - String[] names = layoutTransitionManager.getLayoutNames(); + List names = LayoutAlgorithmNames.getLayoutAlgorithmNames(); List> actionStates = new ArrayList<>(); for (String layoutName : names) { ActionState state = new ActionState<>(layoutName, @@ -781,11 +793,11 @@ public class DefaultGraphDisplay implements GraphDisplay { // condense hierarchical action help to the top-level help description String anchor = layoutName; - if (layoutName.contains(LayoutFunction.VERT_MIN_CROSS)) { - anchor = LayoutFunction.VERT_MIN_CROSS; + if (layoutName.contains(VERT_MIN_CROSS)) { + anchor = VERT_MIN_CROSS; } - else if (layoutName.contains(LayoutFunction.MIN_CROSS)) { - anchor = LayoutFunction.MIN_CROSS; + else if (layoutName.contains(MIN_CROSS)) { + anchor = MIN_CROSS; } state.setHelpLocation(new HelpLocation(ACTION_OWNER, anchor)); @@ -837,25 +849,21 @@ public class DefaultGraphDisplay implements GraphDisplay { // satellite.setGraphMouse(new JgtSatelliteGraphMouse()); - satellite.getRenderContext().setEdgeDrawPaintFunction(Colors::getColor); - satellite.getRenderContext() - .setEdgeStrokeFunction(ProgramGraphFunctions::getEdgeStroke); - satellite.getRenderContext() - .setEdgeDrawPaintFunction(viewer.getRenderContext().getEdgeDrawPaintFunction()); - satellite.getRenderContext() - .setVertexFillPaintFunction(viewer.getRenderContext().getVertexFillPaintFunction()); - satellite.getRenderContext() - .setVertexDrawPaintFunction(viewer.getRenderContext().getVertexDrawPaintFunction()); + RenderContext renderer = satellite.getRenderContext(); + RenderContext viewerRenderer = viewer.getRenderContext(); + renderer.setEdgeDrawPaintFunction(viewerRenderer.getEdgeDrawPaintFunction()); + renderer.setEdgeStrokeFunction(viewerRenderer.getEdgeArrowStrokeFunction()); + renderer.setEdgeDrawPaintFunction(viewerRenderer.getEdgeDrawPaintFunction()); + renderer.setVertexFillPaintFunction(viewerRenderer.getVertexFillPaintFunction()); + renderer.setVertexDrawPaintFunction(viewerRenderer.getVertexDrawPaintFunction()); satellite.scaleToLayout(); - satellite.getRenderContext().setVertexLabelFunction(n -> null); - // always get the current predicate from the main view and test with it, - satellite.getRenderContext() - .setVertexIncludePredicate( - v -> viewer.getRenderContext().getVertexIncludePredicate().test(v)); - satellite.getRenderContext() - .setEdgeIncludePredicate( - e -> viewer.getRenderContext().getEdgeIncludePredicate().test(e)); + renderer.setVertexLabelFunction(n -> null); + + // the satellite should use the same vertex predicate so that it has the same vertices + // as the main graph + renderer.setVertexIncludePredicate(v -> viewerRenderer.getVertexIncludePredicate().test(v)); + renderer.setEdgeIncludePredicate(e -> viewerRenderer.getEdgeIncludePredicate().test(e)); satellite.getComponent().setBorder(BorderFactory.createEtchedBorder()); parentViewer.getComponent().addComponentListener(new ComponentAdapter() { @Override @@ -876,6 +884,9 @@ public class DefaultGraphDisplay implements GraphDisplay { } listener = null; componentProvider.closeComponent(); + if (graphDisplayOptions != null) { + graphDisplayOptions.removeChangeListener(graphDisplayOptionsChangeListener); + } } @Override @@ -1026,8 +1037,9 @@ public class DefaultGraphDisplay implements GraphDisplay { } private void setInitialLayoutAlgorithm() { - if (displayProperties.containsKey(INITIAL_LAYOUT_ALGORITHM)) { - String layoutAlgorithmName = displayProperties.get(INITIAL_LAYOUT_ALGORITHM); + String layoutAlgorithmName = graphDisplayOptions.getDefaultLayoutAlgorithmNameLayout(); + layoutAction.setCurrentActionStateByUserData(layoutAlgorithmName); + if (layoutAlgorithmName != null) { layoutTransitionManager.setLayout(layoutAlgorithmName); } else { @@ -1047,7 +1059,7 @@ public class DefaultGraphDisplay implements GraphDisplay { private boolean isRoot(AttributedVertex vertex) { Set incomingEdgesOf = graph.incomingEdgesOf(vertex); return incomingEdgesOf.isEmpty() || - graph.incomingEdgesOf(vertex).equals(graph.outgoingEdgesOf(vertex)); + graph.incomingEdgesOf(vertex).equals(graph.outgoingEdgesOf(vertex)); } /** @@ -1066,7 +1078,7 @@ public class DefaultGraphDisplay implements GraphDisplay { .elements(vertices) .maxFactor(.05) .buttonSupplier(JRadioButton::new) - .paintFunction(v -> Colors.VERTEX_TYPE_TO_COLOR_MAP.getOrDefault(v, Color.blue)) + .paintFunction(v -> graphDisplayOptions.getVertexColor(v)) .build(); vertexFilters.addItemListener(item -> { @@ -1074,7 +1086,7 @@ public class DefaultGraphDisplay implements GraphDisplay { Set selected = (Set) item.getItem(); viewer.getRenderContext() .setVertexIncludePredicate( - v -> v.getAttributeMap().values().stream().noneMatch(selected::contains)); + v -> v.getAttributes().values().stream().noneMatch(selected::contains)); viewer.repaint(); }); @@ -1084,7 +1096,7 @@ public class DefaultGraphDisplay implements GraphDisplay { .elements(edges) .maxFactor(.01) .buttonSupplier(JRadioButton::new) - .paintFunction(e -> Colors.EDGE_TYPE_TO_COLOR_MAP.getOrDefault(e, Color.green)) + .paintFunction(e -> graphDisplayOptions.getEdgeColor(e)) .build(); edgeFilters.addItemListener(item -> { @@ -1092,7 +1104,7 @@ public class DefaultGraphDisplay implements GraphDisplay { Set selected = (Set) item.getItem(); viewer.getRenderContext() .setEdgeIncludePredicate( - e -> e.getAttributeMap().values().stream().noneMatch(selected::contains)); + e -> e.getAttributes().values().stream().noneMatch(selected::contains)); viewer.repaint(); }); } @@ -1115,36 +1127,21 @@ public class DefaultGraphDisplay implements GraphDisplay { } } - @Override - public void defineVertexAttribute(String attributeName) { - log.fine("defineVertexAttribute " + attributeName + " is not implemented"); + private void setGraphDisplayOptions(GraphDisplayOptions options) { + if (graphDisplayOptions != null) { + graphDisplayOptions.removeChangeListener(graphDisplayOptionsChangeListener); + } + graphDisplayOptions = options; + graphDisplayOptions.addChangeListener(graphDisplayOptionsChangeListener); + graphRenderer.setGraphTypeDisplayOptions(options); + refreshViewer(); + } @Override - public void defineEdgeAttribute(String attributeName) { - log.fine("defineEdgeAttribute " + attributeName + " is not implemented"); - } - - @Override - public void setVertexLabelAttribute(String attributeName, int alignment, int size, - boolean monospace, - int maxLines) { - log.fine("setVertexLabel " + attributeName); - this.iconCache.setPreferredVertexLabelAttribute(attributeName); - } - - /** - * consume a {@link Graph} and display it - * @param graph the graph to display or consume - * @param title a title for the graph - * @param append if true, append the new graph to any existing graph. - * @param monitor a {@link TaskMonitor} which can be used to cancel the graphing operation - */ - @Override - public void setGraph(AttributedGraph graph, String title, boolean append, - TaskMonitor monitor) { - iconCache.clear(); - + public void setGraph(AttributedGraph graph, GraphDisplayOptions options, String title, + boolean append, TaskMonitor monitor) { + setGraphDisplayOptions(options); if (append && Objects.equals(title, this.title) && this.graph != null) { graph = mergeGraphs(graph, this.graph); } @@ -1156,12 +1153,12 @@ public class DefaultGraphDisplay implements GraphDisplay { Msg.showWarn(this, null, "Graph Not Rendered - Too many nodes!", "Exceeded limit of " + MAX_NODES + " nodes.\n\n Graph contained " + count + " nodes!"); - graph = new AttributedGraph(); + graph = new AttributedGraph("Aborted", graph.getGraphType(), "Too Many Nodes"); graph.addVertex("1", "Graph Aborted"); } doSetGraphData(graph); graphCollapser = new GhidraGraphCollapser(viewer); - + buildHighlighers(); } private AttributedGraph mergeGraphs(AttributedGraph newGraph, AttributedGraph oldGraph) { @@ -1172,7 +1169,7 @@ public class DefaultGraphDisplay implements GraphDisplay { AttributedVertex from = oldGraph.getEdgeSource(edge); AttributedVertex to = oldGraph.getEdgeTarget(edge); AttributedEdge newEdge = newGraph.addEdge(from, to); - Map attributeMap = edge.getAttributeMap(); + Map attributeMap = edge.getAttributes(); for (String key : attributeMap.keySet()) { newEdge.setAttribute(key, edge.getAttribute(key)); } @@ -1256,7 +1253,7 @@ public class DefaultGraphDisplay implements GraphDisplay { @Override public void updateVertexName(AttributedVertex vertex, String newName) { vertex.setName(newName); - iconCache.evict(vertex); + graphRenderer.vertexChanged(vertex); viewer.repaint(); } @@ -1311,44 +1308,14 @@ public class DefaultGraphDisplay implements GraphDisplay { PopupSource popupSource = new GraphDisplayPopupSource(vv); popupRegulator = new PopupRegulator<>(popupSource); - this.iconCache = new GhidraIconCache(); RenderContext renderContext = vv.getRenderContext(); renderContext.getSelectedVertexState().addItemListener(item -> { renderContext.getSelectedEdgeState().clear(); - selectEdgesConnecting(renderContext.getSelectedVertexState().getSelected()); - }); + selectEdgesConnecting(renderContext.getSelectedVertexState().getSelected()); + }); - setVertexPreferences(vv); - - renderContext.setEdgeStrokeFunction(e -> ProgramGraphFunctions.getEdgeStroke(e)); - renderContext.setEdgeDrawPaintFunction(e -> Colors.getColor(e)); - renderContext.setArrowDrawPaintFunction(e -> Colors.getColor(e)); - renderContext.setArrowFillPaintFunction(e -> Colors.getColor(e)); - - // assign the shapes to the modal renderer - ModalRenderer modalRenderer = vv.getRenderer(); - // the modal renderer optimizes rendering for large graphs by removing detail - - Renderer.Vertex vertexRenderer = - modalRenderer.getVertexRenderer(LIGHTWEIGHT); - - // cause the lightweight (optimized) renderer to use the vertex shapes instead - // of using default shapes. - if (vertexRenderer instanceof LightweightVertexRenderer) { - Function vertexShapeFunction = - renderContext.getVertexShapeFunction(); - LightweightVertexRenderer lightweightVertexRenderer = - (LightweightVertexRenderer) vertexRenderer; - lightweightVertexRenderer.setVertexShapeFunction(vertexShapeFunction); - } - - renderContext.setVertexLabelRenderer(new JLabelVertexLabelRenderer(Color.black)); - renderContext.setVertexDrawPaintFunction(Colors::getColor); - renderContext.setVertexFillPaintFunction(Colors::getColor); - renderContext.setVertexStrokeFunction(n -> new BasicStroke(3.0f)); - - renderContext.setEdgeShapeFunction(EdgeShape.line()); + graphRenderer.initializeViewer(vv); vv.getComponent().requestFocus(); vv.setBackground(Color.WHITE); @@ -1357,46 +1324,12 @@ public class DefaultGraphDisplay implements GraphDisplay { vv.getComponent().removeMouseListener(mouseListener); } - graphMouse = new JgtGraphMouse(this, - Boolean.parseBoolean(displayProperties.getOrDefault(ENABLE_EDGE_SELECTION, - "false"))); + graphMouse = new JgtGraphMouse(this, false); vv.setGraphMouse(graphMouse); return vv; } - private void setVertexPreferences(VisualizationViewer vv) { - RenderContext renderContext = vv.getRenderContext(); - String useIcons = - displayProperties.getOrDefault(DISPLAY_VERTICES_AS_ICONS, Boolean.TRUE.toString()); - Function toRectangle = s -> RectangleUtils.convert(s.getBounds2D()); - if (Boolean.parseBoolean(useIcons)) { - // set up the shape and color functions - IconShapeFunction nodeShaper = - new IconShapeFunction<>(new EllipseShapeFunction<>()); - - nodeShaper.setIconFunction(iconCache::get); - renderContext.setVertexShapeFunction(nodeShaper); - renderContext.setVertexIconFunction(iconCache::get); - - vv.setInitialDimensionFunction(InitialDimensionFunction - .builder(nodeShaper.andThen(toRectangle)) - .build()); - } - else { - vv.getRenderContext().setVertexShapeFunction(ProgramGraphFunctions::getVertexShape); - vv.setInitialDimensionFunction(InitialDimensionFunction - .builder(renderContext.getVertexShapeFunction() - .andThen(toRectangle)) - .build()); - vv.getRenderContext().setVertexLabelFunction(Object::toString); - vv.getRenderContext() - .setVertexLabelPosition( - VertexLabel.Position.valueOf( - displayProperties.getOrDefault(VERTEX_LABEL_POSITION, "AUTO"))); - } - } - private void copyActionsToNewGraph(DefaultGraphDisplay display) { for (DockingActionIf action : addedActions) { @@ -1445,7 +1378,7 @@ public class DefaultGraphDisplay implements GraphDisplay { return viewer.getSelectedVertices(); } - public ActionContext getActionContext(MouseEvent e) { + ActionContext getActionContext(MouseEvent e) { AttributedVertex pickedVertex = JgtUtils.getVertex(e, viewer); if (pickedVertex != null) { @@ -1587,39 +1520,6 @@ public class DefaultGraphDisplay implements GraphDisplay { } } - private class AttributedToolTipInfo extends ToolTipInfo { - - AttributedToolTipInfo(Attributed graphObject, MouseEvent event) { - super(event, graphObject); - } - - @Override - public JComponent createToolTipComponent() { - if (graphObject == null) { - return null; - } - - String toolTip = graphObject.getHtmlString(); - 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 - } - } - /** * Item listener for selection changes in the graph with the additional * capability of being able to disable the listener without removing it. diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplayProvider.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplayProvider.java index e109ba3b99..d7e6ab523f 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplayProvider.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphDisplayProvider.java @@ -15,9 +15,7 @@ */ package ghidra.graph.visualization; -import java.util.Collections; import java.util.HashSet; -import java.util.Map; import java.util.Set; import ghidra.framework.options.Options; @@ -54,13 +52,7 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider { } @Override - public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) { - return getGraphDisplay(reuseGraph, Collections.emptyMap(), monitor); - } - - @Override - public GraphDisplay getGraphDisplay(boolean reuseGraph, Map properties, - TaskMonitor monitor) { + public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) { if (reuseGraph && !displays.isEmpty()) { DefaultGraphDisplay visibleGraph = getVisibleGraph(); @@ -69,7 +61,7 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider { } DefaultGraphDisplay display = - Swing.runNow(() -> new DefaultGraphDisplay(this, properties, displayCounter++)); + Swing.runNow(() -> new DefaultGraphDisplay(this, displayCounter++)); displays.add(display); return display; } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphRenderer.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphRenderer.java new file mode 100644 index 0000000000..bb97acd8f0 --- /dev/null +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/DefaultGraphRenderer.java @@ -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 iconCache = new ConcurrentHashMap<>(); + private final Map 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 viewer) { + + RenderContext renderContext = viewer.getRenderContext(); + Function toRectangle = + s -> RectangleUtils.convert(s.getBounds2D()); + + if (options.usesIcons()) { + // set up the shape and color functions + IconShapeFunction 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 modalRenderer = viewer.getRenderer(); + Renderer.Vertex lightWeightRenderer = + modalRenderer.getVertexRenderer(LIGHTWEIGHT); + + + // set the lightweight (optimized) renderer to use the vertex shapes instead + // of using default shapes. + if (lightWeightRenderer instanceof LightweightVertexRenderer) { + LightweightVertexRenderer lightweightVertexRenderer = + (LightweightVertexRenderer) lightWeightRenderer; + + Function 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(); + } +} diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/EdgeComparator.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/EdgeComparator.java index 607177defb..9dd0933b81 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/EdgeComparator.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/EdgeComparator.java @@ -15,44 +15,42 @@ */ package ghidra.graph.visualization; -import ghidra.service.graph.AttributedEdge; - import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; + +import ghidra.service.graph.AttributedEdge; +import ghidra.service.graph.GraphType; /** - * {@code Comparator} to order {@code AttributedEdge}s based on their position in a - * supplied {@code List}. - * + * Edge comparator that compares edges based on their edge type. The default renderer will use + * the order in which the edge types were defined in the {@link GraphType}. */ public class EdgeComparator implements Comparator { - /** - * {@code Map} of EdgeType attribute value to integer priority - */ - private Map edgePriorityMap = new HashMap(); + private GraphRenderer renderer; - /** - * Create an instance and place the list values into the {@code edgePriorityMap} - * with a one-up counter expressing their relative priority - * @param edgePriorityList - */ - public EdgeComparator(List edgePriorityList) { - edgePriorityList.forEach(s -> edgePriorityMap.put(s, edgePriorityList.indexOf(s))); + public EdgeComparator(GraphRenderer renderer) { + this.renderer = renderer; } - /** - * {@inheritdoc} - * Compares the {@code AttributedEdge}s using their priority in the supplied {@code edgePriorityMap} - */ @Override - public int compare(AttributedEdge edgeOne, AttributedEdge edgeTwo) { - return priority(edgeOne).compareTo(priority(edgeTwo)); + public int compare(AttributedEdge edge1, AttributedEdge edge2) { + 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); - } } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraIconCache.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraIconCache.java deleted file mode 100644 index 419b71b3e6..0000000000 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GhidraIconCache.java +++ /dev/null @@ -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 = new HashMap<>(); - private int strokeThickness = DEFAULT_STROKE_THICKNESS; - - private final Map 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; - } -} diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GraphRenderer.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GraphRenderer.java new file mode 100644 index 0000000000..4c9ae2a441 --- /dev/null +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GraphRenderer.java @@ -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. + *

+ * @param viewer the {@link VisualizationViewer} + */ + public void initializeViewer(VisualizationViewer 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(); + +} diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GroupVertex.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GroupVertex.java index ffb8f027f9..6b33b49792 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GroupVertex.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/GroupVertex.java @@ -46,8 +46,7 @@ public class GroupVertex extends AttributedVertex { super(id); this.first = first; this.children = children; - setAttribute("VertexType", "Collapsed"); - setAttribute("Icon", "Star"); + setVertexType("Collapsed Group"); } /** diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/IconShape.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/IconShape.java deleted file mode 100644 index c805174a54..0000000000 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/IconShape.java +++ /dev/null @@ -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 { - - @Override - public Type apply(Shape shape) { - List 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 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 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 getShapePoints(Shape shape) { - float[] seg = new float[6]; - List 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; - } - } -} diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutFunction.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutFunction.java index 5c015856bf..136b569035 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutFunction.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutFunction.java @@ -15,6 +15,8 @@ */ package ghidra.graph.visualization; +import static ghidra.service.graph.LayoutAlgorithmNames.*; + import java.util.Comparator; import java.util.function.Function; 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.sugiyama.Layering; +import com.google.common.base.Objects; + import ghidra.service.graph.AttributedEdge; import ghidra.service.graph.AttributedVertex; @@ -36,61 +40,32 @@ import ghidra.service.graph.AttributedVertex; class LayoutFunction implements Function> { - 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 favoredEdgePredicate; Comparator edgeTypeComparator; - LayoutFunction(Comparator edgeTypeComparator, Predicate favoredEdgePredicate) { - this.edgeTypeComparator = edgeTypeComparator; - this.favoredEdgePredicate = favoredEdgePredicate; + LayoutFunction(GraphRenderer renderer) { + this.edgeTypeComparator = new EdgeComparator(renderer); + 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 public LayoutAlgorithm.Builder apply(String name) { switch(name) { case GEM: return GEMLayoutAlgorithm.edgeAwareBuilder(); - case KAMADA_KAWAI: + case FORCED_BALANCED: return KKLayoutAlgorithm. builder() .preRelaxDuration(1000); - case FRUCTERMAN_REINGOLD: + case FORCE_DIRECTED: return FRLayoutAlgorithm. builder() .repulsionContractBuilder(BarnesHutFRRepulsion.builder()); case CIRCLE: return CircleLayoutAlgorithm. builder() .reduceEdgeCrossing(false); - case TIDIER_RADIAL_TREE: + case COMPACT_RADIAL: return TidierRadialTreeLayoutAlgorithm . edgeAwareBuilder() .edgeComparator(edgeTypeComparator); @@ -146,10 +121,10 @@ class LayoutFunction return BalloonLayoutAlgorithm . builder() .verticalVertexSpacing(300); - case TREE: + case HIERACHICAL: return EdgeAwareTreeLayoutAlgorithm .edgeAwareBuilder(); - case TIDIER_TREE: + case COMPACT_HIERACHICAL: default: return TidierTreeLayoutAlgorithm . edgeAwareBuilder() diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutTransitionManager.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutTransitionManager.java index 6849d81829..720903e728 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutTransitionManager.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/LayoutTransitionManager.java @@ -15,10 +15,6 @@ */ 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.Predicate; @@ -30,8 +26,7 @@ import org.jungrapht.visualization.layout.model.Rectangle; import org.jungrapht.visualization.util.LayoutAlgorithmTransition; import org.jungrapht.visualization.util.LayoutPaintable; -import ghidra.service.graph.AttributedEdge; -import ghidra.service.graph.AttributedVertex; +import ghidra.service.graph.*; /** * Manages the selection and transition from one {@link LayoutAlgorithm} to another @@ -73,14 +68,13 @@ class LayoutTransitionManager { public LayoutTransitionManager( VisualizationServer visualizationServer, Predicate rootPredicate, - List edgeTypePriorityList, - Predicate favoredEdgePredicate) { + GraphRenderer renderer) { + this.visualizationServer = visualizationServer; this.rootPredicate = rootPredicate; this.renderContext = visualizationServer.getRenderContext(); this.vertexBoundsFunction = visualizationServer.getRenderContext().getVertexBoundsFunction(); - this.layoutFunction = new LayoutFunction(new EdgeComparator(edgeTypePriorityList), - favoredEdgePredicate); + this.layoutFunction = new LayoutFunction(renderer); } /** @@ -144,7 +138,7 @@ class LayoutTransitionManager { */ public LayoutAlgorithm getInitialLayoutAlgorithm() { LayoutAlgorithm initialLayoutAlgorithm = - layoutFunction.apply(TIDIER_TREE).build(); + layoutFunction.apply(LayoutAlgorithmNames.COMPACT_HIERACHICAL).build(); if (initialLayoutAlgorithm instanceof TreeLayout) { ((TreeLayout) initialLayoutAlgorithm) @@ -157,11 +151,4 @@ class LayoutTransitionManager { return initialLayoutAlgorithm; } - /** - * Supplies a {@code String[]} array of the supported layout names - * @return - */ - public String[] getLayoutNames() { - return layoutFunction.getNames(); - } } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/ProgramGraphFunctions.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/ProgramGraphFunctions.java deleted file mode 100644 index e3249d1ac5..0000000000 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/ProgramGraphFunctions.java +++ /dev/null @@ -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 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 map = attributed.getAttributeMap(); - String name = StringEscapeUtils.escapeHtml4(map.get("Name")); - if (map.containsKey(preferredLabelAttribute)) { - name = StringEscapeUtils.escapeHtml4(map.get(preferredLabelAttribute)); - } - return "" + String.join("

", Splitter.on('\n').split(name)); - } -} diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtGraphMouse.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtGraphMouse.java index fc77954316..2ae17de1ef 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtGraphMouse.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/visualization/mouse/JgtGraphMouse.java @@ -73,4 +73,8 @@ public class JgtGraphMouse extends DefaultGraphMouse - -
-
-
-
-
- -

Provided by: Program Graph Plugin

-

Related Topics

+

Program Graphs Display Options

+
+

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 + Graph Type Display Options for + general help on graph type display options.

+
+

Provided by: Program Graph Plugin

+ +

Related Topics

  • Default Graph Display
  • Graph Export
  • diff --git a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockGraphTask.java b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockGraphTask.java index c524e880c2..52c82af406 100644 --- a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockGraphTask.java +++ b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/BlockGraphTask.java @@ -15,7 +15,8 @@ */ package ghidra.graph.program; -import java.awt.Color; +import static ghidra.graph.ProgramGraphType.*; + import java.util.*; import docking.action.builder.ActionBuilder; @@ -23,6 +24,7 @@ import docking.widgets.EventTrigger; import ghidra.app.plugin.core.colorizer.ColorizingService; import ghidra.app.util.AddEditDialog; import ghidra.framework.plugintool.PluginTool; +import ghidra.graph.*; import ghidra.program.model.address.*; import ghidra.program.model.block.*; import ghidra.program.model.listing.*; @@ -30,7 +32,8 @@ import ghidra.program.model.symbol.*; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; import ghidra.service.graph.*; -import ghidra.util.*; +import ghidra.util.HelpLocation; +import ghidra.util.Msg; import ghidra.util.exception.CancelledException; import ghidra.util.exception.GraphException; import ghidra.util.task.Task; @@ -53,55 +56,6 @@ public class BlockGraphTask extends Task { 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 CodeBlockModel blockModel; private AddressSetView selection; @@ -113,15 +67,17 @@ public class BlockGraphTask extends Task { private Program program; private AddressSetView graphScope; private String graphTitle; + private ProgramGraphType graphType; - public BlockGraphTask(String actionName, boolean graphEntryPointNexus, boolean showCode, - boolean reuseGraph, boolean appendGraph, PluginTool tool, ProgramSelection selection, - ProgramLocation location, CodeBlockModel blockModel, - GraphDisplayProvider graphProvider) { + public BlockGraphTask(ProgramGraphType graphType, + boolean graphEntryPointNexus, boolean reuseGraph, boolean appendGraph, + PluginTool tool, ProgramSelection selection, ProgramLocation location, + CodeBlockModel blockModel, GraphDisplayProvider graphProvider) { super("Graph Program", true, false, true); + this.graphType = graphType; this.graphEntryPointNexus = graphEntryPointNexus; - this.showCode = showCode; + this.showCode = graphType instanceof CodeFlowGraphType; this.reuseGraph = reuseGraph; this.appendGraph = appendGraph; this.tool = tool; @@ -131,7 +87,7 @@ public class BlockGraphTask extends Task { this.selection = selection; this.location = location; this.program = blockModel.getProgram(); - this.graphTitle = actionName + ": "; + this.graphTitle = graphType.getName() + ": "; } /** @@ -140,22 +96,25 @@ public class BlockGraphTask extends Task { @Override public void run(TaskMonitor monitor) throws CancelledException { this.graphScope = getGraphScopeAndGenerateGraphTitle(); - AttributedGraph graph = createGraph(); + AttributedGraph graph = createGraph(graphTitle); monitor.setMessage("Generating Graph..."); 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 = new BlockModelGraphDisplayListener(tool, blockModel, display); addActions(display, v -> listener.getAddress(v)); display.setGraphDisplayListener(listener); if (showCode) { - display.defineVertexAttribute(CODE_ATTRIBUTE); - display.defineVertexAttribute(SYMBOLS_ATTRIBUTE); - display.setVertexLabelAttribute(CODE_ATTRIBUTE, GraphDisplay.ALIGN_LEFT, 12, true, - codeLimitPerBlock + 1); + graphOptions.setVertexLabelOverrideAttributeKey(CODE_ATTRIBUTE); } - display.setGraph(graph, graphTitle, appendGraph, monitor); + display.setGraph(graph, graphOptions, graphTitle, appendGraph, monitor); if (location != null) { // initialize the graph location, but don't have the graph send an event @@ -217,9 +176,9 @@ public class BlockGraphTask extends Task { codeLimitPerBlock = maxLines; } - protected AttributedGraph createGraph() throws CancelledException { + protected AttributedGraph createGraph(String name) throws CancelledException { int blockCount = 0; - AttributedGraph graph = new AttributedGraph(); + AttributedGraph graph = new AttributedGraph(name, graphType); CodeBlockIterator it = getBlockIterator(); List entryPoints = new ArrayList<>(); @@ -334,8 +293,7 @@ public class BlockGraphTask extends Task { AttributedVertex entryNexusVertex = getEntryNexusVertex(graph); for (AttributedVertex vertex : entries) { AttributedEdge edge = graph.addEdge(entryNexusVertex, vertex); - edge.setAttribute("Name", edgeNames[ENTRY]); - edge.setAttribute("EdgeType", edgeTypes[ENTRY]); + edge.setAttribute("EdgeType", ENTRY_NEXUS); } } @@ -517,99 +475,36 @@ public class BlockGraphTask extends Task { } protected void setEdgeAttributes(AttributedEdge edge, CodeBlockReference ref) { - - 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]); + edge.setEdgeType(ProgramGraphType.getEdgeType(ref.getFlowType())); } protected void setVertexAttributes(AttributedVertex vertex, CodeBlock bb, boolean isEntry) { - String vertexType = BODY_NODE; + String vertexType = BODY; Address firstStartAddress = bb.getFirstStartAddress(); if (firstStartAddress.isExternalAddress()) { - vertexType = EXTERNAL_NODE; + vertexType = EXTERNAL; } else if (isEntry) { - vertexType = ENTRY_NODE; + vertexType = ENTRY; } else { FlowType flowType = bb.getFlowType(); if (flowType.isTerminal()) { - vertexType = EXIT_NODE; + vertexType = EXIT; } else if (flowType.isComputed()) { - vertexType = SWITCH_NODE; + vertexType = SWITCH; } else if (flowType == RefType.INDIRECTION) { - vertexType = DATA_NODE; + vertexType = DATA; } else if (flowType == RefType.INVALID) { - vertexType = BAD_NODE; + vertexType = BAD; } } - - 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"); - } + vertex.setVertexType(vertexType); } private AttributedVertex getEntryNexusVertex(AttributedGraph graph) { diff --git a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/DataReferenceGraph.java b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/DataReferenceGraph.java index 57c4f602e1..090126123a 100644 --- a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/DataReferenceGraph.java +++ b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/DataReferenceGraph.java @@ -15,6 +15,8 @@ */ package ghidra.graph.program; +import ghidra.graph.DataFlowGraphType; +import ghidra.graph.ProgramGraphType; import ghidra.program.model.address.Address; import ghidra.program.model.listing.*; 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) */ public DataReferenceGraph(Program program, int depth) { + super("Data Reference", new DataFlowGraphType()); this.program = program; this.depthPerStep = depth; } @@ -106,13 +109,14 @@ public class DataReferenceGraph extends AttributedGraph { private void setupEdge(AttributedEdge edge, Reference ref) { 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) { edge.setAttribute(REF_SYMBOL_ATTRIBUTE, program.getSymbolTable().getSymbol(ref.getSymbolID()).getName()); } } + private void setupVertex(AttributedVertex vertex) { Address address = program.getAddressFactory().getAddress(vertex.getAttribute(ADDRESS_ATTRIBUTE)); @@ -122,9 +126,13 @@ public class DataReferenceGraph extends AttributedGraph { CodeUnit unit = program.getListing().getCodeUnitContaining(address); if (unit instanceof Data) { vertex.setAttribute(DATA_ATTRIBUTE, ((Data) unit).getBaseDataType().getName()); + vertex.setVertexType(ProgramGraphType.DATA); } else if (unit instanceof Instruction) { - vertex.setAttribute("Icon", "TriangleDown"); + vertex.setVertexType(ProgramGraphType.INSTRUCTION); + } + else { + vertex.setVertexType(ProgramGraphType.STACK); } } diff --git a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/DataReferenceGraphTask.java b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/DataReferenceGraphTask.java index 765e262d64..3462c78401 100644 --- a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/DataReferenceGraphTask.java +++ b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/DataReferenceGraphTask.java @@ -17,6 +17,8 @@ package ghidra.graph.program; import docking.widgets.EventTrigger; import ghidra.framework.plugintool.PluginTool; +import ghidra.graph.DataFlowGraphType; +import ghidra.graph.ProgramGraphDisplayOptions; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSet; import ghidra.program.model.listing.CodeUnit; @@ -35,6 +37,7 @@ import ghidra.util.task.TaskMonitor; */ public class DataReferenceGraphTask extends Task { + private static final String VERTEX_COLOR_OVERRIDE = "Color"; private String graphTitle; private GraphDisplayProvider graphProvider; private boolean reuseGraph; @@ -117,7 +120,7 @@ public class DataReferenceGraphTask extends Task { /* TODO * 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) { @@ -128,19 +131,16 @@ public class DataReferenceGraphTask extends Task { try { if (display == null) { 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 = new DataReferenceGraphDisplayListener(tool, display, program, totalMaxDepth); display.setGraphDisplayListener(listener); } - - display.setGraph(graph, graphTitle, appendGraph, monitor); + GraphDisplayOptions graphDisplayOptions = + 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) { // initialize the graph location, but don't have the graph send an event diff --git a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/ProgramGraphPlugin.java b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/ProgramGraphPlugin.java index 0cc1d5eb79..0961c2ad93 100644 --- a/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/ProgramGraphPlugin.java +++ b/Ghidra/Features/ProgramGraph/src/main/java/ghidra/graph/program/ProgramGraphPlugin.java @@ -35,6 +35,7 @@ import ghidra.framework.options.*; import ghidra.framework.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.graph.*; import ghidra.program.model.block.CodeBlockModel; import ghidra.service.graph.GraphDisplayProvider; import ghidra.util.HelpLocation; @@ -106,6 +107,16 @@ public class ProgramGraphPlugin extends ProgramPlugin public ProgramGraphPlugin(PluginTool tool) { super(tool, true, true); intializeOptions(); + registerProgramFlowGraphDisplayOptionsWithTool(); + } + + private void registerProgramFlowGraphDisplayOptionsWithTool() { + ProgramGraphDisplayOptions displayOptions = + new ProgramGraphDisplayOptions(new BlockFlowGraphType(), null); + + // this will register Program Flow Graph Type options with the tool + HelpLocation help = new HelpLocation(getName(), "Program Graphs Display Options"); + displayOptions.registerOptions(tool.getOptions("Graph"), help); } private void intializeOptions() { @@ -315,19 +326,19 @@ public class ProgramGraphPlugin extends ProgramPlugin } private void graphBlockFlow() { - graph("Block Flow Graph", blockModelService.getActiveBlockModelName(), false); + graph(new BlockFlowGraphType(), blockModelService.getActiveBlockModelName()); } private void graphCodeFlow() { - graph("Code Flow Graph", blockModelService.getActiveBlockModelName(), true); + graph(new CodeFlowGraphType(), blockModelService.getActiveBlockModelName()); } private void graphSubroutines() { - graph("Call Graph", blockModelService.getActiveSubroutineModelName(), false); + graph(new CallGraphType(), blockModelService.getActiveSubroutineModelName()); } private void graphSubroutinesUsing(String modelName) { - graph("Call Graph (" + modelName + ")", modelName, false); + graph(new CallGraphType(), modelName); } private void graphDataReferences() { @@ -342,11 +353,12 @@ public class ProgramGraphPlugin extends ProgramPlugin graphData(DataReferenceGraph.Directions.FROM_ONLY); } - private void graph(String actionName, String modelName, boolean showCode) { + private void graph(ProgramGraphType graphType, String modelName) { try { CodeBlockModel model = blockModelService.getNewModelByName(modelName, currentProgram, true); - BlockGraphTask task = new BlockGraphTask(actionName, graphEntryPointNexus, showCode, + BlockGraphTask task = + new BlockGraphTask(graphType, graphEntryPointNexus, reuseGraph, appendToGraph, tool, currentSelection, currentLocation, model, defaultGraphService); task.setCodeLimitPerBlock(codeLimitPerBlock); diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/wizard/OptionsPanel.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/wizard/OptionsPanel.java index 4228334da0..eecde4e144 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/wizard/OptionsPanel.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/wizard/OptionsPanel.java @@ -15,29 +15,25 @@ */ package ghidra.feature.vt.gui.wizard; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.*; + +import javax.swing.JPanel; +import javax.swing.JScrollPane; + +import docking.options.editor.OptionsEditorPanel; +import docking.wizard.*; import ghidra.feature.vt.api.main.VTProgramCorrelatorFactory; import ghidra.feature.vt.api.util.VTOptions; import ghidra.framework.options.EditorStateFactory; import ghidra.framework.options.Options; import ghidra.util.HelpLocation; -import ghidra.util.Msg; -import ghidra.util.exception.InvalidInputException; import ghidra.util.layout.MiddleLayout; import ghidra.util.layout.VerticalLayout; - -import java.awt.BorderLayout; -import java.awt.Dimension; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.util.ArrayList; -import java.util.List; - -import javax.swing.JPanel; -import javax.swing.JScrollPane; - import util.CollectionUtils; -import docking.options.editor.OptionsEditorPanel; -import docking.wizard.*; public class OptionsPanel extends AbstractMageJPanel { @@ -118,7 +114,7 @@ public class OptionsPanel extends AbstractMageJPanel { if (optionNames.isEmpty()) { continue; } - + Collections.sort(optionNames); OptionsEditorPanel optionsPanel = new OptionsEditorPanel(title, options, optionNames, editorStateFactory); optionsPanel.setOptionsPropertyChangeListener(propertyChangeListener); @@ -222,13 +218,8 @@ public class OptionsPanel extends AbstractMageJPanel { } private void applyOptions() { - try { - for (OptionsEditorPanel panel : optionsEditorPanelList) { - panel.apply(); - } - } - catch (InvalidInputException e) { - Msg.showError(this, this, "Error", "could not apply options settings", e); + for (OptionsEditorPanel panel : optionsEditorPanelList) { + panel.apply(); } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/options/editor/OptionsEditorPanel.java b/Ghidra/Framework/Docking/src/main/java/docking/options/editor/OptionsEditorPanel.java index aae4aa4a37..b4eb4d7aea 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/options/editor/OptionsEditorPanel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/options/editor/OptionsEditorPanel.java @@ -17,9 +17,11 @@ package docking.options.editor; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.util.*; +import java.util.ArrayList; +import java.util.List; -import javax.swing.*; +import javax.swing.BorderFactory; +import javax.swing.JPanel; import javax.swing.border.Border; import docking.help.Help; @@ -27,7 +29,6 @@ import docking.help.HelpService; import ghidra.framework.options.*; import ghidra.util.HelpLocation; import ghidra.util.exception.AssertException; -import ghidra.util.exception.InvalidInputException; import ghidra.util.layout.VerticalLayout; /** @@ -35,7 +36,7 @@ import ghidra.util.layout.VerticalLayout; * Panel that shows each property in an Options category or a Group in an * Options category. */ -public class OptionsEditorPanel extends JPanel implements OptionsEditor { +public class OptionsEditorPanel extends JPanel { private EditorStateFactory editorStateFactory; private Options options; @@ -63,12 +64,9 @@ public class OptionsEditorPanel extends JPanel implements OptionsEditor { this.optionNames = optionNames; this.title = title; - Collections.sort(optionNames); - create(); } - @Override public void dispose() { propertyChangeListener = null; editorInfoList.clear(); @@ -116,33 +114,11 @@ public class OptionsEditorPanel extends JPanel implements OptionsEditor { setBorder(border); } -//================================================================================================== -// OptionsEditor Interface Methods -//================================================================================================== - - @Override - public void apply() throws InvalidInputException { + public void apply() { for (EditorState state : editorInfoList) { state.applyValue(); } } - - @Override - public void cancel() { - // nothing to do - } - - @Override - public void reload() { - // nothing to do, as this component is reloaded when options are changed - } - - @Override - public JComponent getEditorComponent(Options o, EditorStateFactory factory) { - return this; - } - - @Override public void setOptionsPropertyChangeListener(PropertyChangeListener listener) { this.propertyChangeListener = listener; } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/options/editor/OptionsPanel.java b/Ghidra/Framework/Docking/src/main/java/docking/options/editor/OptionsPanel.java index 38ed5ff74e..d013009034 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/options/editor/OptionsPanel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/options/editor/OptionsPanel.java @@ -341,13 +341,13 @@ public class OptionsPanel extends JPanel { if (options == null) { return null; } - List optionList = node.getOptionNames(); editor = options.getOptionsEditor(); if (editor == null) { + List optionList = node.getOptionNames(); + Collections.sort(optionList); if (optionList.size() > 0) { - editor = new ScrollableOptionsEditor(options.getName(), options, optionList, - editorStateFactory); + editor = new ScrollableOptionsEditor(options.getName(), optionList); } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/options/editor/ScrollableOptionsEditor.java b/Ghidra/Framework/Docking/src/main/java/docking/options/editor/ScrollableOptionsEditor.java index f7a8873e6c..1e55ea59f4 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/options/editor/ScrollableOptionsEditor.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/options/editor/ScrollableOptionsEditor.java @@ -29,30 +29,36 @@ import ghidra.util.layout.MiddleLayout; /** * Panel that shows each property in an Options category or a Group in an Options category */ -public class ScrollableOptionsEditor extends JScrollPane implements OptionsEditor { +public class ScrollableOptionsEditor implements OptionsEditor { private OptionsEditorPanel optionsPanel; + private String title; + private List optionNames; + private JScrollPane scrollPane; + private PropertyChangeListener listener; /** - * Creates a panel for editing the given options + * Creates a panel for editing options. This version of the constructor allows the client + * to specify the option names to put them in some order other than the default alphabetical + * ordering. * * @param title The title of the options panel - * @param options the options for this panel * @param optionNames the names of the options for this panel - * @param editorStateFactory the factory needed by the editor */ - public ScrollableOptionsEditor(String title, Options options, List optionNames, - EditorStateFactory editorStateFactory) { + public ScrollableOptionsEditor(String title, List optionNames) { + this.title = title; + this.optionNames = optionNames; - optionsPanel = new OptionsEditorPanel(title, options, optionNames, editorStateFactory); + } - setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED); - setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED); - - // the outer panel is 'Scrollable' and uses a layout that centers the options panel - JPanel outerPanel = new ScollableOptionsPanel(); - outerPanel.add(optionsPanel); - setViewportView(outerPanel); + /** + * Creates a panel for editing options. This version of the constructor will get the + * options names from the options object when + * {@link #getEditorComponent(Options, EditorStateFactory)} is called. + * @param title the title for the panel + */ + public ScrollableOptionsEditor(String title) { + this(title, null); } //================================================================================================== @@ -61,7 +67,9 @@ public class ScrollableOptionsEditor extends JScrollPane implements OptionsEdito @Override public void dispose() { - // stub + if (optionsPanel != null) { + optionsPanel.dispose(); + } } @Override @@ -81,12 +89,27 @@ public class ScrollableOptionsEditor extends JScrollPane implements OptionsEdito @Override public JComponent getEditorComponent(Options options, EditorStateFactory factory) { - return this; + scrollPane = new JScrollPane(); + optionsPanel = new OptionsEditorPanel(title, options, optionNames, factory); + optionsPanel.setOptionsPropertyChangeListener(listener); + + scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + + // the outer panel is 'Scrollable' and uses a layout that centers the options panel + JPanel outerPanel = new ScollableOptionsPanel(); + outerPanel.add(optionsPanel); + scrollPane.setViewportView(outerPanel); + + return scrollPane; } @Override public void setOptionsPropertyChangeListener(PropertyChangeListener listener) { - optionsPanel.setOptionsPropertyChangeListener(listener); + this.listener = listener; + if (optionsPanel != null) { + optionsPanel.setOptionsPropertyChangeListener(listener); + } } //================================================================================================== @@ -120,7 +143,7 @@ public class ScrollableOptionsEditor extends JScrollPane implements OptionsEdito // scrollbars. // Dimension mySize = getPreferredSize(); - Dimension viewSize = ScrollableOptionsEditor.this.getViewport().getSize(); + Dimension viewSize = scrollPane.getViewport().getSize(); boolean viewIsLarger = viewSize.height > mySize.height; return viewIsLarger; } @@ -135,7 +158,7 @@ public class ScrollableOptionsEditor extends JScrollPane implements OptionsEdito // scrollbars. // Dimension mySize = getPreferredSize(); - Dimension viewSize = ScrollableOptionsEditor.this.getViewport().getSize(); + Dimension viewSize = scrollPane.getViewport().getSize(); boolean viewIsLarger = viewSize.width > mySize.width; return viewIsLarger; } @@ -147,4 +170,8 @@ public class ScrollableOptionsEditor extends JScrollPane implements OptionsEdito } } + // for testing + public JComponent getComponent() { + return scrollPane; + } } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/HTMLUtilities.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/HTMLUtilities.java index 45206a0ead..14e4844c16 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/HTMLUtilities.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/HTMLUtilities.java @@ -851,15 +851,8 @@ public class HTMLUtilities { * @return a string of the format #RRGGBB. */ public static String toHexString(Color color) { - int r = color.getRed(); - int g = color.getGreen(); - int b = color.getBlue(); - - StringBuilder buffy = new StringBuilder("#"); - buffy.append(StringUtilities.pad(Integer.toHexString(r), '0', 2)); - buffy.append(StringUtilities.pad(Integer.toHexString(g), '0', 2)); - buffy.append(StringUtilities.pad(Integer.toHexString(b), '0', 2)); - return buffy.toString().toUpperCase(); + // this will format a color value as a 6 digit hex string (e.g. #rrggbb) + return String.format("#%06X", color.getRGB() & 0xffffff); } } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/WebColors.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/WebColors.java new file mode 100644 index 0000000000..e28748e217 --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/WebColors.java @@ -0,0 +1,249 @@ +/* ### + * 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.util; + +import java.awt.Color; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Class for web color support. This class defines many of the colors used by html. This class + * includes methods for converting a color to a string (name or hex value) and for converting + * those strings back to a color. + */ +public abstract class WebColors { + private static final Pattern HEX_PATTERN = Pattern.compile("(0x|#)[0-9A-Fa-f]{6}"); + private static final Map nameToColorMap = new HashMap<>(); + private static final Map colorToNameMap = new HashMap<>(); + + //@formatter:off + public static final Color BLACK = registerColor("Black", Color.black); + public static final Color NAVY = registerColor("Navy", Color.decode("0x000080")); + public static final Color DARK_BLUE = registerColor("DarkBlue", Color.decode("0x00008B")); + public static final Color MEDIUM_BLUE = registerColor("MediumBlue", Color.decode("0x0000CD")); + public static final Color BLUE = registerColor("Blue", Color.decode("0x0000FF")); + public static final Color DARK_GREEN = registerColor("DarkGreen", Color.decode("0x006400")); + public static final Color GREEN = registerColor("Green", Color.decode("0x008000")); + public static final Color TEAL = registerColor("Teal", Color.decode("0x008080")); + public static final Color DARK_CYAN = registerColor("DarkCyan", Color.decode("0x008B8B")); + public static final Color DEEP_SKY_BLUE = registerColor("DeepSkyBlue", Color.decode("0x00BFFF")); + public static final Color DARK_TURQUOSE = registerColor("DarkTurquoise", Color.decode("0x00CED1")); + public static final Color LIME = registerColor("Lime", Color.decode("0x00FF00")); + public static final Color SPRING_GREEN = registerColor("SpringGreen", Color.decode("0x00FF7F")); + public static final Color AQUA = registerColor("Aqua", Color.decode("0x00FFFF")); + public static final Color CYAN = registerColor("Cyan", Color.decode("0x00FFFF")); + public static final Color MIDNIGHT_BLUE = registerColor("MidnightBlue", Color.decode("0x191970")); + public static final Color DOGER_BLUE = registerColor("DodgerBlue", Color.decode("0x1E90FF")); + public static final Color LIGHT_SEA_GREEN = registerColor("LightSeaGreen", Color.decode("0x20B2AA")); + public static final Color FOREST_GREEN = registerColor("ForestGreen", Color.decode("0x228B22")); + public static final Color SEA_GREEN = registerColor("SeaGreen", Color.decode("0x2E8B57")); + public static final Color DARK_SLATE_GRAY = registerColor("DarkSlateGray", Color.decode("0x2F4F4F")); + public static final Color LIME_GREEN = registerColor("LimeGreen", Color.decode("0x32CD32")); + public static final Color TURQUOISE = registerColor("Turquoise", Color.decode("0x40E0D0")); + public static final Color ROYAL_BLUE = registerColor("RoyalBlue", Color.decode("0x4169E1")); + public static final Color STEEL_BLUE = registerColor("SteelBlue", Color.decode("0x4682B4")); + public static final Color DARK_SLATE_BLUE = registerColor("DarkSlateBlue", Color.decode("0x483D8B")); + public static final Color INDIGO = registerColor("Indigo", Color.decode("0x4B0082")); + public static final Color CADET_BLUE = registerColor("CadetBlue", Color.decode("0x5F9EA0")); + public static final Color REBECCA_PURPLE = registerColor("RebeccaPurple", Color.decode("0x663399")); + public static final Color DIM_GRAY = registerColor("DimGray", Color.decode("0x696969")); + public static final Color SLATE_BLUE = registerColor("SlateBlue", Color.decode("0x6A5ACD")); + public static final Color OLIVE_DRAB = registerColor("OliveDrab", Color.decode("0x6B8E23")); + public static final Color SLATE_GRAY = registerColor("SlateGray", Color.decode("0x708090")); + public static final Color LAWN_GREEN = registerColor("LawnGreen", Color.decode("0x7CFC00")); + public static final Color CHARTREUSE = registerColor("Chartreuse", Color.decode("0x7FFF00")); + public static final Color AQUAMARINE = registerColor("Aquamarine", Color.decode("0x7FFFD4")); + public static final Color MAROON = registerColor("Maroon", Color.decode("0x800000")); + public static final Color PURPLE = registerColor("Purple", Color.decode("0x800080")); + public static final Color OLIVE = registerColor("Olive", Color.decode("0x808000")); + public static final Color GRAY = registerColor("Gray", Color.decode("0x808080")); + public static final Color SYY_BLUE = registerColor("SkyBlue", Color.decode("0x87CEEB")); + public static final Color LIGHT_SKY_BLUE = registerColor("LightSkyBlue", Color.decode("0x87CEFA")); + public static final Color BLUE_VIOLET = registerColor("BlueViolet", Color.decode("0x8A2BE2")); + public static final Color DARK_RED = registerColor("DarkRed", Color.decode("0x8B0000")); + public static final Color DARK_MAGENTA = registerColor("DarkMagenta", Color.decode("0x8B008B")); + public static final Color SADDLE_BROWN = registerColor("SaddleBrown", Color.decode("0x8B4513")); + public static final Color DARK_SEA_GREEN = registerColor("DarkSeaGreen", Color.decode("0x8FBC8F")); + public static final Color LIGHT_GREEN = registerColor("LightGreen", Color.decode("0x90EE90")); + public static final Color MEDIUM_PURPLE = registerColor("MediumPurple", Color.decode("0x9370DB")); + public static final Color DARK_VIOLET = registerColor("DarkViolet", Color.decode("0x9400D3")); + public static final Color PALE_GREEN = registerColor("PaleGreen", Color.decode("0x98FB98")); + public static final Color DARK_ORCHID = registerColor("DarkOrchid", Color.decode("0x9932CC")); + public static final Color YELLOW_GREEN = registerColor("YellowGreen", Color.decode("0x9ACD32")); + public static final Color SIENNA = registerColor("Sienna", Color.decode("0xA0522D")); + public static final Color BROWN = registerColor("Brown", Color.decode("0xA52A2A")); + public static final Color DARK_GRAY = registerColor("DarkGray", Color.decode("0xA9A9A9")); + public static final Color LIGHT_BLUE = registerColor("LightBlue", Color.decode("0xADD8E6")); + public static final Color GREEN_YELLOW = registerColor("GreenYellow", Color.decode("0xADFF2F")); + public static final Color PALE_TURQUOISE = registerColor("PaleTurquoise", Color.decode("0xAFEEEE")); + public static final Color POWDER_BLUE = registerColor("PowderBlue", Color.decode("0xB0E0E6")); + public static final Color FIRE_BRICK = registerColor("FireBrick", Color.decode("0xB22222")); + public static final Color DARK_GOLDENROD = registerColor("DarkGoldenRod", Color.decode("0xB8860B")); + public static final Color MEDIUM_ORCHID = registerColor("MediumOrchid", Color.decode("0xBA55D3")); + public static final Color ROSY_BROWN = registerColor("RosyBrown", Color.decode("0xBC8F8F")); + public static final Color DARK_KHAKI = registerColor("DarkKhaki", Color.decode("0xBDB76B")); + public static final Color SILVER = registerColor("Silver", Color.decode("0xC0C0C0")); + public static final Color INDIAN_RED = registerColor("IndianRed", Color.decode("0xCD5C5C")); + public static final Color PERU = registerColor("Peru", Color.decode("0xCD853F")); + public static final Color CHOCOLATE = registerColor("Chocolate", Color.decode("0xD2691E")); + public static final Color TAN = registerColor("Tan", Color.decode("0xD2B48C")); + public static final Color LIGHT_GRAY = registerColor("LightGray", Color.decode("0xD3D3D3")); + public static final Color THISTLE = registerColor("Thistle", Color.decode("0xD8BFD8")); + public static final Color ORCHID = registerColor("Orchid", Color.decode("0xDA70D6")); + public static final Color GOLDEN_ROD = registerColor("GoldenRod", Color.decode("0xDAA520")); + public static final Color PALE_VIOLET_RED = registerColor("PaleVioletRed", Color.decode("0xDB7093")); + public static final Color CRIMSON = registerColor("Crimson", Color.decode("0xDC143C")); + public static final Color GAINSBORO = registerColor("Gainsboro", Color.decode("0xDCDCDC")); + public static final Color PLUM = registerColor("Plum", Color.decode("0xDDA0DD")); + public static final Color BURLYWOOD = registerColor("BurlyWood", Color.decode("0xDEB887")); + public static final Color LIGHT_CYAN = registerColor("LightCyan", Color.decode("0xE0FFFF")); + public static final Color LAVENDER = registerColor("Lavender", Color.decode("0xE6E6FA")); + public static final Color DARK_SALMON = registerColor("DarkSalmon", Color.decode("0xE9967A")); + public static final Color VIOLET = registerColor("Violet", Color.decode("0xEE82EE")); + public static final Color PALE_GOLDENROD = registerColor("PaleGoldenRod", Color.decode("0xEEE8AA")); + public static final Color LIGHT_CORAL = registerColor("LightCoral", Color.decode("0xF08080")); + public static final Color KHAKE = registerColor("Khaki", Color.decode("0xF0E68C")); + public static final Color ALICE_BLUE = registerColor("AliceBlue", Color.decode("0xF0F8FF")); + public static final Color HONEY_DEW = registerColor("HoneyDew", Color.decode("0xF0FFF0")); + public static final Color AZURE = registerColor("Azure", Color.decode("0xF0FFFF")); + public static final Color SANDY_BROWN = registerColor("SandyBrown", Color.decode("0xF4A460")); + public static final Color WHEAT = registerColor("Wheat", Color.decode("0xF5DEB3")); + public static final Color BEIGE = registerColor("Beige", Color.decode("0xF5F5DC")); + public static final Color WHITE_SMOKE = registerColor("WhiteSmoke", Color.decode("0xF5F5F5")); + public static final Color MINT_CREAM = registerColor("MintCream", Color.decode("0xF5FFFA")); + public static final Color GHOST_WHITE = registerColor("GhostWhite", Color.decode("0xF8F8FF")); + public static final Color SALMON = registerColor("Salmon", Color.decode("0xFA8072")); + public static final Color ANTIQUE_WHITE = registerColor("AntiqueWhite", Color.decode("0xFAEBD7")); + public static final Color LINEN = registerColor("Linen", Color.decode("0xFAF0E6")); + public static final Color OLDLACE = registerColor("OldLace", Color.decode("0xFDF5E6")); + public static final Color RED = registerColor("Red", Color.decode("0xFF0000")); + public static final Color FUCHSIA = registerColor("Fuchsia", Color.decode("0xFF00FF")); + public static final Color MAGENTA = registerColor("Magenta", Color.decode("0xFF00FF")); + public static final Color DEEP_PINK = registerColor("DeepPink", Color.decode("0xFF1493")); + public static final Color ORANGE_RED = registerColor("OrangeRed", Color.decode("0xFF4500")); + public static final Color TOMATO = registerColor("Tomato", Color.decode("0xFF6347")); + public static final Color HOT_PINK = registerColor("HotPink", Color.decode("0xFF69B4")); + public static final Color CORAL = registerColor("Coral", Color.decode("0xFF7F50")); + public static final Color DARK_ORANGE = registerColor("DarkOrange", Color.decode("0xFF8C00")); + public static final Color LIGHT_SALMON = registerColor("LightSalmon", Color.decode("0xFFA07A")); + public static final Color ORANGE = registerColor("Orange", Color.decode("0xFFA500")); + public static final Color LIGHT_PINK = registerColor("LightPink", Color.decode("0xFFB6C1")); + public static final Color PINK = registerColor("Pink", Color.decode("0xFFC0CB")); + public static final Color GOLD = registerColor("Gold", Color.decode("0xFFD700")); + public static final Color PEACH_PUFF = registerColor("PeachPuff", Color.decode("0xFFDAB9")); + public static final Color NAVAJO_WHITE = registerColor("NavajoWhite", Color.decode("0xFFDEAD")); + public static final Color MOCCASIN = registerColor("Moccasin", Color.decode("0xFFE4B5")); + public static final Color BISQUE = registerColor("Bisque", Color.decode("0xFFE4C4")); + public static final Color MISTY_ROSE = registerColor("MistyRose", Color.decode("0xFFE4E1")); + public static final Color BLANCHED_ALMOND = registerColor("BlanchedAlmond", Color.decode("0xFFEBCD")); + public static final Color PAPAYA_WHIP = registerColor("PapayaWhip", Color.decode("0xFFEFD5")); + public static final Color LAVENDAR_BLUSH = registerColor("LavenderBlush", Color.decode("0xFFF0F5")); + public static final Color SEASHELL = registerColor("SeaShell", Color.decode("0xFFF5EE")); + public static final Color CORNSILK = registerColor("Cornsilk", Color.decode("0xFFF8DC")); + public static final Color LEMON_CHIFFON = registerColor("LemonChiffon", Color.decode("0xFFFACD")); + public static final Color FLORAL_WHITE = registerColor("FloralWhite", Color.decode("0xFFFAF0")); + public static final Color SNOW = registerColor("Snow", Color.decode("0xFFFAFA")); + public static final Color YELLOW = registerColor("Yellow", Color.decode("0xFFFF00")); + public static final Color LIGHT_YELLOW = registerColor("LightYellow", Color.decode("0xFFFFE0")); + public static final Color IVORY = registerColor("Ivory", Color.decode("0xFFFFF0")); + public static final Color WHITE = registerColor("White", Color.decode("0xFFFFFF")); + public static final Color MEDIUM_SPRING_GREEN = registerColor("MediumSpringGreen", Color.decode("0x00FA9A")); + public static final Color LIGHT_GOLDENROD = registerColor("LightGoldenRodYellow", Color.decode("0xFAFAD2")); + public static final Color MEDIUM_VIOLET_RED = registerColor("MediumVioletRed", Color.decode("0xC71585")); + public static final Color LIGHT_STEEL_BLUE = registerColor("LightSteelBlue", Color.decode("0xB0C4DE")); + public static final Color LIGHT_SLATE_GRAY = registerColor("LightSlateGray", Color.decode("0x778899")); + public static final Color MEDIUM_SLATE_BLUE = registerColor("MediumSlateBlue", Color.decode("0x7B68EE")); + public static final Color MEDIUM_SEA_GREEN = registerColor("MediumSeaGreen", Color.decode("0x3CB371")); + public static final Color MEDUM_AQUA_MARINE = registerColor("MediumAquaMarine", Color.decode("0x66CDAA")); + public static final Color MEDIUM_TURQOISE = registerColor("MediumTurquoise", Color.decode("0x48D1CC")); + public static final Color DARK_OLIVE_GREEN = registerColor("DarkOliveGreen", Color.decode("0x556B2F")); + public static final Color CORN_FLOWER_BLUE = registerColor("CornflowerBlue", Color.decode("0x6495ED")); + //@formatter:on + + // cannot instantiate nor extend + private WebColors() { + } + + /** + * Tries to find a color for the given String value. The String value can either be + * a hex string (see {@link Color#decode(String)}) or a web color name as defined + * above + * + * @param value the string value to interpret as a color + * @param defaultColor a default color to return if the string can't be converted to a color + * @return a color for the given string value or the default color if the string can't be translated + */ + public static Color getColorOrDefault(String value, Color defaultColor) { + Color color = getColor(value); + return color != null ? color : defaultColor; + } + + /** + * Tries to find a color for the given String value. The String value can either be + * a hex string (see {@link Color#decode(String)}) or a web color name as defined + * in {@link WebColors} + * + * @param value the string value to interpret as a color + * @return a color for the given string value or null if the string can't be translated + */ + public static Color getColor(String value) { + Color color = nameToColorMap.get(value); + if (color != null) { + return color; + } + // if the value matches an RGB hex string, turn that into a color + color = getHexColor(value); + if (color != null) { + return color; + } + return null; + } + + /** + * Converts a color to a string value. If there is a defined color for the given color value, + * the color name will be returned. Otherwise, it will return a hex string for the color as + * defined by {@link Color#toString()}. The result of this call can be passed to + * {@link #getColor(String)} and be guaranteed that a color will be returned + * + * @param color the color to convert to a string. + * @return the string representation for the given color. + */ + public static String toString(Color color) { + String name = colorToNameMap.get(color); + if (name != null) { + return name; + } + // this will format a color value as a 6 digit hex (e.g. #rrggbb) + return String.format("#%06X", color.getRGB() & 0xffffff); + } + + private static Color getHexColor(String hexString) { + Matcher matcher = HEX_PATTERN.matcher(hexString); + if (matcher.matches()) { + return Color.decode(hexString); + } + return null; + } + + private static Color registerColor(String name, Color color) { + nameToColorMap.put(name, color); + colorToNameMap.put(color, name); + return color; + } + +} diff --git a/Ghidra/Framework/Generic/src/test/java/ghidra/util/WebColorsTest.java b/Ghidra/Framework/Generic/src/test/java/ghidra/util/WebColorsTest.java new file mode 100644 index 0000000000..6968ef9a29 --- /dev/null +++ b/Ghidra/Framework/Generic/src/test/java/ghidra/util/WebColorsTest.java @@ -0,0 +1,65 @@ +/* ### + * 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.util; + +import static org.junit.Assert.*; + +import java.awt.Color; + +import org.junit.Test; + +public class WebColorsTest { + + @Test + public void testColorToStringFromDefinedValue() { + assertEquals("Navy", WebColors.toString(WebColors.NAVY)); + } + + @Test + public void testColorToStringNewColor() { + assertEquals("Navy", WebColors.toString(new Color(0, 0, 0x80))); + } + + @Test + public void testColorToStringFromColorWithNoDefinedEntry() { + assertEquals("#0123EF", WebColors.toString(new Color(0x01, 0x23, 0xEF))); + } + + @Test + public void testGetColorFromName() { + assertEquals(WebColors.NAVY, WebColors.getColor("Navy")); + } + + @Test + public void testGetColorFromHexString() { + assertEquals(WebColors.NAVY, WebColors.getColor("0x000080")); + } + + @Test + public void testGetColorFromHexString2() { + assertEquals(WebColors.NAVY, WebColors.getColor("#000080")); + } + + @Test + public void testGetColorWithNoDefinedValue() { + assertEquals(new Color(0x12, 0x34, 0x56), WebColors.getColor("0x123456")); + } + + @Test + public void testGetColorByBadName() { + assertNull(WebColors.getColor("ABCDEFG")); + } +} diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/Attributed.java b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/Attributed.java index e0bd9028d8..07b3d7ea5c 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/Attributed.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/Attributed.java @@ -16,17 +16,8 @@ package ghidra.service.graph; import java.util.*; -import java.util.Map.Entry; - -import org.apache.commons.text.StringEscapeUtils; - -import com.google.common.base.Splitter; public class Attributed { - /** - * cache of the html rendering of the vertex attributes - */ - private String htmlString; private static final String DESCRIPTION = "Description"; /** @@ -39,7 +30,7 @@ public class Attributed { * @return an unmodifiable view of the attribute map */ - public Map getAttributeMap() { + public Map getAttributes() { return Collections.unmodifiableMap(attributes); } @@ -51,7 +42,6 @@ public class Attributed { * @return the previous value of the attribute */ public String setAttribute(String key, String value) { - htmlString = null; return attributes.put(key, value); } @@ -154,7 +144,6 @@ public class Attributed { * @return the previously set description */ public String setDescription(String value) { - htmlString = null; return attributes.put(DESCRIPTION, value); } @@ -167,35 +156,4 @@ public class Attributed { return getAttribute(DESCRIPTION); } - /** - * parse (one time) then cache the attributes to html - * @return the html string - */ - public String getHtmlString() { - - if (htmlString != null) { - return htmlString; - } - - htmlString = getDescription(); - if (htmlString == null) { // if no description is set, create a default one - Set> entries = entrySet(); - if (entries.isEmpty()) { - return ""; // empty so tooltip clients can handle empty data - } - StringBuilder buf = new StringBuilder(); - for (Map.Entry entry : entries) { - buf.append(entry.getKey()); - buf.append(":"); - String value = entry.getValue(); - value = StringEscapeUtils.escapeHtml4(value); - String split = String.join("
    ", Splitter.on('\n').split(value)); - split = split.replaceAll("\\s", " "); - buf.append(split); - buf.append("
    "); - } - htmlString = buf.toString(); - } - return htmlString; - } } diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedEdge.java b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedEdge.java index 3b5a92482c..7f852cdc75 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedEdge.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedEdge.java @@ -19,6 +19,7 @@ package ghidra.service.graph; * Generic directed graph edge implementation */ public class AttributedEdge extends Attributed { + public static final String EDGE_TYPE_KEY = "EdgeType"; private final String id; /** @@ -62,4 +63,21 @@ public class AttributedEdge extends Attributed { return id.equals(other.id); } + /** + * Returns the edge type for this edge + * @return the edge type for this edge + */ + public String getEdgeType() { + return getAttribute(EDGE_TYPE_KEY); + } + + /** + * Sets the edge type for this edge. Should be a value defined by the {@link GraphType} for + * this graph, but there is no enforcement for this. If the value is not defined in GraphType, + * it will be rendered using the default edge color for {@link GraphType} + * @param edgeType the edge type for this edge + */ + public void setEdgeType(String edgeType) { + setAttribute(EDGE_TYPE_KEY, edgeType); + } } diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedGraph.java b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedGraph.java index 3b9bb49887..07fcd4344c 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedGraph.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedGraph.java @@ -33,29 +33,80 @@ import org.jgrapht.graph.DefaultGraphType; * to the same source/destination vertex pair. */ public class AttributedGraph extends AbstractBaseGraph { - private static final String WEIGHT = "Weight"; + public static final String WEIGHT = "Weight"; private Map vertexMap = new HashMap<>(); private final boolean collapseDuplicateEdges; + private String name; + + private GraphType type; + + private String description; + /** * Create a new empty AttributedGraph that automatically collapses duplicate edges + * + * @param name the name of the graph + * @param type the {@link GraphType} which defines valid vertex and edge types. */ - public AttributedGraph() { - this(true); + public AttributedGraph(String name, GraphType type) { + this(name, type, name, true); + } + + /** + * Create a new empty AttributedGraph that automatically collapses duplicate edges + * + * @param name the name of the graph + * @param type the {@link GraphType} which defines valid vertex and edge types. + * @param description a description of the graph + */ + public AttributedGraph(String name, GraphType type, String description) { + this(name, type, description, true); } /** * Create a new empty AttributedGraph. - * + * + * @param name the name of the graph + * @param type the {@link GraphType} which defines valid vertex and edge types. + * @param description a description of the graph * @param collapseDuplicateEdges if true, duplicate edges will be collapsed into a single * edge with a "Weight" attribute whose value is the number of edges between those vertices. */ - public AttributedGraph(boolean collapseDuplicateEdges) { + public AttributedGraph(String name, GraphType type, String description, + boolean collapseDuplicateEdges) { super(new VertexSupplier(), new EdgeSupplier(), DefaultGraphType.directedPseudograph()); + this.name = name; + this.type = type; + this.description = description; this.collapseDuplicateEdges = collapseDuplicateEdges; } + /** + * Returns the name of the graph + * @return the name of the graph + */ + public String getName() { + return name; + } + + /** + * Returns a description of the graph + * @return a description of the graph + */ + public String getDescription() { + return description; + } + + /** + * Returns the {@link GraphType} for this graph + * @return the {@link GraphType} for this graph + */ + public GraphType getGraphType() { + return type; + } + /** * Adds a new vertex with the given id. The vertex's name will be the same as the id. * If a vertex already exists with that id, diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedVertex.java b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedVertex.java index 529be98249..a377580082 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedVertex.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/AttributedVertex.java @@ -20,6 +20,8 @@ package ghidra.service.graph; */ public class AttributedVertex extends Attributed { + public static final String NAME_KEY = "Name"; + public static final String VERTEX_TYPE_KEY = "VertexType"; private final String id; /** @@ -43,7 +45,7 @@ public class AttributedVertex extends Attributed { * @param name the new name for the vertex */ public void setName(String name) { - setAttribute("Name", name); + setAttribute(NAME_KEY, name); } /** @@ -60,7 +62,7 @@ public class AttributedVertex extends Attributed { * @return the name of the vertex */ public String getName() { - return getAttribute("Name"); + return getAttribute(NAME_KEY); } @Override @@ -88,4 +90,22 @@ public class AttributedVertex extends Attributed { return id.equals(other.id); } + /** + * Returns the vertex type for this vertex + * @return the vertex type for this vertex + */ + public String getVertexType() { + return getAttribute(VERTEX_TYPE_KEY); + } + + /** + * Sets the vertex type for this vertex. Should be a value defined by the {@link GraphType} for + * this graph, but there is no enforcement for this. If the value is not defined in GraphType, + * it will be rendered using the default vertex shape and color for the {@link GraphType} + * @param vertexType the vertex type for this vertex + */ + public void setVertexType(String vertexType) { + setAttribute(VERTEX_TYPE_KEY, vertexType); + } + } diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/DefaultGraphDisplayOptions.java b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/DefaultGraphDisplayOptions.java new file mode 100644 index 0000000000..d5a9cfe9b7 --- /dev/null +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/DefaultGraphDisplayOptions.java @@ -0,0 +1,28 @@ +/* ### + * 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.service.graph; + +/** + * Empty implementation of GraphDiaplayOptions. Used as an initial default to avoid null + * checks + */ +public class DefaultGraphDisplayOptions extends GraphDisplayOptions { + + public DefaultGraphDisplayOptions() { + super(new EmptyGraphType()); + } + +} diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/EmptyGraphType.java b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/EmptyGraphType.java new file mode 100644 index 0000000000..ab90d149a5 --- /dev/null +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/EmptyGraphType.java @@ -0,0 +1,30 @@ +/* ### + * 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.service.graph; + +import java.util.Collections; + +/** + * Default GraphType implementation that has no vertex or edge types defined + */ +public class EmptyGraphType extends GraphType { + + public EmptyGraphType() { + super("Empty Graph Type", "Graph type with no defined vertex or edge types", + Collections.emptyList(), Collections.emptyList()); + } + +} diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/GraphDisplayOptions.java b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/GraphDisplayOptions.java new file mode 100644 index 0000000000..bf87eef867 --- /dev/null +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/GraphDisplayOptions.java @@ -0,0 +1,831 @@ +/* ### + * 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.service.graph; + +import java.awt.Color; +import java.awt.Font; +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; + +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +import org.apache.commons.text.StringEscapeUtils; + +import com.google.common.base.Splitter; + +import docking.Tool; +import docking.options.editor.*; +import ghidra.framework.options.*; +import ghidra.util.HelpLocation; +import ghidra.util.WebColors; +import ghidra.util.bean.opteditor.OptionsVetoException; + +/** + * Class for managing graph display options. This includes color options for each vertex + * and edge type and shapes for vertex types. + */ +public class GraphDisplayOptions implements OptionsChangeListener { + + public static final GraphDisplayOptions DEFAULT = + new GraphDisplayOptions(new EmptyGraphType()); + + private static final String FONT = "Font"; + private static final String LABEL_POSITION = "Label Position"; + private static final String USE_ICONS = "Use Icons"; + private static final String DEFAULT_LAYOUT_ALGORITHM = "Default Layout Algorithm"; + private static final String EDGE_COLORS = "Edge Colors"; + private static final String VERTEX_COLORS = "Vertex Colors"; + private static final String VERTEX_SHAPES = "Vertex Shapes"; + private static final String MISCELLANIOUS_OPTIONS = "Miscellanious"; + private static final String DEFAULT_VERTEX_COLOR = "Default Vertex Color"; + private static final String DEFAULT_EDGE_COLOR = "Default Edge Color"; + private static final String DEFAULT_VERTEX_SHAPE = "Default Vertex Shape"; + private static final String FAVORED_EDGE_TYPE = "Favored Edge Type"; + private static final String VERTEX_SELECTION_COLOR = "Selected Vertex Color"; + private static final String EDGE_SELECTION_COLOR = "Selected Edge Color"; + + private GraphType graphType; + + private Map vertexColorMap = new HashMap<>(); + private Map edgeColorMap = new HashMap<>(); + private Map vertexShapeMap = new HashMap<>(); + private Map edgePriorityMap = new HashMap<>(); + private List changeListeners = new CopyOnWriteArrayList<>(); + + private Color vertexSelectionColor = Color.green; + private Color edgeSelectionColor = Color.green; + private Color defaultVertexColor = Color.blue; + private Color defaultEdgeColor = Color.blue; + private String favoredEdgeType; + + private VertexShape defaultVertexShape = VertexShape.RECTANGLE; + private String vertexLabelOverride = null; + private String vertexColorOverride = null; + private String vertexShapeOverride = null; + private String edgeColorOverride = null; + private final String rootOptionsName; + private boolean registeredWithTool = false; + private String defaultLayoutAlgorithmName = LayoutAlgorithmNames.MIN_CROSS_COFFMAN_GRAHAM; + private boolean useIcons = true; + private GraphLabelPosition labelPosition = GraphLabelPosition.SOUTH; + private Font font = new Font("Dialog", Font.BOLD, 18); + private int arrowLength = 15; + + /** + * Constructs a new GraphTypeDisplayOptions for the given {@link GraphType} + * @param graphType The {@link GraphType} for which to define display options + */ + public GraphDisplayOptions(GraphType graphType) { + this(graphType, null); + } + + /** + * Constructs a new GraphTypeDisplayOptions for the given {@link GraphType} and initializes + * from tool options. Note this form should only be used for display options on + * {@link GraphType}s that have options registered in the tool. + * @param graphType The {@link GraphType} for which to define display options + * @param tool the tool from which to initialize from {@link ToolOptions} + */ + public GraphDisplayOptions(GraphType graphType, Tool tool) { + this.graphType = graphType; + rootOptionsName = graphType.getOptionsName(); + List edgeTypes = graphType.getEdgeTypes(); + if (!edgeTypes.isEmpty()) { + favoredEdgeType = edgeTypes.iterator().next(); + } + initializeEdgePriorities(); + initializeDefaults(); + initializeFromOptions(tool); + } + + private void initializeEdgePriorities() { + // initialize priorities based on the order they were defined + for (String edgeType : graphType.getEdgeTypes()) { + edgePriorityMap.put(edgeType, edgePriorityMap.size()); + } + } + + protected void initializeDefaults() { + // Overridden by subclass to define defaultValues + } + + /** + * Adds a ChangeListener to be notified when display options change + * @param listener the listener to be notified. + */ + public void addChangeListener(ChangeListener listener) { + changeListeners.add(listener); + } + + /** + * Removes the listener so that it won't be notified of changes any longer + * @param listener the listener to be removed + */ + public void removeChangeListener(ChangeListener listener) { + changeListeners.remove(listener); + } + + + + /** + * Sets the default shape to be used by vertices that don't have a vertex type set + * @param shape the default vertex shape + */ + public void setDefaultVertexShape(VertexShape shape) { + this.defaultVertexShape = Objects.requireNonNull(shape); + } + + /** + * Sets the default color to be used by vertices that don't have a vertex type set + * @param color the default vertex shape + */ + public void setDefaultVertexColor(Color color) { + this.defaultVertexColor = Objects.requireNonNull(color); + } + + /** + * Sets the default color to be used by edges that don't have a edge type set + * @param color the default edge shape + */ + public void setDefaultEdgeColor(Color color) { + this.defaultEdgeColor = Objects.requireNonNull(color); + } + + /** + * Returns the default color for edges that don't have an edge type set + * @return the default color for edges that don't have an edge type set + */ + public Color getDefaultEdgeColor() { + return defaultEdgeColor; + } + + /** + * Returns the default color for vertices that don't have an vertex type set + * @return the default color for vertices that don't have an vertex type set + */ + public Color getDefaultVertexColor() { + return defaultVertexColor; + } + + /** + * Sets the attribute key that can be used to override the label text shown for the vertex. + * Normally, the vertex's name is shown as the label. + * @param attributeKey the attribute key that, if set, will be used to define the vertice's label + */ + public void setVertexLabelOverrideAttributeKey(String attributeKey) { + vertexLabelOverride = attributeKey; + } + + /** + * Returns the attribute key that can override the vertices label text + * @return the attribute key that can override the vertices label text + */ + public String getVertexLabelOverride() { + return vertexLabelOverride; + } + + /** + * Sets the attribute key that can be used to override the color for a vertex. Normally, the + * color is determined by the vertex type, which will be mapped to a color + * @param attributeKey the attribute key that, if set, will be used to define the vertice's color + */ + public void setVertexColorOverrideAttributeKey(String attributeKey) { + vertexColorOverride = attributeKey; + } + + /** + * Sets the attribute key that can be used to override the color for an edge. Normally, the + * color is determined by the edge type, which will be mapped to a color + * @param attributeKey the attribute key that, if set, will be used to define the edge's color + */ + public void setEdgeColorOverrideAttributeKey(String attributeKey) { + edgeColorOverride = attributeKey; + } + + /** + * Returns the attribute key that can be used to override the color of an edge + * @return the attribute key that can be used to override the color of an edge + */ + public String getEdgeColorOverrideAttributeKey() { + return edgeColorOverride; + } + + /** + * Sets the attribute key that can be used to override the shape for a vertex. Normally, the + * shape is determined by the vertex type, which will be mapped to a shape + * @param attributeKey the attribute key that, if set, will be used to define the vertice's shape + */ + public void setVertexShapeOverrideAttributeKey(String attributeKey) { + vertexShapeOverride = attributeKey; + } + + /** + * Returns the text that will be displayed as the label for the given vertex + * @param vertex the vertex for which to get label text + * @return the text that will be displayed as the label for the given vertex + */ + public String getVertexLabel(AttributedVertex vertex) { + String vertexLabel = null; + + if (vertexLabelOverride != null) { + vertexLabel = vertex.getAttribute(vertexLabelOverride); + } + + if (vertexLabel == null) { + vertexLabel = vertex.getName(); + } + + if (vertexLabel.contains("\n")) { + vertexLabel = StringEscapeUtils.escapeHtml4(vertexLabel); + return "" + String.join("

    ", Splitter.on('\n').split(vertexLabel)); + } + return vertexLabel; + } + + /** + * Returns the {@link VertexShape} that will be used to draw the vertex's shape + * @param vertex the vertex for which to get the shape + * @return the {@link VertexShape} that will be used to draw the vertex's shape + */ + public VertexShape getVertexShape(AttributedVertex vertex) { + if (vertexShapeOverride != null) { + String shapeName = vertex.getAttribute(vertexShapeOverride); + if (shapeName != null) { + VertexShape shape = VertexShape.getShape(shapeName); + if (shape != null) { + return shape; + } + } + } + String vertexType = vertex.getVertexType(); + return vertexShapeMap.getOrDefault(vertexType, defaultVertexShape); + } + + /** + * Returns the color that will be used to draw the vertex + * @param vertex the vertex for which to get the color + * @return the color that will be used to draw the vertex + */ + public Color getVertexColor(AttributedVertex vertex) { + if (vertexColorOverride != null) { + String colorValue = vertex.getAttribute(vertexColorOverride); + if (colorValue != null) { + Color color = WebColors.getColor(colorValue); + if (color != null) { + return color; + } + } + } + + String vertexType = vertex.getVertexType(); + return vertexColorMap.getOrDefault(vertexType, defaultVertexColor); + } + + /** + * Returns the color that will be used to draw the edge + * @param edge the edge for which to get the color + * @return the color that will be used to draw the edge + */ + public Color getEdgeColor(AttributedEdge edge) { + if (edgeColorOverride != null) { + String colorValue = edge.getAttribute(edgeColorOverride); + if (colorValue != null) { + Color color = WebColors.getColor(colorValue); + if (color != null) { + return color; + } + } + } + + String edgeType = edge.getEdgeType(); + return edgeColorMap.getOrDefault(edgeType, defaultEdgeColor); + } + + /** + * Returns the priority for the given edge type. This is used by layout algorithms to + * determine which edges should have more influence on the layout. + * @param edgeType the edge type for which to get it's priority + * @return the priority for the given edge type + */ + public Integer getEdgePriority(String edgeType) { + return edgePriorityMap.getOrDefault(edgeType, Integer.MAX_VALUE); + } + + /** + * Returns the edge type that is the preferred edge for layout purposes + * @return the edge type that is the preferred edge for layout purposes + */ + public String getFavoredEdgeType() { + return favoredEdgeType; + } + + /** + * Sets the favored edge type. The favored edge type is used to influence layout algorithms + * @param favoredEdgeType the edge type that is to be favored by layout algorithms + */ + public void setFavoredEdgeType(String favoredEdgeType) { + checkEdgeType(favoredEdgeType); + this.favoredEdgeType = favoredEdgeType; + } + + /** + * Returns the {@link GraphType} that this object provides display options for + * @return the {@link GraphType} that this object provides display options for + */ + public GraphType getGraphType() { + return graphType; + } + + /** + * Returns the color for the given vertex type + * @param vertexType the vertex type to get the color for + * @return the color for the given vertex type + */ + public Color getVertexColor(String vertexType) { + return vertexColorMap.getOrDefault(vertexType, defaultVertexColor); + } + + /** + * Sets the color for vertices with the given vertex type + * @param vertexType the vertex type for which to set its color + * @param color the color to use for vertices with the given vertex type + */ + public void setVertexColor(String vertexType, Color color) { + checkVertexType(vertexType); + vertexColorMap.put(vertexType, Objects.requireNonNull(color)); + } + + private String getVertexShapeName(String vertexType) { + VertexShape vertexShape = vertexShapeMap.getOrDefault(vertexType, defaultVertexShape); + return vertexShape.getName(); + } + + /** + * Sets the {@link VertexShape} to use for vertices with the given vertex type + * @param vertexType the vertex type for which to set its shape + * @param vertexShape the {@link VertexShape} to use for vertices with the given vertex type + */ + public void setVertexShape(String vertexType, VertexShape vertexShape) { + checkVertexType(vertexType); + vertexShapeMap.put(vertexType, Objects.requireNonNull(vertexShape)); + } + + + /** + * Returns the color for the given edge type + * @param edgeType the edge type whose color is to be determined. + * @return the color for the given edge type. + */ + public Color getEdgeColor(String edgeType) { + return edgeColorMap.getOrDefault(edgeType, defaultEdgeColor); + } + + /** + * Sets the color for edges with the given edge type + * @param edgeType the edge type for which to set its color + * @param color the new color for edges with the given edge type + */ + public void setEdgeColor(String edgeType, Color color) { + checkEdgeType(edgeType); + edgeColorMap.put(edgeType, Objects.requireNonNull(color)); + } + + @Override + public void optionsChanged(ToolOptions options, String optionName, Object oldValue, + Object newValue) throws OptionsVetoException { + if (optionName.startsWith(rootOptionsName)) { + updateOptions(options.getOptions(rootOptionsName)); + } + notifyListeners(); + } + + /** + * Returns the name for the root Options name for this {@link GraphDisplayOptions} + * @return the name for the root Options name for this {@link GraphDisplayOptions} + */ + public String getRootOptionsName() { + return rootOptionsName; + } + + /** + * Returns the attribute key that can be used to override the color of a vertex. Normally, + * a vertex is colored based on its vertex type. However, if this value is non-null, a vertex + * can override its color by setting an attribute using this key name. + * @return the attribute key that can be used to override the color of a vertex + */ + public String getVertexColorOverrideAttributeKey() { + return vertexColorOverride; + } + + /** + * Returns the attribute key that can be used to override the shape of a vertex. Normally, + * a vertex has a shape based on its vertex type. However, if this value is non-null, a vertex + * can override its shape by setting an attribute using this key name. + * @return the attribute key that can be used to override the shape of a vertex + */ + public String getVertexShapeOverrideAttributeKey() { + return vertexShapeOverride; + } + + /** + * returns the {@link VertexShape} for any vertex that has not vertex type defined + * @return the {@link VertexShape} for any vertex that has not vertex type defined + */ + public VertexShape getDefaultVertexShape() { + return defaultVertexShape; + } + + /** + * Returns the {@link VertexShape} for vertices that have the given vertex type + * @param vertexType the vertex type for which to get its asigned shape + * @return the {@link VertexShape} for vertices that have the given vertex type + */ + public VertexShape getVertexShape(String vertexType) { + return vertexShapeMap.getOrDefault(vertexType, defaultVertexShape); + } + + /** + * Returns the vertex selection color + * @return the vertex selection color + */ + public Color getVertexSelectionColor() { + return vertexSelectionColor; + } + + /** + * Sets the vertex selection color + * @param vertexSelectionColor the color to use for highlighting selected vertices + */ + public void setVertexSelectionColor(Color vertexSelectionColor) { + this.vertexSelectionColor = vertexSelectionColor; + } + + /** + * Returns the color for edge selections + * @return the color fore edge selections + */ + public Color getEdgeSelectionColor() { + return edgeSelectionColor; + } + + /** + * Sets the edge selection color + * @param edgeSelectionColor color to use for highlighting selected edges + */ + public void setEdgeSelectionColor(Color edgeSelectionColor) { + this.edgeSelectionColor = edgeSelectionColor; + } + + /** + * Returns the name of the default graph layout algorithm + * @return the name of the default graph layout algorithms + */ + public String getDefaultLayoutAlgorithmNameLayout() { + return defaultLayoutAlgorithmName; + } + + /** + * Sets the name of the default layout algorithm + * @param defaultLayout the name of the layout algorithm to use by default + */ + public void setDefaultLayoutAlgorithmName(String defaultLayout) { + this.defaultLayoutAlgorithmName = defaultLayout; + } + + /** + * Returns true if the rendering mode is to use icons for the vertices. If using + * icons, the label is drawn inside the shape. + * @return true if the rendering mode is to use icons. + */ + public boolean usesIcons() { + return useIcons; + } + + /** + * Sets whether the graph rendering mode is to use icons or not. If using icons, the label and + * shape are drawn together into a cached icon. Otherwise, the shapes are drawn on the fly and + * labeled separately. + * @param b true to render in icon mode. + */ + public void setUsesIcons(boolean b) { + this.useIcons = b; + } + + /** + * Returns the label position relative to the vertex. Note this is only relevant + * if {@link #usesIcons()} is false + * @return the label position relative to the vertex + */ + public GraphLabelPosition getLabelPosition() { + return labelPosition; + } + + /** + * Sets the label position relative to the vertex. Note this is only relevant + * if {@link #usesIcons()} is false. + * @param labelPosition the {@link GraphLabelPosition} to use for rendering vertex labels + */ + public void setLabelPosition(GraphLabelPosition labelPosition) { + this.labelPosition = labelPosition; + } + + /** + * Sets the font to use for drawing vertex labels + * @param font the font to use for drawing vertex labels + */ + public void setFont(Font font) { + this.font = font; + } + + /** + * Returns the font being used to render vertex labels + * @return the font being used to render vertex labels + */ + public Font getFont() { + return font; + } + + /** + * Returns the length of the arrow. The width will be proportional to the length. + * Note: this option is not exposed in the Options because it is too specific to a graph + * instance and wouldn't be appropriate to apply to shared options. + * @return the size if the arrow + */ + public int getArrowLength() { + return arrowLength; + } + + /** + * Sets the length of the arrow. The width will be proportional to the length. + * Note: this option is not exposed in the Options because it is too specific to a graph + * instance and wouldn't be appropriate to apply to shared options. + * @param length the size of the arrow + */ + public void setArrowLength(int length) { + this.arrowLength = length; + } + + /** + * Returns true if this {@link GraphDisplayOptions} instance has been constructed with + * a tool for getting/saving option values in the tool options + * @return true if this {@link GraphDisplayOptions} instance is connected to tool options + */ + public boolean isRegisteredWithTool() { + return registeredWithTool; + } + + /** + * Registers this GraphTypeDisplayOptions with {@link ToolOptions}. Note: this should only + * be used by plugins or other objects that get instantiated immediately when the tool is + * constructed. Otherwise, if the tool exits and this hasn't been called, any saved option + * values will be lost. + *

    + * @param toolOptions the {@link ToolOptions} to register these options with + * @param help the help location to be used by the {@link OptionsDialog} for display/editing + * these options + */ + public void registerOptions(ToolOptions toolOptions, HelpLocation help) { + Options rootOptions = toolOptions.getOptions(graphType.getOptionsName()); + registerVertexColorOptions(rootOptions, help); + registerVertexShapeOptions(rootOptions, help); + registerEdgeColorOptions(rootOptions, help); + registerMiscellaniousOptions(rootOptions, help); + } + + /** + * Sets default values for vertex types + * @param vertexType the vertex type whose default color and shape are being defined + * @param vertexShape the default vertex shape for the given vertex type + * @param color the default color for the given vertex type + */ + protected void configureVertexType(String vertexType, VertexShape vertexShape, Color color) { + checkVertexType(vertexType); + vertexShapeMap.put(vertexType, vertexShape); + vertexColorMap.put(vertexType, color); + } + + /** + * Sets default values for edge types + * @param edgeType the edge type whose default color and shape are being defined + * @param color the default color for the given edge type + */ + protected void configureEdgeType(String edgeType, Color color) { + checkEdgeType(edgeType); + edgeColorMap.put(edgeType, color); + } + + /** + * Loads values from tool options + * + * @param tool the tool from which to update values. + */ + public void initializeFromOptions(Tool tool) { + if (tool == null) { + return; + } + ToolOptions toolOptions = tool.getOptions("Graph"); + toolOptions.addOptionsChangeListener(this); + updateOptions(toolOptions.getOptions(rootOptionsName)); + registeredWithTool = true; + } + + + private void updateOptions(Options rootOptions) { + updateVertexColorsFromOptions(rootOptions); + updateEdgeColorsFromOptions(rootOptions); + updateVertexShapesFromOptions(rootOptions); + updateMiscellaniousOptions(rootOptions); + } + + private void updateMiscellaniousOptions(Options rootOptions) { + Options options = rootOptions.getOptions(MISCELLANIOUS_OPTIONS); + String shapeName = options.getString(DEFAULT_VERTEX_SHAPE, defaultVertexShape.getName()); + defaultVertexShape = VertexShape.getShape(shapeName); + + defaultVertexColor = options.getColor(DEFAULT_VERTEX_COLOR, defaultVertexColor); + defaultEdgeColor = options.getColor(DEFAULT_EDGE_COLOR, defaultEdgeColor); + favoredEdgeType = options.getString(FAVORED_EDGE_TYPE, favoredEdgeType); + + vertexSelectionColor = options.getColor(VERTEX_SELECTION_COLOR, vertexSelectionColor); + edgeSelectionColor = options.getColor(EDGE_SELECTION_COLOR, edgeSelectionColor); + + defaultLayoutAlgorithmName = + options.getString(DEFAULT_LAYOUT_ALGORITHM, defaultLayoutAlgorithmName); + + useIcons = options.getBoolean(USE_ICONS, useIcons); + labelPosition = options.getEnum(LABEL_POSITION, labelPosition); + font = options.getFont(FONT, font); + } + + private void updateVertexShapesFromOptions(Options rootOptions) { + Options options = rootOptions.getOptions(VERTEX_SHAPES); + for (String vertexType : graphType.getVertexTypes()) { + String current = getVertexShapeName(vertexType); + String shapeName = options.getString(vertexType, current); + if (shapeName != null && !shapeName.equals(current)) { + VertexShape shape = VertexShape.getShape(shapeName); + if (shape != null) { + setVertexShape(vertexType, VertexShape.getShape(shapeName)); + } + } + } + } + + private void updateEdgeColorsFromOptions(Options rootOptions) { + Options options = rootOptions.getOptions(EDGE_COLORS); + for (String edgeType : graphType.getEdgeTypes()) { + Color current = getEdgeColor(edgeType); + Color color = options.getColor(edgeType, current); + if (color != null && !color.equals(current)) { + setEdgeColor(edgeType, color); + } + } + } + + private void notifyListeners() { + for (ChangeListener changeListener : changeListeners) { + changeListener.stateChanged(new ChangeEvent(this)); + } + } + + private void updateVertexColorsFromOptions(Options rootOptions) { + Options options = rootOptions.getOptions(VERTEX_COLORS); + for (String vertexType : graphType.getVertexTypes()) { + Color current = getVertexColor(vertexType); + Color color = options.getColor(vertexType, current); + if (color != null && !color.equals(current)) { + setVertexColor(vertexType, color); + } + } + } + + + private void registerVertexColorOptions(Options rootOptions, HelpLocation help) { + Options options = rootOptions.getOptions(VERTEX_COLORS); + + for (String vertexType : graphType.getVertexTypes()) { + options.registerOption(vertexType, OptionType.COLOR_TYPE, + getVertexColor(vertexType), help, + "Choose the color for this vertex type"); + } + List list = new ArrayList<>(graphType.getVertexTypes()); + OptionsEditor editor = new ScrollableOptionsEditor(VERTEX_COLORS, list); + options.registerOptionsEditor(editor); + } + + private void registerVertexShapeOptions(Options rootOptions, HelpLocation help) { + Options options = rootOptions.getOptions(VERTEX_SHAPES); + + List shapeNames = VertexShape.getShapeNames(); + + for (String vertexType : graphType.getVertexTypes()) { + StringWithChoicesEditor editor = new StringWithChoicesEditor(shapeNames); + options.registerOption(vertexType, OptionType.STRING_TYPE, + getVertexShapeName(vertexType), help, + "Choose the shape for this vertex type", editor); + } + List list = new ArrayList<>(graphType.getVertexTypes()); + OptionsEditor editor = new ScrollableOptionsEditor(VERTEX_SHAPES, list); + options.registerOptionsEditor(editor); + } + + private void registerEdgeColorOptions(Options rootOptions, HelpLocation help) { + Options options = rootOptions.getOptions(EDGE_COLORS); + + for (String edgeType : graphType.getEdgeTypes()) { + options.registerOption(edgeType, OptionType.COLOR_TYPE, + getEdgeColor(edgeType), help, "Choose the color for this edge type"); + } + List list = new ArrayList<>(graphType.getEdgeTypes()); + OptionsEditor editor = new ScrollableOptionsEditor(EDGE_COLORS, list); + options.registerOptionsEditor(editor); + } + + private void registerMiscellaniousOptions(Options rootOptions, HelpLocation help) { + + Options options = rootOptions.getOptions(MISCELLANIOUS_OPTIONS); + + StringWithChoicesEditor editor = new StringWithChoicesEditor(VertexShape.getShapeNames()); + + options.registerOption(VERTEX_SELECTION_COLOR, OptionType.COLOR_TYPE, vertexSelectionColor, + help, "Color for highlighting selected vertices"); + + options.registerOption(EDGE_SELECTION_COLOR, OptionType.COLOR_TYPE, edgeSelectionColor, + help, "Color for highlighting selected edge"); + + options.registerOption(DEFAULT_VERTEX_SHAPE, OptionType.STRING_TYPE, + defaultVertexShape.getName(), + help, "Shape for vertices that have no vertex type defined", editor); + + options.registerOption(DEFAULT_VERTEX_COLOR, OptionType.COLOR_TYPE, defaultVertexColor, + help, "Color for vertices that have no vertex type defined"); + + options.registerOption(DEFAULT_EDGE_COLOR, OptionType.COLOR_TYPE, defaultEdgeColor, + help, "Color for edge that have no edge type defined"); + + editor = new StringWithChoicesEditor(graphType.getEdgeTypes()); + options.registerOption(FAVORED_EDGE_TYPE, OptionType.STRING_TYPE, favoredEdgeType, help, + "Favored edge is used to influence layout algorithms", editor); + + editor = new StringWithChoicesEditor(LayoutAlgorithmNames.getLayoutAlgorithmNames()); + options.registerOption(DEFAULT_LAYOUT_ALGORITHM, OptionType.STRING_TYPE, + defaultLayoutAlgorithmName, help, "Initial layout algorithm", editor); + + options.registerOption(USE_ICONS, OptionType.BOOLEAN_TYPE, useIcons, help, + "If true, vertices are drawn using pre-rendered images versus compact shapes"); + + options.registerOption(LABEL_POSITION, OptionType.ENUM_TYPE, labelPosition, help, + "Relative postion of labels to vertex shape (Only applicable if \"Use Icons\" is true"); + + options.registerOption(FONT, OptionType.FONT_TYPE, font, help, + "Font to use for vertex labels"); + + List optionNamesInDisplayOrder = new ArrayList<>(); + + optionNamesInDisplayOrder.add(VERTEX_SELECTION_COLOR); + optionNamesInDisplayOrder.add(EDGE_SELECTION_COLOR); + optionNamesInDisplayOrder.add(DEFAULT_VERTEX_COLOR); + optionNamesInDisplayOrder.add(DEFAULT_EDGE_COLOR); + optionNamesInDisplayOrder.add(DEFAULT_VERTEX_SHAPE); + optionNamesInDisplayOrder.add(FAVORED_EDGE_TYPE); + optionNamesInDisplayOrder.add(DEFAULT_LAYOUT_ALGORITHM); + optionNamesInDisplayOrder.add(LABEL_POSITION); + optionNamesInDisplayOrder.add(FONT); + optionNamesInDisplayOrder.add(USE_ICONS); + + + OptionsEditor optionsEditor = + new ScrollableOptionsEditor(MISCELLANIOUS_OPTIONS, optionNamesInDisplayOrder); + options.registerOptionsEditor(optionsEditor); + + } + + private void checkVertexType(String vertexType) { + if (!getGraphType().containsVertexType(vertexType)) { + throw new IllegalArgumentException("VertexType \"" + vertexType + + "\" not defined in GraphType \"" + getGraphType().getName() + "\"."); + } + } + + private void checkEdgeType(String edgeType) { + if (!getGraphType().containsEdgeType(edgeType)) { + throw new IllegalArgumentException("EdgeType \"" + edgeType + + "\" not defined in GraphType \"" + getGraphType().getName() + "\"."); + } + } +} + diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/GraphDisplayOptionsBuilder.java b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/GraphDisplayOptionsBuilder.java new file mode 100644 index 0000000000..809d2f0ee3 --- /dev/null +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/GraphDisplayOptionsBuilder.java @@ -0,0 +1,193 @@ +/* ### + * 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.service.graph; + +import java.awt.Color; +import java.util.Objects; + +/** + * Builder for building {@link GraphDisplayOptions} + */ +public class GraphDisplayOptionsBuilder { + + private GraphDisplayOptions displayOptions; + + /** + * Create a new {@link GraphDisplayOptionsBuilder} + * @param graphType the {@link GraphType} of graphs that this instance configures. + */ + public GraphDisplayOptionsBuilder(GraphType graphType) { + displayOptions = new GraphDisplayOptions(graphType); + } + + /** + * Sets the default vertex color for vertexes that don't have a registered vertex type + * @param c the default vertex color + * @return this {@link GraphDisplayOptionsBuilder} + */ + public GraphDisplayOptionsBuilder defaultVertexColor(Color c) { + displayOptions.setDefaultVertexColor(c); + return this; + } + + /** + * Sets the default edge color for edges that don't have a registered edge type + * @param c the default edge color + * @return this {@link GraphDisplayOptionsBuilder} + */ + public GraphDisplayOptionsBuilder defaultEdgeColor(Color c) { + Objects.requireNonNull(c); + displayOptions.setDefaultEdgeColor(c); + return this; + } + + /** + * Sets the vertex selection color + * @param color the vertex selection color + * @return this {@link GraphDisplayOptionsBuilder} + */ + public GraphDisplayOptionsBuilder vertexSelectionColor(Color color) { + displayOptions.setVertexSelectionColor(color); + return this; + } + + /** + * Sets the edge selection color + * @param color the edge selection color + * @return this {@link GraphDisplayOptionsBuilder} + */ + public GraphDisplayOptionsBuilder edgeSelectionColor(Color color) { + displayOptions.setEdgeSelectionColor(color); + return this; + } + + /** + * Sets the default vertex shape for vertices that don't have a registered vertex type + * @param vertexShape the {@link VertexShape} to use as a default + * @return this {@link GraphDisplayOptionsBuilder} + */ + public GraphDisplayOptionsBuilder defaultVertexShape(VertexShape vertexShape) { + Objects.requireNonNull(vertexShape); + displayOptions.setDefaultVertexShape(vertexShape); + return this; + } + + /** + * Sets the shape and color for vertices of the given type + * @param vertexType the vertex type to assign shape and color + * @param vertexShape the shape to use for the named vertex type + * @param color the color to use for the named vertex type + * @return this {@link GraphDisplayOptionsBuilder} + */ + public GraphDisplayOptionsBuilder vertex(String vertexType, VertexShape vertexShape, + Color color) { + displayOptions.configureVertexType(vertexType, vertexShape, color); + return this; + } + + /** + * Sets the color for edges of the given type + * @param edgeType the edge type to assign color + * @param color the color to use for the named edge type + * @return this {@link GraphDisplayOptionsBuilder} + */ + public GraphDisplayOptionsBuilder edge(String edgeType, Color color) { + displayOptions.configureEdgeType(edgeType, color); + return this; + } + + /** + * Sets the attribute used to override the color for a vertex + * @param colorAttributeKey the attribute key to use for overriding a vertex color + * @return this {@link GraphDisplayOptionsBuilder} + */ + public GraphDisplayOptionsBuilder vertexColorOverrideAttribute(String colorAttributeKey) { + displayOptions.setVertexColorOverrideAttributeKey(colorAttributeKey); + return this; + } + + /** + * Sets the attribute used to override the color for a edge + * @param colorAttributeKey the attribute key to use for overriding an edge color + * @return this {@link GraphDisplayOptionsBuilder} + */ + public GraphDisplayOptionsBuilder edgeColorOverrideAttribute(String colorAttributeKey) { + displayOptions.setEdgeColorOverrideAttributeKey(colorAttributeKey); + return this; + } + + /** + * Sets the attribute used to override the shape for a vertex + * @param shapeAttributeKey the attribute key to use of shape override + * @return this {@link GraphDisplayOptionsBuilder} + */ + public GraphDisplayOptionsBuilder shapeOverrideAttribute(String shapeAttributeKey) { + displayOptions.setVertexShapeOverrideAttributeKey(shapeAttributeKey); + return this; + } + + /** + * Sets the name of the layout algorithm that will be used to initially layout the graph + * @param string the name of the layout algoritm to use to initially layout the graph + * @return this {@link GraphDisplayOptionsBuilder} + */ + public GraphDisplayOptionsBuilder defaultLayoutAlgorithm(String string) { + displayOptions.setDefaultLayoutAlgorithmName(string); + return this; + } + + /** + * Sets drawing "mode" for the graph display. If true, vertices are drawn as scaled + * cached images with the label inside the shapes. If false, vertices are drawn as smaller + * shapes with labels drawn near the shapes. + * @param b true to use pre-rendered icon images + * @return this {@link GraphDisplayOptionsBuilder} + */ + public GraphDisplayOptionsBuilder useIcons(boolean b) { + displayOptions.setUsesIcons(b); + return this; + } + + /** + * Sets the length of the arrows to display in the graph. The width will be sized proportionately. + * @param length the length the arrows to display in the graph + * @return this {@link GraphDisplayOptionsBuilder} + */ + public GraphDisplayOptionsBuilder arrowLength(int length) { + displayOptions.setArrowLength(length); + return this; + } + + /** + * Sets the vertex label position relative to vertex shape. This is only applicable if the + * {@link #useIcons(boolean)} is set to true. + * @param labelPosition the relative position to place the vertex label + * @return this {@link GraphDisplayOptionsBuilder} + */ + public GraphDisplayOptionsBuilder labelPosition(GraphLabelPosition labelPosition) { + displayOptions.setLabelPosition(labelPosition); + return this; + } + + /** + * Returns a GraphTypeDisplayOptions as configured by this builder + * @return a GraphTypeDisplayOptions as configured by this builder + */ + public GraphDisplayOptions build() { + return displayOptions; + } + +} diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/GraphLabelPosition.java b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/GraphLabelPosition.java new file mode 100644 index 0000000000..6cae6e1ad0 --- /dev/null +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/GraphLabelPosition.java @@ -0,0 +1,23 @@ +/* ### + * 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.service.graph; + +/** + * Specification for the vertex label position relative to the vertex shape. + */ +public enum GraphLabelPosition { + NORTH, NORTHEAST, EAST, SOUTHEAST, SOUTH, SOUTHWEST, WEST, NORTHWEST, CENTER +} diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/GraphType.java b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/GraphType.java new file mode 100644 index 0000000000..16d92bf6aa --- /dev/null +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/GraphType.java @@ -0,0 +1,140 @@ +/* ### + * 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.service.graph; + +import java.util.*; + +/** + * Class that defines a new graph type. It defines the set of valid vertex and edge types + */ +public class GraphType { + private final String name; + private final String description; + + private final Set vertexTypes; + private final Set edgeTypes; + + /** + * Constructs a new GraphType + * + * @param name the name of this GraphType instance + * @param description a brief description for graphs of this type + * @param vertexTypes a list of all valid vertex types for graphs of this type + * @param edgeTypes a list of all valid edge types for graphs of this type + */ + public GraphType(String name, String description, List vertexTypes, + List edgeTypes) { + this.name = Objects.requireNonNull(name); + this.description = Objects.requireNonNull(description); + + this.vertexTypes = Collections.unmodifiableSet(new LinkedHashSet(vertexTypes)); + this.edgeTypes = Collections.unmodifiableSet(new LinkedHashSet(edgeTypes)); + } + + /** + * Returns a name for this type of graph + * @return a name of this type of graph + */ + public String getName() { + return name; + } + + /** + * Returns a description for this type of graph + * @return a description for this type of graph + */ + public String getDescription() { + return description; + } + + /** + * Returns a list of valid vertex types for graphs of this type + * @return a list of valid vertex types for graphs of this type + */ + public List getVertexTypes() { + return new ArrayList<>(vertexTypes); + } + + /** + * Returns a list of valid edge types for graphs of this type + * @return a list of valid edge types for graphs of this type + */ + public List getEdgeTypes() { + return new ArrayList<>(edgeTypes); + } + + /** + * Test if the given string is a valid vertex type + * @param vertexType the string to test for being a valid vertex type + * @return true if the given string is a valid vertex type + */ + public boolean containsVertexType(String vertexType) { + return vertexTypes.contains(vertexType); + } + + /** + * Test if the given string is a valid edge type + * @param edgeType the string to test for being a valid edge type + * @return true if the given string is a valid edge type + */ + public boolean containsEdgeType(String edgeType) { + return edgeTypes.contains(edgeType); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((description == null) ? 0 : description.hashCode()); + result = prime * result + ((edgeTypes == null) ? 0 : edgeTypes.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((vertexTypes == null) ? 0 : vertexTypes.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + GraphType other = (GraphType) obj; + + if (!name.equals(other.name)) { + return false; + } + if (!description.equals(other.description)) { + return false; + } + if (!edgeTypes.equals(other.edgeTypes)) { + return false; + } + if (!vertexTypes.equals(other.vertexTypes)) { + return false; + } + return true; + } + + public String getOptionsName() { + return getName() + " Graph Type"; + } + +} diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/GraphTypeBuilder.java b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/GraphTypeBuilder.java new file mode 100644 index 0000000000..dca4dbae1e --- /dev/null +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/GraphTypeBuilder.java @@ -0,0 +1,76 @@ +/* ### + * 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.service.graph; + +import java.util.ArrayList; +import java.util.List; + +/** + * Builder class for building new {@link GraphType}s + */ +public class GraphTypeBuilder { + private List vertexTypes = new ArrayList<>(); + private List edgeTypes = new ArrayList<>(); + private final String name; + private String description; + + /** + * Create a new builder + * @param name the name of the new {@link GraphType} + */ + public GraphTypeBuilder(String name) { + this.name = name; + this.description = name; + } + + /** + * Sets the description for the {@link GraphType} + * @param text the description + * @return this GraphTypeBuilder + */ + public GraphTypeBuilder description(String text) { + this.description = text; + return this; + } + + /** + * Defines a new vertex type + * @param type a string that names a new vertex type + * @return this GraphTypeBuilder + */ + public GraphTypeBuilder vertexType(String type) { + vertexTypes.add(type); + return this; + } + + /** + * Defines a new edge type + * @param type a string that names a new edge type + * @return this GraphTypeBuilder + */ + public GraphTypeBuilder edgeType(String type) { + edgeTypes.add(type); + return this; + } + + /** + * Builds a new GraphType + * @return a new GraphType + */ + public GraphType build() { + return new GraphType(name, description, vertexTypes, edgeTypes); + } +} diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/LayoutAlgorithmNames.java b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/LayoutAlgorithmNames.java new file mode 100644 index 0000000000..8153243f7b --- /dev/null +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/LayoutAlgorithmNames.java @@ -0,0 +1,56 @@ +/* ### + * 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.service.graph; + +import java.util.Arrays; +import java.util.List; + +/** + * Just a static list of graph layout algorithm names + */ +public class LayoutAlgorithmNames { + //@formatter:off + public static final String FORCED_BALANCED = "Force Balanced"; + public static final String FORCE_DIRECTED = "Force Directed"; + public static final String CIRCLE = "Circle"; + public static final String COMPACT_HIERACHICAL = "Compact Hierarchical"; + public static final String COMPACT_RADIAL = "Compact Radial"; + public static final String MIN_CROSS_TOP_DOWN = "Hierarchical MinCross Top Down"; + public static final String MIN_CROSS_LONGEST_PATH = "Hierarchical MinCross Longest Path"; + public static final String MIN_CROSS_NETWORK_SIMPLEX = "Hierarchical MinCross Network Simplex"; + public static final String MIN_CROSS_COFFMAN_GRAHAM = "Hierarchical MinCross Coffman Graham"; + public static final String VERT_MIN_CROSS_TOP_DOWN = "Vertical Hierarchical MinCross Top Down"; + public static final String VERT_MIN_CROSS_LONGEST_PATH ="Vertical Hierarchical MinCross Longest Path"; + public static final String VERT_MIN_CROSS_NETWORK_SIMPLEX ="Vertical Hierarchical MinCross Network Simplex"; + public static final String VERT_MIN_CROSS_COFFMAN_GRAHAM ="Vertical Hierarchical MinCross Coffman Graham"; + public static final String HIERACHICAL = "Hierarchical"; + public static final String RADIAL = "Radial"; + public static final String BALLOON = "Balloon"; + public static final String GEM = "GEM"; + + //@formatter:on + + public static List getLayoutAlgorithmNames() { + return Arrays.asList(COMPACT_HIERACHICAL, HIERACHICAL, + COMPACT_RADIAL, 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, + FORCED_BALANCED, FORCE_DIRECTED, RADIAL, BALLOON, GEM); + } +} diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/VertexShape.java b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/VertexShape.java new file mode 100644 index 0000000000..31ad5bfd25 --- /dev/null +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/service/graph/VertexShape.java @@ -0,0 +1,339 @@ +/* ### + * 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.service.graph; + +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.geom.*; +import java.util.*; + +/** + * Class for defining shapes to use for rendering vertices in a graph + */ +public abstract class VertexShape { + private static Map registeredShapes = new HashMap<>(); + private static int SIZE = 50; + + public static VertexShape RECTANGLE = new RectangleVertexShape(SIZE); + public static VertexShape ELLIPSE = new EllipseVertexShape(SIZE); + public static VertexShape TRIANGLE_UP = new TriangleUpVertexShape(SIZE); + public static VertexShape TRIANGLE_DOWN = new TriangleDownVertexShape(SIZE); + public static VertexShape STAR = new StarVertexShape(SIZE); + public static VertexShape DIAMOND = new DiamondVertexShape(SIZE); + public static VertexShape PENTAGON = new PentagonVertexShape(SIZE); + public static VertexShape HEXAGON = new HexagonVertexShape(SIZE); + public static VertexShape OCTAGON = new OctagonVertexShape(SIZE); + + + private Shape cachedShape; + private String name; + private int size; + + VertexShape(String name, int size) { + this.name = name; + this.size = size; + registeredShapes.put(name, this); + } + + /** + * Returns the name of the shape + * @return the name of the shape + */ + public String getName() { + return name; + } + + /** + * Returns the {@link Shape} for this {@link VertexShape} instance + * @return the {@link Shape} for this {@link VertexShape} instance + */ + public Shape getShape() { + if (cachedShape == null) { + cachedShape = size(createShape()); + } + return cachedShape; + } + + private Shape size(Shape shape) { + AffineTransform transform = new AffineTransform(); + Rectangle bounds = shape.getBounds(); + double scale = size / bounds.getWidth(); + transform.scale(scale, scale); + return transform.createTransformedShape(shape); + } + + /** + * Gets the relative amount of margin space to allocate above the label. The default is + * 0.5 which will center the label in the associated shape. A value closer to 0 will move + * the label closer to the top and a value closer to 1 will move the label closer to the + * bottom. + * @return the relative amount of margin space to allocate obove the label.s + */ + public double getLabelPosition() { + return .5; + } + + /** + * Returns the size factor for a shape relative to its label. Shapes are sized based on the + * label of a vertex so that the label can fit inside the shape (mostly). Some subclasses + * will need to override this value to some value > 1 to fit the label in the shape. For + * example, a rectangle shape does not need to be extended because text naturally fits. But + * for a shape like a triangle, its bounding box needs to be bigger so that text doesn't + * "stick out" in the narrow part of the triangle. + * @return the size factor for a shape relatvie to its label + */ + public double getShapeToLabelRatio() { + return 1.0; + } + + /** + * This is a factor to keep some shapes from being so distorted by very long labels that they + * effectively lose their shape when seen by the user + * @return the max width to height ratio + */ + public int getMaxWidthToHeightRatio() { + return 10; + } + + protected abstract Shape createShape(); + + /** + * Returns the {@link VertexShape} for the given shape name + * @param shapeName the name of the shape for which to get the {@link VertexShape} + * @return the {@link VertexShape} for the given shape name + */ + public static VertexShape getShape(String shapeName) { + return registeredShapes.get(shapeName); + } + + /** + * Returns a list of names for all the supported {@link VertexShape}s + * @return a list of names for all the supported {@link VertexShape}s + */ + public static List getShapeNames() { + ArrayList list = new ArrayList(registeredShapes.keySet()); + Collections.sort(list); + return list; + } + + @Override + public int hashCode() { + return Objects.hash(name, size); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + VertexShape other = (VertexShape) obj; + return Objects.equals(name, other.name) && size == other.size; + } + +////////////////////////////////////////////////////////////////////////////////////////////////// +//Vertex Shape Classes +////////////////////////////////////////////////////////////////////////////////////////////////// + static class RectangleVertexShape extends VertexShape { + private RectangleVertexShape(int size) { + super("Rectangle", size); + } + + protected Shape createShape() { + return new Rectangle2D.Double(-1.0, -1.0, 2.0, 2.0); + } + } + + static class EllipseVertexShape extends VertexShape { + + private EllipseVertexShape(int size) { + super("Ellipse", size); + } + + protected Shape createShape() { + return new Ellipse2D.Double(-1.0, 1.0, 2.0, 2.0); + } + + @Override + public double getShapeToLabelRatio() { + return 1.4; + } + } + + static class TriangleUpVertexShape extends VertexShape { + + private TriangleUpVertexShape(int size) { + super("Triangle Up", size); + } + + protected Shape createShape() { + Path2D path = new Path2D.Double(); + path.moveTo(-1.0, 1.0); + path.lineTo(1.0, 1.0); + path.lineTo(0.0, -1.0); + path.closePath(); + return path; + } + + @Override + public double getShapeToLabelRatio() { + return 1.6; + } + + @Override + public double getLabelPosition() { + return 0.90; + } + } + + static class TriangleDownVertexShape extends VertexShape { + private TriangleDownVertexShape(int size) { + super("Triangle Down", size); + } + + protected Shape createShape() { + Path2D path = new Path2D.Double(); + path.moveTo(-1.0, -1.0); + path.lineTo(1.0, -1.0); + path.lineTo(0.0, 1.0); + path.closePath(); + return path; + } + + @Override + public double getShapeToLabelRatio() { + return 1.6; + } + + @Override + public double getLabelPosition() { + return 0.10; + } + } + + static class StarVertexShape extends VertexShape { + + private StarVertexShape(int size) { + super("Star", size); + } + + protected Shape createShape() { + int numPoints = 7; + Path2D path = new Path2D.Double(); + double outerRadius = 2; + double innerRadius = 1; + double deltaAngle = Math.PI / numPoints; + double angle = 3 * Math.PI / 2; // start such that star points up. + path.moveTo(outerRadius * Math.cos(angle), outerRadius * Math.sin(angle)); + for (int i = 0; i < numPoints; i++) { + angle += deltaAngle; + path.lineTo(innerRadius * Math.cos(angle), innerRadius * Math.sin(angle)); + angle += deltaAngle; + path.lineTo(outerRadius * Math.cos(angle), outerRadius * Math.sin(angle)); + } + return path; + } + + @Override + public double getShapeToLabelRatio() { + return 2.0; + } + + } + + static class DiamondVertexShape extends VertexShape { + private DiamondVertexShape(int size) { + super("Diamond", size); + } + + protected Shape createShape() { + Path2D path = new Path2D.Double(); + path.moveTo(0.0, -1.0); + path.lineTo(-1.0, 0.0); + path.lineTo(0.0, 1.0); + path.lineTo(1.0, 0.0); + path.closePath(); + return path; + } + + @Override + public double getShapeToLabelRatio() { + return 1.6; + } + } + + static class EquilateralPolygonVertexShape extends VertexShape { + private int numSides; + private double startAngle; + + protected EquilateralPolygonVertexShape(String name, int numSides, double startAngle, + int size) { + super(name, size); + this.numSides = numSides; + this.startAngle = startAngle; + } + + protected Shape createShape() { + Path2D path = new Path2D.Double(); + + double deltaAngle = Math.PI * 2 / numSides; + double angle = startAngle; + + path.moveTo(Math.cos(angle), Math.sin(angle)); + for (int i = 0; i < numSides; i++) { + angle += deltaAngle; + path.lineTo(Math.cos(angle), Math.sin(angle)); + } + return path; + } + + @Override + public int getMaxWidthToHeightRatio() { + return 2; + } + + @Override + public double getShapeToLabelRatio() { + return 1.4; + } + } + + static class PentagonVertexShape extends EquilateralPolygonVertexShape { + + private PentagonVertexShape(int size) { + super("Pentaon", 5, Math.PI + Math.PI / 10, size); + } + } + + static class HexagonVertexShape extends EquilateralPolygonVertexShape { + + private HexagonVertexShape(int size) { + super("Hexagon", 6, 0, size); + } + } + + static class OctagonVertexShape extends EquilateralPolygonVertexShape { + + private OctagonVertexShape(int size) { + super("Octagon", 8, 0, size); + } + } +} diff --git a/Ghidra/Framework/Graph/src/test/java/ghidra/service/graph/AttributedGraphTest.java b/Ghidra/Framework/Graph/src/test/java/ghidra/service/graph/AttributedGraphTest.java index 8ead376d3d..a7372d93ef 100644 --- a/Ghidra/Framework/Graph/src/test/java/ghidra/service/graph/AttributedGraphTest.java +++ b/Ghidra/Framework/Graph/src/test/java/ghidra/service/graph/AttributedGraphTest.java @@ -28,7 +28,7 @@ public class AttributedGraphTest { @Before public void setup() { - graph = new AttributedGraph(); + graph = new AttributedGraph("Test", new EmptyGraphType()); } @Test @@ -192,7 +192,7 @@ public class AttributedGraphTest { @Test public void testNonCollapsingEdges() { - graph = new AttributedGraph(false); + graph = new AttributedGraph("Test", new EmptyGraphType(), "Test", false); AttributedVertex v1 = graph.addVertex("A"); AttributedVertex v2 = graph.addVertex("B"); diff --git a/Ghidra/Framework/Graph/src/test/java/ghidra/service/graph/GraphDisplayOptionsTest.java b/Ghidra/Framework/Graph/src/test/java/ghidra/service/graph/GraphDisplayOptionsTest.java new file mode 100644 index 0000000000..99cfcede06 --- /dev/null +++ b/Ghidra/Framework/Graph/src/test/java/ghidra/service/graph/GraphDisplayOptionsTest.java @@ -0,0 +1,281 @@ +/* ### + * 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.service.graph; + +import static org.junit.Assert.*; + +import java.awt.Color; +import java.util.Arrays; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import docking.FakeDockingTool; +import ghidra.framework.options.Options; +import ghidra.framework.options.ToolOptions; +import ghidra.util.HelpLocation; +import ghidra.util.WebColors; + +public class GraphDisplayOptionsTest { + + private GraphType graphType; + private GraphDisplayOptions options; + + @Before + public void setUp() { + List vertexTypes = Arrays.asList("V1", "V2", "V3" ); + List edgeTypes = Arrays.asList("E1", "E2", "E3" ); + graphType = new GraphType("Test", "Test Description", vertexTypes, edgeTypes); + options = new GraphDisplayOptions(graphType); + } + + @Test + public void testSetAndGetDefaultVertexShape() { + options.setDefaultVertexShape(VertexShape.STAR); + assertEquals(VertexShape.STAR, options.getDefaultVertexShape()); + } + + @Test + public void testSetAndGetDefaultVertexColor() { + options.setDefaultVertexColor(Color.MAGENTA); + assertEquals(Color.MAGENTA, options.getDefaultVertexColor()); + } + + @Test + public void testSetAndGetDefaultEdgeColor() { + options.setDefaultEdgeColor(Color.MAGENTA); + assertEquals(Color.MAGENTA, options.getDefaultEdgeColor()); + } + + @Test + public void testSetAndGetVertexLabelOverride() { + assertEquals(null, options.getVertexLabelOverride()); + options.setVertexLabelOverrideAttributeKey("LABEL"); + assertEquals("LABEL", options.getVertexLabelOverride()); + } + + @Test + public void testSetAndGetVertexColorOverrideAttributeKey() { + assertEquals(null, options.getVertexColorOverrideAttributeKey()); + options.setVertexColorOverrideAttributeKey("COLOR"); + assertEquals("COLOR", options.getVertexColorOverrideAttributeKey()); + } + + @Test + public void testSetAndGetEdgeColorOverrideAttributeKey() { + assertEquals(null, options.getEdgeColorOverrideAttributeKey()); + options.setEdgeColorOverrideAttributeKey("COLOR"); + assertEquals("COLOR", options.getEdgeColorOverrideAttributeKey()); + } + + @Test + public void testSetAndGetVertexShapeOverrideAttributeKey() { + assertEquals(null, options.getVertexShapeOverrideAttributeKey()); + options.setVertexColorOverrideAttributeKey("SHAPE"); + assertEquals("SHAPE", options.getVertexColorOverrideAttributeKey()); + } + + @Test + public void testGetVertexLabel() { + AttributedVertex vertex = new AttributedVertex("Foo"); + assertEquals("Foo", options.getVertexLabel(vertex)); + } + + @Test + public void testGetVertexLabelWithLableOverride() { + options.setVertexLabelOverrideAttributeKey("Label"); + AttributedVertex vertex = new AttributedVertex("Foo"); + vertex.setAttribute("Label", "Bar"); + assertEquals("Bar", options.getVertexLabel(vertex)); + } + + @Test + public void testGetVertexShape() { + options.setVertexShape("V1", VertexShape.DIAMOND); + options.setVertexShape("V2", VertexShape.PENTAGON); + AttributedVertex vertex = new AttributedVertex("Foo"); + + vertex.setVertexType("V1"); + assertEquals(VertexShape.DIAMOND, options.getVertexShape(vertex)); + + vertex.setVertexType("V2"); + assertEquals(VertexShape.PENTAGON, options.getVertexShape(vertex)); + } + + @Test + public void testGetVertexShapeWithOverride() { + options.setVertexShape("V1", VertexShape.DIAMOND); + options.setVertexShapeOverrideAttributeKey("Shape"); + AttributedVertex vertex = new AttributedVertex("Foo"); + + vertex.setVertexType("V1"); + assertEquals(VertexShape.DIAMOND, options.getVertexShape(vertex)); + + vertex.setAttribute("Shape", VertexShape.ELLIPSE.getName()); + assertEquals(VertexShape.ELLIPSE, options.getVertexShape(vertex)); + } + + @Test + public void testGetVertexColor() { + options.setVertexColor("V1", Color.RED); + options.setVertexColor("V2", Color.GREEN); + AttributedVertex vertex = new AttributedVertex("Foo"); + + assertEquals(options.getDefaultVertexColor(), options.getVertexColor(vertex)); + + vertex.setVertexType("V1"); + assertEquals(Color.RED, options.getVertexColor(vertex)); + + vertex.setVertexType("V2"); + assertEquals(Color.GREEN, options.getVertexColor(vertex)); + } + + @Test + public void testGetVertexColorWithOverride() { + options.setVertexColor("V1", Color.RED); + options.setVertexColor("V2", Color.GREEN); + options.setVertexColorOverrideAttributeKey("Color"); + AttributedVertex vertex = new AttributedVertex("Foo"); + + vertex.setVertexType("V1"); + assertEquals(Color.RED, options.getVertexColor(vertex)); + + vertex.setAttribute("Color", WebColors.toString(Color.BLUE)); + + assertEquals(Color.BLUE, options.getVertexColor(vertex)); + } + + @Test + public void testGetEdgeColor() { + options.setEdgeColor("E1", Color.RED); + options.setEdgeColor("E2", Color.GREEN); + AttributedEdge edge = new AttributedEdge("1"); + assertEquals(options.getDefaultEdgeColor(), options.getEdgeColor(edge)); + + edge.setEdgeType("E1"); + assertEquals(Color.RED, options.getEdgeColor(edge)); + + edge.setEdgeType("E2"); + assertEquals(Color.GREEN, options.getEdgeColor(edge)); + } + + @Test + public void testGetEdgeColorWithOverride() { + options.setEdgeColor("E1", Color.RED); + options.setEdgeColor("E2", Color.GREEN); + options.setEdgeColorOverrideAttributeKey("Color"); + AttributedEdge edge = new AttributedEdge("1"); + assertEquals(options.getDefaultEdgeColor(), options.getEdgeColor(edge)); + + edge.setEdgeType("E1"); + assertEquals(Color.RED, options.getEdgeColor(edge)); + + edge.setAttribute("Color", WebColors.toString(Color.BLUE)); + + assertEquals(Color.BLUE, options.getEdgeColor(edge)); + } + + @Test + public void testGetEdgePriority() { + assertEquals(0, options.getEdgePriority("E1").intValue()); + assertEquals(1, options.getEdgePriority("E2").intValue()); + } + + @Test + public void testGetFavoredEdgeType() { + // favored edge defaults to first edge defined + assertEquals("E1", options.getFavoredEdgeType()); + options.setFavoredEdgeType("E2"); + assertEquals("E2", options.getFavoredEdgeType()); + } + + @Test + public void testGetVertexColorForType() { + assertEquals(options.getDefaultVertexColor(), options.getVertexColor("V1")); + options.setVertexColor("V1", Color.RED); + assertEquals(Color.RED, options.getVertexColor("V1")); + } + + @Test + public void testGetVertexShapeForType() { + assertEquals(options.getDefaultVertexShape(), options.getVertexShape("V1")); + options.setVertexShape("V1", VertexShape.STAR); + assertEquals(VertexShape.STAR, options.getVertexShape("V1")); + } + + + @Test + public void testGetEdgeColorForType() { + assertEquals(options.getDefaultEdgeColor(), options.getEdgeColor("V1")); + options.setEdgeColor("E1", Color.RED); + assertEquals(Color.RED, options.getEdgeColor("E1")); + } + + @Test + public void testRegisterOptions() { + ToolOptions toolOptions = new ToolOptions("Test"); + HelpLocation help = new HelpLocation("Topic", "anchor"); + options.registerOptions(toolOptions, help); + + Options graphDisplayOptions = toolOptions.getOptions(options.getRootOptionsName()); + assertNotNull(graphDisplayOptions); + + Options vertexColorOptions = graphDisplayOptions.getOptions("Vertex Colors"); + List leafOptionNames = vertexColorOptions.getLeafOptionNames(); + assertEquals(Arrays.asList("V1", "V2", "V3"), leafOptionNames); + assertEquals(options.getDefaultVertexColor(), + vertexColorOptions.getColor("V1", Color.WHITE)); + + Options vertexShapeOptions = graphDisplayOptions.getOptions("Vertex Shapes"); + leafOptionNames = vertexShapeOptions.getLeafOptionNames(); + assertEquals(Arrays.asList("V1", "V2", "V3"), leafOptionNames); + assertEquals(options.getDefaultVertexShape().getName(), + vertexShapeOptions.getString("V1", "Bob")); + + Options edgeColorOptions = graphDisplayOptions.getOptions("Edge Colors"); + leafOptionNames = edgeColorOptions.getLeafOptionNames(); + assertEquals(Arrays.asList("E1", "E2", "E3"), leafOptionNames); + assertEquals(options.getDefaultEdgeColor(), + edgeColorOptions.getColor("E1", Color.WHITE)); + + Options miscellaniousOptions = graphDisplayOptions.getOptions("Miscellanious"); + leafOptionNames = miscellaniousOptions.getLeafOptionNames(); + assertEquals(Arrays.asList("Use Icons", "Selected Vertex Color", "Default Layout Algorithm", + "Default Vertex Color", "Default Vertex Shape", "Selected Edge Color", "Label Position", + "Default Edge Color", "Font", "Favored Edge Type"), leafOptionNames); + + } + + @Test + public void testChangingToolOptionsAffectsGraph() { + FakeDockingTool tool = new FakeDockingTool(); + ToolOptions toolOptions = tool.getOptions("Graph"); + options.registerOptions(toolOptions, null); + options.initializeFromOptions(tool); + + AttributedVertex vertex = new AttributedVertex("Foo"); + vertex.setVertexType("V1"); + assertEquals(Color.BLUE, options.getVertexColor(vertex)); + + Options graphDisplayOptions = toolOptions.getOptions(options.getRootOptionsName()); + Options vertexColorOptions = graphDisplayOptions.getOptions("Vertex Colors"); + vertexColorOptions.setColor("V1", Color.CYAN); + + assertEquals(Color.CYAN, options.getVertexColor(vertex)); + } + +} diff --git a/Ghidra/Framework/Graph/src/test/java/ghidra/service/graph/GraphTypeBuilderTest.java b/Ghidra/Framework/Graph/src/test/java/ghidra/service/graph/GraphTypeBuilderTest.java new file mode 100644 index 0000000000..ba30867254 --- /dev/null +++ b/Ghidra/Framework/Graph/src/test/java/ghidra/service/graph/GraphTypeBuilderTest.java @@ -0,0 +1,71 @@ +/* ### + * 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.service.graph; + +import static org.junit.Assert.*; + +import java.util.List; + +import org.junit.Test; + +public class GraphTypeBuilderTest { + + @Test + public void testName() { + GraphType graphType = new GraphTypeBuilder("Test").build(); + assertEquals("Test", graphType.getName()); + } + + @Test + public void testDescription() { + GraphType graphType = new GraphTypeBuilder("Test") + .description("abc") + .build(); + assertEquals("abc", graphType.getDescription()); + } + + @Test + public void testNoDescriptionUsesName() { + GraphType graphType = new GraphTypeBuilder("Test").build(); + assertEquals("Test", graphType.getDescription()); + } + + @Test + public void testVertexType() { + GraphType graphType = new GraphTypeBuilder("Test") + .vertexType("V1") + .vertexType("V2") + .build(); + + List vertexTypes = graphType.getVertexTypes(); + assertEquals(2, vertexTypes.size()); + assertEquals("V1", vertexTypes.get(0)); + assertEquals("V2", vertexTypes.get(1)); + } + + @Test + public void testEdgeType() { + GraphType graphType = new GraphTypeBuilder("Test") + .edgeType("E1") + .edgeType("E2") + .build(); + + List edgeTypes = graphType.getEdgeTypes(); + assertEquals(2, edgeTypes.size()); + assertEquals("E1", edgeTypes.get(0)); + assertEquals("E2", edgeTypes.get(1)); + } +} diff --git a/Ghidra/Framework/Graph/src/test/java/ghidra/service/graph/GraphTypeTest.java b/Ghidra/Framework/Graph/src/test/java/ghidra/service/graph/GraphTypeTest.java new file mode 100644 index 0000000000..ca1c412f27 --- /dev/null +++ b/Ghidra/Framework/Graph/src/test/java/ghidra/service/graph/GraphTypeTest.java @@ -0,0 +1,80 @@ +/* ### + * 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.service.graph; + +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +public class GraphTypeTest { + private GraphType graphType; + + @Before + public void setUp() { + List vertexTypes = Arrays.asList("V1", "V2", "V3" ); + List edgeTypes = Arrays.asList("E1", "E2", "E3" ); + graphType = new GraphType("Test", "Test Description", vertexTypes, edgeTypes); + + } + + @Test + public void testName() { + assertEquals("Test", graphType.getName()); + } + + @Test + public void testDescription() { + assertEquals("Test Description", graphType.getDescription()); + } + + @Test + public void testGetVertexTypes() { + List types = graphType.getVertexTypes(); + assertEquals(3, types.size()); + assertEquals("V1", types.get(0)); + assertEquals("V2", types.get(1)); + assertEquals("V3", types.get(2)); + } + + @Test + public void testGetEdgeTypes() { + List types = graphType.getEdgeTypes(); + assertEquals(3, types.size()); + assertEquals("E1", types.get(0)); + assertEquals("E2", types.get(1)); + assertEquals("E3", types.get(2)); + } + + @Test + public void testContainsVertexType() { + assertTrue(graphType.containsVertexType("V1")); + assertTrue(graphType.containsVertexType("V2")); + assertTrue(graphType.containsVertexType("V3")); + assertFalse(graphType.containsVertexType("E1")); + } + + @Test + public void testContainsEdgeType() { + assertTrue(graphType.containsEdgeType("E1")); + assertTrue(graphType.containsEdgeType("E2")); + assertTrue(graphType.containsEdgeType("E3")); + assertFalse(graphType.containsEdgeType("V2")); + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplay.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplay.java index 0f1358f53f..b0516d116c 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplay.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplay.java @@ -139,33 +139,6 @@ public interface GraphDisplay { */ public void close(); - /** - * Defines a vertex attribute type for this graph window - * - * @param name the name of the attribute which may be attached to vertices - */ - public void defineVertexAttribute(String name); - - /** - * Defines an edge attribute type for this graph window - * - * @param name the name of the attribute which may be attached to edges. - */ - public void defineEdgeAttribute(String name); - - /** - * Sets the name of the attribute which should be used as the primary vertex label - * - * @param attributeName the name of the attribute to use as the display label for vertices - * @param alignment (ALIGN_LEFT, ALIGN_RIGHT, or ALIGN_CENTER) - * @param size the font size to use for the display label - * @param monospace true if the font should be monospaced - * @param maxLines the maximum number lines to display in the vertex labels - */ - public void setVertexLabelAttribute(String attributeName, int alignment, int size, - boolean monospace, - int maxLines); - /** * Sets the graph to be displayed or consumed by this graph display * @@ -174,10 +147,25 @@ public interface GraphDisplay { * @param monitor a {@link TaskMonitor} which can be used to cancel the graphing operation * @param append if true, append the new graph to any existing graph * @throws CancelledException thrown if the graphing operation was cancelled + * @deprecated You should now use the form that takes in a {@link GraphDisplayOptions} */ - public void setGraph(AttributedGraph graph, String title, boolean append, - TaskMonitor monitor) - throws CancelledException; + public default void setGraph(AttributedGraph graph, String title, boolean append, + TaskMonitor monitor) throws CancelledException { + setGraph(graph, new GraphDisplayOptions(graph.getGraphType()), title, append, monitor); + } + /** + * Sets the graph to be displayed or consumed by this graph display + * + * @param graph the graph to display or consume + * @param options {@link GraphDisplayOptions} for configuring how the display will + * render vertices and edges based on there vertex type and edge type respectively. + * @param title a title for the graph + * @param monitor a {@link TaskMonitor} which can be used to cancel the graphing operation + * @param append if true, append the new graph to any existing graph + * @throws CancelledException thrown if the graphing operation was cancelled + */ + public void setGraph(AttributedGraph graph, GraphDisplayOptions options, String title, + boolean append, TaskMonitor monitor) throws CancelledException; /** * Clears all graph vertices and edges from this graph display diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplayProvider.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplayProvider.java index 80b36c556e..1e4a2b711e 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplayProvider.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/service/graph/GraphDisplayProvider.java @@ -22,9 +22,6 @@ import ghidra.util.classfinder.ExtensionPoint; import ghidra.util.exception.GraphException; import ghidra.util.task.TaskMonitor; -import java.util.Collections; -import java.util.Map; - /** * Basic interface for objects that can display or otherwise consume a generic graph */ @@ -46,19 +43,6 @@ public interface GraphDisplayProvider extends ExtensionPoint { */ public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) throws GraphException; - /** - * Returns a GraphDisplay that can be used to "display" a graph - * - * @param reuseGraph if true, this provider will attempt to re-use an existing GraphDisplay - * @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 monitor and cancel the operation - * @return A GraphDisplay that can be used to display (or otherwise consume - e.g. export) the graph - * @throws GraphException thrown if there is a problem creating a GraphDisplay - */ - default GraphDisplay getGraphDisplay(boolean reuseGraph, Map properties, - TaskMonitor monitor) throws GraphException { - return getGraphDisplay(reuseGraph, monitor); - } /** * Provides an opportunity for this provider to register and read tool options diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GraphServicesScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GraphServicesScreenShots.java index c776dc3f94..c17f258cd4 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GraphServicesScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/GraphServicesScreenShots.java @@ -22,6 +22,7 @@ import org.junit.Test; import docking.ComponentProvider; import ghidra.app.services.GraphDisplayBroker; +import ghidra.graph.CallGraphType; import ghidra.graph.export.GraphExporterDialog; import ghidra.graph.visualization.DefaultGraphDisplay; import ghidra.graph.visualization.DefaultGraphDisplayComponentProvider; @@ -46,7 +47,7 @@ public class GraphServicesScreenShots extends GhidraScreenShotGenerator { GraphDisplayBroker broker = tool.getService(GraphDisplayBroker.class); GraphDisplayProvider export = broker.getGraphDisplayProvider("Graph Export"); GraphDisplay display = export.getGraphDisplay(false, TaskMonitor.DUMMY); - AttributedGraph graph = new AttributedGraph(); + AttributedGraph graph = new AttributedGraph("Test", new EmptyGraphType()); display.setGraph(graph, "test", false, TaskMonitor.DUMMY); GraphExporterDialog dialog = (GraphExporterDialog) getDialog(); dialog.setFilePath("/users/user1/graph"); @@ -59,18 +60,18 @@ public class GraphServicesScreenShots extends GhidraScreenShotGenerator { GraphDisplayBroker broker = tool.getService(GraphDisplayBroker.class); GraphDisplayProvider export = broker.getGraphDisplayProvider("Default Graph Display"); GraphDisplay display = export.getGraphDisplay(false, TaskMonitor.DUMMY); - AttributedGraph graph = new AttributedGraph(); + AttributedGraph graph = new AttributedGraph("Test", new CallGraphType()); AttributedVertex v1 = graph.addVertex("0000", "main"); - v1.setAttribute("VertexType", "Entry"); + v1.setVertexType("Entry"); AttributedVertex v2 = graph.addVertex("0100", "Fun_One"); - v2.setAttribute("VertexType", "Entry"); + v2.setVertexType("Entry"); AttributedVertex v3 = graph.addVertex("0200", "Fun_Two"); - v3.setAttribute("VertexType", "Entry"); + v3.setVertexType("Entry"); AttributedEdge e1 = graph.addEdge(v1, v2); - e1.setAttribute("EdgeType", "Unconditional-Call"); + e1.setEdgeType("Unconditional-Call"); AttributedEdge e2 = graph.addEdge(v1, v3); - e2.setAttribute("EdgeType", "Unconditional-Call"); + e2.setEdgeType("Unconditional-Call"); display.setGraph(graph, "Program Graph", false, TaskMonitor.DUMMY); waitForSwing(); diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/graph/GraphActionsTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/graph/GraphActionsTest.java index f5ec77e2b2..218cd5ca68 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/graph/GraphActionsTest.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/graph/GraphActionsTest.java @@ -536,7 +536,7 @@ public class GraphActionsTest extends AbstractGhidraHeadedIntegrationTest { } private AttributedGraph createGraph() { - AttributedGraph g = new AttributedGraph(); + AttributedGraph g = new AttributedGraph("Test", new EmptyGraphType()); a = g.addVertex("A"); b = g.addVertex("B"); c = g.addVertex("C"); diff --git a/Ghidra/Test/IntegrationTest/src/test/java/ghidra/app/plugin/core/datamgr/actions/TypeGraphTaskTest.java b/Ghidra/Test/IntegrationTest/src/test/java/ghidra/app/plugin/core/datamgr/actions/TypeGraphTaskTest.java index 81afe862bf..e676f1b98d 100644 --- a/Ghidra/Test/IntegrationTest/src/test/java/ghidra/app/plugin/core/datamgr/actions/TypeGraphTaskTest.java +++ b/Ghidra/Test/IntegrationTest/src/test/java/ghidra/app/plugin/core/datamgr/actions/TypeGraphTaskTest.java @@ -87,7 +87,7 @@ public class TypeGraphTaskTest extends AbstractGhidraHeadedIntegrationTest { assertNotNull(v2); AttributedEdge e1 = graph.getEdge(v1, v2); assertNotNull(e1); - assertEquals(TypeGraphTask.EMBEDDED, e1.getAttribute(TypeGraphTask.TYPE_ATTRIBUTE)); + assertEquals(TypeGraphTask.COMPOSITE, e1.getEdgeType()); } @Test @@ -122,7 +122,7 @@ public class TypeGraphTaskTest extends AbstractGhidraHeadedIntegrationTest { assertNotNull(v2); AttributedEdge e1 = graph.getEdge(v1, v2); assertNotNull(e1); - assertEquals(TypeGraphTask.POINTER, e1.getAttribute(TypeGraphTask.TYPE_ATTRIBUTE)); + assertEquals(TypeGraphTask.REFERENCE, e1.getEdgeType()); } @Test @@ -143,7 +143,7 @@ public class TypeGraphTaskTest extends AbstractGhidraHeadedIntegrationTest { assertNotNull(v2); AttributedEdge e1 = graph.getEdge(v1, v2); assertNotNull(e1); - assertEquals(TypeGraphTask.EMBEDDED, e1.getAttribute(TypeGraphTask.TYPE_ATTRIBUTE)); + assertEquals(TypeGraphTask.COMPOSITE, e1.getEdgeType()); } @Test @@ -164,7 +164,7 @@ public class TypeGraphTaskTest extends AbstractGhidraHeadedIntegrationTest { assertNotNull(v2); AttributedEdge e1 = graph.getEdge(v1, v2); assertNotNull(e1); - assertEquals(TypeGraphTask.POINTER, e1.getAttribute(TypeGraphTask.TYPE_ATTRIBUTE)); + assertEquals(TypeGraphTask.REFERENCE, e1.getEdgeType()); } @Test @@ -184,7 +184,7 @@ public class TypeGraphTaskTest extends AbstractGhidraHeadedIntegrationTest { assertNotNull(v2); AttributedEdge e1 = graph.getEdge(v1, v2); assertNotNull(e1); - assertEquals(TypeGraphTask.EMBEDDED, e1.getAttribute(TypeGraphTask.TYPE_ATTRIBUTE)); + assertEquals(TypeGraphTask.COMPOSITE, e1.getEdgeType()); } @Test @@ -205,7 +205,7 @@ public class TypeGraphTaskTest extends AbstractGhidraHeadedIntegrationTest { assertNotNull(v2); AttributedEdge e1 = graph.getEdge(v1, v2); assertNotNull(e1); - assertEquals(TypeGraphTask.POINTER, e1.getAttribute(TypeGraphTask.TYPE_ATTRIBUTE)); + assertEquals(TypeGraphTask.REFERENCE, e1.getEdgeType()); } @Test diff --git a/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/TestGraphDisplay.java b/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/TestGraphDisplay.java index bbe79bcad0..0535f15e05 100644 --- a/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/TestGraphDisplay.java +++ b/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/TestGraphDisplay.java @@ -15,7 +15,8 @@ */ package ghidra.graph; -import java.util.*; +import java.util.Map; +import java.util.Set; import docking.action.DockingActionIf; import docking.widgets.EventTrigger; @@ -24,8 +25,6 @@ import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; public class TestGraphDisplay implements GraphDisplay { - private Set definedVertexAttributes = new HashSet<>(); - private Set definedEdgeAttributes = new HashSet<>(); private AttributedGraph graph; private String title; private GraphDisplayListener listener; @@ -62,23 +61,6 @@ public class TestGraphDisplay implements GraphDisplay { // nothing } - @Override - public void defineVertexAttribute(String name) { - definedVertexAttributes.add(name); - } - - @Override - public void defineEdgeAttribute(String name) { - definedEdgeAttributes.add(name); - } - - @Override - public void setVertexLabelAttribute(String attributeName, int alignment, int size, - boolean monospace, - int maxLines) { - // nothing - } - @Override public void setGraph(AttributedGraph graph, String title, boolean append, TaskMonitor monitor) @@ -92,6 +74,12 @@ public class TestGraphDisplay implements GraphDisplay { this.title = title; } + @Override + public void setGraph(AttributedGraph graph, GraphDisplayOptions options, String title, + boolean append, TaskMonitor monitor) throws CancelledException { + setGraph(graph, title, append, monitor); + } + @Override public void clear() { // nothing @@ -133,7 +121,7 @@ public class TestGraphDisplay implements GraphDisplay { AttributedVertex from = oldGraph.getEdgeSource(edge); AttributedVertex to = oldGraph.getEdgeTarget(edge); AttributedEdge newEdge = newGraph.addEdge(from, to); - Map attributeMap = edge.getAttributeMap(); + Map attributeMap = edge.getAttributes(); for (String key : attributeMap.keySet()) { newEdge.setAttribute(key, edge.getAttribute(key)); } diff --git a/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/program/BlockGraphEventTest.java b/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/program/BlockGraphEventTest.java index ed4c444f5a..e152733cd7 100644 --- a/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/program/BlockGraphEventTest.java +++ b/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/program/BlockGraphEventTest.java @@ -23,8 +23,7 @@ import java.util.Set; import org.junit.Test; import ghidra.app.events.ProgramSelectionPluginEvent; -import ghidra.graph.TestGraphDisplay; -import ghidra.graph.TestGraphService; +import ghidra.graph.*; import ghidra.program.model.address.AddressSet; import ghidra.program.model.block.CodeBlockModel; import ghidra.program.util.ProgramLocation; @@ -46,7 +45,7 @@ public class BlockGraphEventTest extends AbstractBlockGraphTest { blockModelService.getNewModelByName(modelName, program, true); TestGraphService graphService = new TestGraphService(); BlockGraphTask task = - new BlockGraphTask("test", false, false, false, false, + new BlockGraphTask(new BlockFlowGraphType(), false, false, false, tool, null, null, model, graphService); task.monitoredRun(TaskMonitor.DUMMY); diff --git a/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/program/BlockGraphTaskTest.java b/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/program/BlockGraphTaskTest.java index ec15405540..14d4f9ca51 100644 --- a/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/program/BlockGraphTaskTest.java +++ b/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/program/BlockGraphTaskTest.java @@ -21,16 +21,13 @@ import java.util.Map; import org.junit.Test; -import ghidra.graph.TestGraphDisplay; -import ghidra.graph.TestGraphService; +import ghidra.graph.*; import ghidra.program.model.block.CodeBlockModel; import ghidra.program.util.ProgramSelection; import ghidra.service.graph.*; import ghidra.util.task.TaskMonitor; public class BlockGraphTaskTest extends AbstractBlockGraphTest { - private static final boolean SHOW_CODE = true; - private static final boolean DONT_SHOW_CODE = false; @Test public void testBlockGraph() throws Exception { @@ -39,8 +36,8 @@ public class BlockGraphTaskTest extends AbstractBlockGraphTest { blockModelService.getNewModelByName(modelName, program, true); TestGraphService graphService = new TestGraphService(); BlockGraphTask task = - new BlockGraphTask("test", false, DONT_SHOW_CODE, false, false, - tool, null, null, model, graphService); + new BlockGraphTask(new BlockFlowGraphType(), false, false, + false, tool, null, null, model, graphService); task.monitoredRun(TaskMonitor.DUMMY); @@ -74,7 +71,7 @@ public class BlockGraphTaskTest extends AbstractBlockGraphTest { assertNotNull(e4); assertNotNull(e5); - Map map = v1.getAttributeMap(); + Map map = v1.getAttributes(); assertEquals(2, map.size()); assertTrue(map.containsKey("Name")); assertTrue(map.containsKey("VertexType")); @@ -83,14 +80,13 @@ public class BlockGraphTaskTest extends AbstractBlockGraphTest { assertEquals("Body", v4.getAttribute("VertexType")); assertEquals("Exit", v5.getAttribute("VertexType")); - map = e1.getAttributeMap(); - assertEquals(2, map.size()); - assertTrue(map.containsKey("Name")); + map = e1.getAttributes(); + assertEquals(1, map.size()); assertTrue(map.containsKey("EdgeType")); - assertEquals("Fall-Through", e3.getAttribute("EdgeType")); - assertEquals("Fall-Through", e4.getAttribute("EdgeType")); - assertEquals("Conditional-Jump", e5.getAttribute("EdgeType")); + assertEquals("Fall Through", e3.getAttribute("EdgeType")); + assertEquals("Fall Through", e4.getAttribute("EdgeType")); + assertEquals("Conditional Jump", e5.getAttribute("EdgeType")); } @Test @@ -100,7 +96,7 @@ public class BlockGraphTaskTest extends AbstractBlockGraphTest { blockModelService.getNewModelByName(modelName, program, true); TestGraphService graphService = new TestGraphService(); BlockGraphTask task = - new BlockGraphTask("test", false, SHOW_CODE, false, false, + new BlockGraphTask(new CodeFlowGraphType(), false, false, false, tool, null, null, model, graphService); task.monitoredRun(TaskMonitor.DUMMY); @@ -135,7 +131,7 @@ public class BlockGraphTaskTest extends AbstractBlockGraphTest { assertNotNull(e4); assertNotNull(e5); - Map map = v3.getAttributeMap(); + Map map = v3.getAttributes(); assertEquals(4, map.size()); assertTrue(map.containsKey("Name")); assertTrue(map.containsKey("VertexType")); @@ -153,7 +149,7 @@ public class BlockGraphTaskTest extends AbstractBlockGraphTest { blockModelService.getNewModelByName(modelName, program, true); TestGraphService graphService = new TestGraphService(); BlockGraphTask task = - new BlockGraphTask("test", false, false, false, false, + new BlockGraphTask(new CallGraphType(), false, false, false, tool, null, null, model, graphService); task.monitoredRun(TaskMonitor.DUMMY); @@ -174,7 +170,7 @@ public class BlockGraphTaskTest extends AbstractBlockGraphTest { AttributedEdge e1 = graph.getEdge(v1, v2); assertNotNull(e1); - Map map = v1.getAttributeMap(); + Map map = v1.getAttributes(); assertEquals(2, map.size()); assertTrue(map.containsKey("Name")); assertTrue(map.containsKey("VertexType")); @@ -182,13 +178,12 @@ public class BlockGraphTaskTest extends AbstractBlockGraphTest { assertEquals("Entry", v1.getAttribute("VertexType")); assertEquals("Entry", v2.getAttribute("VertexType")); - map = e1.getAttributeMap(); + map = e1.getAttributes(); - assertEquals(2, map.size()); - assertTrue(map.containsKey("Name")); + assertEquals(1, map.size()); assertTrue(map.containsKey("EdgeType")); - assertEquals("Unconditional-Call", e1.getAttribute("EdgeType")); + assertEquals("Unconditional Call", e1.getAttribute("EdgeType")); } @@ -200,8 +195,8 @@ public class BlockGraphTaskTest extends AbstractBlockGraphTest { TestGraphService graphService = new TestGraphService(); ProgramSelection sel = new ProgramSelection(addr(0x1002239), addr(0x1002247)); BlockGraphTask task = - new BlockGraphTask("test", false, DONT_SHOW_CODE, false, false, - tool, sel, null, model, graphService); + new BlockGraphTask(new BlockFlowGraphType(), false, false, + false, tool, sel, null, model, graphService); task.monitoredRun(TaskMonitor.DUMMY); diff --git a/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/program/DataReferenceGraphTaskTest.java b/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/program/DataReferenceGraphTaskTest.java index 1cafee22b6..4bb2019431 100644 --- a/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/program/DataReferenceGraphTaskTest.java +++ b/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/program/DataReferenceGraphTaskTest.java @@ -227,7 +227,7 @@ public class DataReferenceGraphTaskTest extends AbstractDataReferenceGraphTest { assertNotNull(v1); assertNotNull(v2); assertNotNull(e1); - assertEquals("TriangleDown", v1.getAttribute("Icon")); + assertEquals("Initial Instruction", v1.getVertexType()); } @Test