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 d7922ee835..a9cc27d188 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 @@ -4,9 +4,9 @@ * 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. @@ -151,7 +151,7 @@ public class GraphDisplayBrokerPlugin extends Plugin public GraphDisplay getDefaultGraphDisplay(boolean reuseGraph, TaskMonitor monitor) throws GraphException { if (defaultGraphDisplayProvider != null) { - return defaultGraphDisplayProvider.getGraphDisplay(reuseGraph, monitor); + return defaultGraphDisplayProvider.getGraphDisplay(reuseGraph, false, monitor); } return null; } diff --git a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplayProvider.java b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplayProvider.java index 8dc952b1f3..24de24efeb 100644 --- a/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplayProvider.java +++ b/Ghidra/Features/GraphServices/src/main/java/ghidra/graph/export/ExportAttributedGraphDisplayProvider.java @@ -4,9 +4,9 @@ * 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. @@ -52,8 +52,7 @@ public class ExportAttributedGraphDisplayProvider implements GraphDisplayProvide } @Override - public GraphDisplay getGraphDisplay(boolean reuseGraph, - TaskMonitor monitor) { + public GraphDisplay getGraphDisplay(boolean reuseGraph, boolean append, TaskMonitor monitor) { return new ExportAttributedGraphDisplay(this); } 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 59954c39e0..f736a01fb4 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 @@ -568,7 +568,8 @@ public class DefaultGraphDisplay implements GraphDisplay { } private void createAndDisplaySubGraph() { - GraphDisplay display = graphDisplayProvider.getGraphDisplay(false, TaskMonitor.DUMMY); + GraphDisplay display = + graphDisplayProvider.getGraphDisplay(false, false, TaskMonitor.DUMMY); try { display.setGraph(createSubGraph(), graphRenderer.getGraphDisplayOptions(), title + " - Sub-graph", false, TaskMonitor.DUMMY); @@ -1065,7 +1066,8 @@ public class DefaultGraphDisplay implements GraphDisplay { 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) { + if (append && this.graph != null && + graph.getGraphType().equals(this.graph.getGraphType())) { graph = mergeGraphs(graph, this.graph); } 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 91e127ab69..e790516dc5 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 @@ -4,9 +4,9 @@ * 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. @@ -52,7 +52,7 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider { } @Override - public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) { + public GraphDisplay getGraphDisplay(boolean reuseGraph, boolean append, TaskMonitor monitor) { return Swing.runNow(() -> { @@ -61,9 +61,15 @@ public class DefaultGraphDisplayProvider implements GraphDisplayProvider { (DefaultGraphDisplayWrapper) getActiveGraphDisplay(); // set a temporary dummy graph; clients will set a real graph - visibleGraph.setGraph(new AttributedGraph("Empty", null), - new DefaultGraphDisplayOptions(), "", false, monitor); - visibleGraph.restoreDefaultState(); + if (!append) { + // only clear the graph if we aren't appending. We did the clear in case + // it was laying out the graph and nodes got yanked out from under it, but + // if the intention is to append to the graph, all existing nodes will still + // be there, so no stack trace attempting to access a missing vertex. + visibleGraph.setGraph(new AttributedGraph("Empty", null), + new DefaultGraphDisplayOptions(), "", false, monitor); + visibleGraph.restoreDefaultState(); + } return visibleGraph; } 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 d3feafef3b..446b9ab79c 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 @@ -4,9 +4,9 @@ * 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. @@ -93,7 +93,7 @@ public class BlockGraphTask extends Task { AttributedGraph graph = createGraph(graphTitle); monitor.setMessage("Generating Graph..."); try { - GraphDisplay display = graphProvider.getGraphDisplay(reuseGraph, monitor); + GraphDisplay display = graphProvider.getGraphDisplay(reuseGraph, appendGraph, monitor); GraphDisplayOptions graphOptions = new ProgramGraphDisplayOptions(graphType, tool); if (showCode) { // arrows need to be bigger as this generates larger vertices graphOptions.setArrowLength(30); 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 c616130436..6d29dbf3d4 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 @@ -4,9 +4,9 @@ * 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. @@ -130,7 +130,7 @@ public class DataReferenceGraphTask extends Task { try { if (display == null) { - display = graphProvider.getGraphDisplay(reuseGraph, monitor); + display = graphProvider.getGraphDisplay(reuseGraph, appendGraph, monitor); DataReferenceGraphDisplayListener listener = new DataReferenceGraphDisplayListener(tool, display, program, totalMaxDepth); 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 b460ab7f70..33d7edbc4e 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 @@ -4,9 +4,9 @@ * 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. @@ -38,12 +38,34 @@ public interface GraphDisplayProvider extends ExtensionPoint { /** * 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 reuseGraph if true, this provider will attempt to re-use an existing GraphDisplay. + * Note: this form will always clear the graph. If the intention is to append to the graph, + * use the {@link #getGraphDisplay(boolean, boolean, TaskMonitor)} method instead. + * so that it can be appended to. Otherwise, any reused graph display will be have its + * existing graph cleared. + * * @param monitor the {@link TaskMonitor} that can be used to monitor and cancel the operation * @return an object 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 */ - public GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) + public default GraphDisplay getGraphDisplay(boolean reuseGraph, TaskMonitor monitor) + throws GraphException { + return getGraphDisplay(reuseGraph, false, monitor); + } + + /** + * 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 append if true and there is a graph display to reuse, don't clear the existing graph + * so that it can be appended to. Otherwise, any reused graph display will be have its + * existing graph cleared. + * + * @param monitor the {@link TaskMonitor} that can be used to monitor and cancel the operation + * @return an object 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 + */ + public GraphDisplay getGraphDisplay(boolean reuseGraph, boolean append, TaskMonitor monitor) throws GraphException; /** diff --git a/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/TestGraphService.java b/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/TestGraphService.java index 41732e64b5..d9c7d1fe78 100644 --- a/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/TestGraphService.java +++ b/Ghidra/Test/IntegrationTest/src/test/java/ghidra/graph/TestGraphService.java @@ -4,9 +4,9 @@ * 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. @@ -35,7 +35,7 @@ public class TestGraphService implements GraphDisplayProvider { } @Override - public GraphDisplay getGraphDisplay(boolean reuseGraph, + public GraphDisplay getGraphDisplay(boolean reuseGraph, boolean append, TaskMonitor monitor) throws GraphException { return testDisplay; } 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 14d4f9ca51..01f4dee773 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 @@ -4,9 +4,9 @@ * 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. @@ -22,6 +22,8 @@ import java.util.Map; import org.junit.Test; import ghidra.graph.*; +import ghidra.graph.visualization.DefaultGraphDisplayProvider; +import ghidra.program.model.address.AddressSet; import ghidra.program.model.block.CodeBlockModel; import ghidra.program.util.ProgramSelection; import ghidra.service.graph.*; @@ -225,6 +227,36 @@ public class BlockGraphTaskTest extends AbstractBlockGraphTest { assertNotNull(e3); assertNotNull(e4); assertNotNull(e5); + } + + @Test + public void testAppendGraph() throws Exception { + String modelName = blockModelService.getActiveBlockModelName(); + CodeBlockModel model = blockModelService.getNewModelByName(modelName, program, true); + ProgramSelection selection = + new ProgramSelection(new AddressSet(addr(0x1002200), addr(0x01002203))); + GraphDisplayProvider graphService = new DefaultGraphDisplayProvider(); + runSwing(() -> graphService.initialize(tool, tool.getOptions("foo"))); + BlockGraphTask task = + new BlockGraphTask(new BlockFlowGraphType(), false, false, + false, tool, selection, null, model, graphService); + task.monitoredRun(TaskMonitor.DUMMY); + waitForSwing(); + + GraphDisplay display = graphService.getActiveGraphDisplay(); + + AttributedGraph graph = display.getGraph(); + + assertEquals(2, graph.getVertexCount()); + + selection = new ProgramSelection(new AddressSet(addr(0x01002239))); + task = new BlockGraphTask(new BlockFlowGraphType(), false, true, true, tool, selection, + null, model, graphService); + task.monitoredRun(TaskMonitor.DUMMY); + waitForSwing(); + + graph = display.getGraph(); + assertEquals(3, graph.getVertexCount()); }