diff --git a/Ghidra/Features/FunctionGraph/src/main/help/help/topics/FunctionGraphPlugin/Function_Graph.html b/Ghidra/Features/FunctionGraph/src/main/help/help/topics/FunctionGraphPlugin/Function_Graph.html index feb5225643..12185e4d0c 100644 --- a/Ghidra/Features/FunctionGraph/src/main/help/help/topics/FunctionGraphPlugin/Function_Graph.html +++ b/Ghidra/Features/FunctionGraph/src/main/help/help/topics/FunctionGraphPlugin/Function_Graph.html @@ -729,6 +729,12 @@

+

The Max Nodes option limits how many nodes will be generated while building the + graph. When the limit is reached, the graph loading will be cancelled and an error message + will be displayed at the bottom of the graph. This option is useful if user navigation + triggers large graphs loading that causes the Function Graph to lock-up the UI while it is + open. +

The Scroll Wheel Pans option signals to move the graph vertical when scrolling the mouse scroll wheel. Disabling this option restores the original function graph scroll wheel diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FunctionGraphFactory.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FunctionGraphFactory.java index 90ba27424f..76db409604 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FunctionGraphFactory.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FunctionGraphFactory.java @@ -316,7 +316,17 @@ public class FunctionGraphFactory { CodeBlockIterator iterator = blockModel.getCodeBlocksContaining(addresses, monitor); monitor.initialize(addresses.getNumAddresses()); - for (; iterator.hasNext();) { + FunctionGraphOptions fgOptions = controller.getFunctionGraphOptions(); + int maxNodes = fgOptions.getMaxNodes(); + + for (int i = 0; iterator.hasNext(); i++) { + + if (i > maxNodes) { + String message = + "Graph is too large; options limit set to %s nodes".formatted(maxNodes); + throw new CancelledException(message); + } + CodeBlock codeBlock = iterator.next(); FlowType flowType = codeBlock.getFlowType(); diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FunctionGraphOptions.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FunctionGraphOptions.java index ea6e0bc29d..d2c76bba2b 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FunctionGraphOptions.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FunctionGraphOptions.java @@ -80,12 +80,16 @@ public class FunctionGraphOptions extends VisualGraphOptions { private static final String DEFAULT_GROUP_BACKGROUND_COLOR_DESCRPTION = "The default background color applied to newly created group vertices"; - private static final String UPDATE_GROUP_AND_UNGROUP_COLORS = + private static final String UPDATE_GROUP_AND_UNGROUP_COLORS_KEY = "Update Vertex Colors When Grouping"; private static final String UPDATE_GROUP_AND_UNGROUP_COLORS_DESCRIPTION = "Signals that any user color changes to a group vertex will apply that same color to " + "all grouped vertices as well."; + private static final String MAX_NODES_KEY = "Max Nodes"; + private static final String MAX_NODES_DESCRIPTION = + "The maximum number of nodes to process before cancelling graph loading."; + private boolean updateGroupColorsAutomatically = true; //@formatter:off @@ -102,6 +106,7 @@ public class FunctionGraphOptions extends VisualGraphOptions { private GColor unconditionalJumpEdgeHighlightColor = new GColor("color.bg.plugin.functiongraph.edge.jump.unconditional.highlight"); //@formatter:on + private int maxNodes = 1000; private boolean useFullSizeTooltip = false; private RelayoutOption relayoutOption = RelayoutOption.VERTEX_GROUPING_CHANGES; @@ -122,6 +127,10 @@ public class FunctionGraphOptions extends VisualGraphOptions { return updateGroupColorsAutomatically; } + public int getMaxNodes() { + return maxNodes; + } + public Color getFallthroughEdgeColor() { return fallthroughEdgeColor; } @@ -178,12 +187,14 @@ public class FunctionGraphOptions extends VisualGraphOptions { options.registerThemeColorBinding(DEFAULT_GROUP_BACKGROUND_COLOR_KEY, defaultGroupBackgroundColor.getId(), help, DEFAULT_GROUP_BACKGROUND_COLOR_DESCRPTION); - options.registerOption(UPDATE_GROUP_AND_UNGROUP_COLORS, updateGroupColorsAutomatically, + options.registerOption(UPDATE_GROUP_AND_UNGROUP_COLORS_KEY, updateGroupColorsAutomatically, help, UPDATE_GROUP_AND_UNGROUP_COLORS_DESCRIPTION); options.registerOption(USE_FULL_SIZE_TOOLTIP_KEY, useFullSizeTooltip, help, USE_FULL_SIZE_TOOLTIP_DESCRIPTION); + options.registerOption(MAX_NODES_KEY, maxNodes, help, MAX_NODES_DESCRIPTION); + options.registerThemeColorBinding(EDGE_COLOR_CONDITIONAL_JUMP_KEY, conditionalJumpEdgeColor.getId(), help, "Conditional jump edge color"); @@ -220,7 +231,9 @@ public class FunctionGraphOptions extends VisualGraphOptions { useFullSizeTooltip = options.getBoolean(USE_FULL_SIZE_TOOLTIP_KEY, useFullSizeTooltip); updateGroupColorsAutomatically = - options.getBoolean(UPDATE_GROUP_AND_UNGROUP_COLORS, updateGroupColorsAutomatically); + options.getBoolean(UPDATE_GROUP_AND_UNGROUP_COLORS_KEY, updateGroupColorsAutomatically); + + maxNodes = options.getInt(MAX_NODES_KEY, maxNodes); Set> entries = layoutOptionsByName.entrySet(); for (Entry entry : entries) { diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FunctionGraphRunnable.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FunctionGraphRunnable.java index 0c27396362..f84d0ecbda 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FunctionGraphRunnable.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FunctionGraphRunnable.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. @@ -107,7 +107,12 @@ public class FunctionGraphRunnable implements SwingRunnable { "Finished creating graph for \"" + validatedFunction.getName() + "\""); } catch (CancelledException e) { + String message = "Cancelled graph for \"" + validatedFunction.getName() + "\""; + if (!e.isDefaultMessage()) { + message = e.getMessage(); + } + graphData = new EmptyFunctionGraphData(message); monitor.setMessage(message); } diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/util/exception/CancelledException.java b/Ghidra/Framework/Utility/src/main/java/ghidra/util/exception/CancelledException.java index 78a6aac5ba..e3832ffbf4 100644 --- a/Ghidra/Framework/Utility/src/main/java/ghidra/util/exception/CancelledException.java +++ b/Ghidra/Framework/Utility/src/main/java/ghidra/util/exception/CancelledException.java @@ -1,13 +1,12 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * 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. @@ -16,22 +15,30 @@ */ package ghidra.util.exception; - /** * CancelledException indicates that the user cancelled * the current operation. */ public class CancelledException extends UsrException { + public static final String DEFAULT_MESSAGE = "Operation cancelled"; + /** * Default constructor. Message indicates 'Operation cancelled'. */ public CancelledException() { - super("Operation cancelled"); + super(DEFAULT_MESSAGE); } public CancelledException(String msg) { super(msg); } - + + /** + * {@return true if the message of this exception is {@value #DEFAULT_MESSAGE}} + */ + public boolean isDefaultMessage() { + return DEFAULT_MESSAGE.equals(getMessage()); + } + }