diff --git a/Ghidra/Features/FunctionGraph/build.gradle b/Ghidra/Features/FunctionGraph/build.gradle index 1e952eb764..f572998ad9 100644 --- a/Ghidra/Features/FunctionGraph/build.gradle +++ b/Ghidra/Features/FunctionGraph/build.gradle @@ -25,10 +25,14 @@ eclipse.project.name = 'Features Graph FunctionGraph' dependencies { + api project(":Base") + + api project(":GraphServices") testImplementation "org.jmockit:jmockit:1.44" helpPath project(path: ":Base", configuration: 'helpPath') } + diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/DiscoverableFGLayoutFinder.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/DiscoverableFGLayoutFinder.java index b9f320bcbb..a6b0aceca5 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/DiscoverableFGLayoutFinder.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/DiscoverableFGLayoutFinder.java @@ -15,10 +15,13 @@ */ package ghidra.app.plugin.core.functiongraph; +import java.util.ArrayList; import java.util.List; import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayout; import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider; +import ghidra.app.plugin.core.functiongraph.graph.layout.jungrapht.JgtLayoutFactory; +import ghidra.app.plugin.core.functiongraph.graph.layout.jungrapht.JgtNamedLayoutProvider; import ghidra.util.classfinder.ClassSearcher; /** @@ -28,8 +31,20 @@ public class DiscoverableFGLayoutFinder implements FGLayoutFinder { @Override public List findLayouts() { - List instances = ClassSearcher.getInstances(FGLayoutProvider.class); - return instances; + + List list = new ArrayList<>(); + + // add discovered layouts + List instances = + ClassSearcher.getInstances(FGLayoutProvider.class); + list.addAll(instances); + + // add hand-picked, generated layout providers + List jgtLayoutNames = JgtLayoutFactory.getSupportedLayoutNames(); + for (String name : jgtLayoutNames) { + list.add(new JgtNamedLayoutProvider(name)); + } + return list; } } diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java index 3c58dc4885..cc42be9207 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java @@ -17,7 +17,6 @@ package ghidra.app.plugin.core.functiongraph; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; -import java.lang.reflect.Constructor; import java.util.*; import javax.swing.*; @@ -47,14 +46,19 @@ import ghidra.program.model.address.AddressSetView; import ghidra.program.model.listing.Function; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; -import ghidra.util.*; +import ghidra.util.HelpLocation; +import ghidra.util.SystemUtilities; import resources.Icons; import resources.ResourceManager; class FGActionManager { private static final String EDGE_HOVER_HIGHLIGHT = "EDGE_HOVER_HIGHLIGHT"; private static final String EDGE_SELECTION_HIGHLIGHT = "EDGE_SELECTION_HIGHLIGHT"; + + // save state key names private static final String LAYOUT_NAME = "LAYOUT_NAME"; + private static final String COMPLEX_LAYOUT_NAME = "COMPLEX_LAYOUT_NAME"; + private static final String LAYOUT_CLASS_NAME = "LAYOUT_CLASS_NAME"; private static final ImageIcon EDIT_ICON = ResourceManager.loadImage("images/id.png"); private static final ImageIcon FULL_SCREEN_ICON = @@ -71,7 +75,7 @@ class FGActionManager { private MultiStateDockingAction vertexHoverModeAction; private MultiStateDockingAction vertexFocusModeAction; - private MultiStateDockingAction> layoutAction; + private MultiStateDockingAction layoutAction; FGActionManager(FunctionGraphPlugin plugin, FGController controller, FGProvider provider) { this.plugin = plugin; @@ -860,13 +864,12 @@ class FGActionManager { @Override protected void doActionPerformed(ActionContext context) { // this callback is when the user clicks the button - Class currentUserData = getCurrentUserData(); + FGLayoutProvider currentUserData = getCurrentUserData(); changeLayout(currentUserData); } @Override - public void actionStateChanged( - ActionState> newActionState, + public void actionStateChanged(ActionState newActionState, EventTrigger trigger) { changeLayout(newActionState.getUserData()); if (trigger != EventTrigger.API_CALL) { @@ -878,34 +881,34 @@ class FGActionManager { layoutAction.setSubGroup("2"); // 2 after refresh, which is 1 layoutAction.setHelpLocation(layoutHelpLocation); - List>> actionStates = + // This icon will display when the action has no icon. This allows actions with no good + // icon to be blank in the menu, but to use this icon on the toolbar. + layoutAction.setDefaultIcon(ResourceManager.loadImage("images/preferences-system.png")); + + List> actionStates = loadActionStatesForLayoutProviders(); - for (ActionState> actionState : actionStates) { + for (ActionState actionState : actionStates) { layoutAction.addActionState(actionState); } provider.addLocalAction(layoutAction); } - private void changeLayout(Class layoutClass) { - FGLayoutProvider layoutInstance = getLayoutInstance(layoutClass); - if (layoutInstance == null) { - return; - } - controller.changeLayout(layoutInstance); + private void changeLayout(FGLayoutProvider layout) { + controller.changeLayout(layout); } - private List>> loadActionStatesForLayoutProviders() { + private List> loadActionStatesForLayoutProviders() { List layoutInstances = plugin.getLayoutProviders(); - List>> list = new ArrayList<>(); + List> list = new ArrayList<>(); HelpLocation layoutHelpLocation = new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout"); for (FGLayoutProvider layout : layoutInstances) { - ActionState> layoutState = new ActionState<>( - layout.getLayoutName(), layout.getActionIcon(), layout.getClass()); + ActionState layoutState = new ActionState<>( + layout.getLayoutName(), layout.getActionIcon(), layout); layoutState.setHelpLocation(layoutHelpLocation); list.add(layoutState); } @@ -913,29 +916,20 @@ class FGActionManager { return list; } - private FGLayoutProvider getLayoutInstance(Class layoutClass) { - FGLayoutProvider layoutInstance = null; - try { - Constructor constructor = - layoutClass.getConstructor((Class[]) null); - layoutInstance = constructor.newInstance((Object[]) null); - } - catch (Exception e) { - Msg.showError(this, provider.getComponent(), "Unable to Create Graph Layout", - "Unable to create layout: " + layoutClass.getName(), e); - } - return layoutInstance; - } + private void setLayoutActionStateByClassName(String layoutClassName, String layoutName) { - @SuppressWarnings("unchecked") - private void setLayoutActionStateByClassName(String layoutClassName) { - try { - Class classInstance = Class.forName(layoutClassName); - layoutAction.setCurrentActionStateByUserData( - (Class) classInstance); + if (layoutName == null) { + return; // this may be null when coming from an older version of Ghidra } - catch (ClassNotFoundException e) { - // give up...leave the action the way it was + + List> states = layoutAction.getAllActionStates(); + for (ActionState state : states) { + FGLayoutProvider layoutProvider = state.getUserData(); + String stateLayoutName = layoutProvider.getLayoutName(); + if (stateLayoutName.equals(layoutName)) { + layoutAction.setCurrentActionState(state); + return; + } } } @@ -1216,11 +1210,11 @@ class FGActionManager { togglePopups.setSelected(visible); } - void setCurrentActionState(ActionState> state) { + void setCurrentActionState(ActionState state) { layoutAction.setCurrentActionState(state); } - ActionState> getCurrentLayoutState() { + ActionState getCurrentLayoutState() { return layoutAction.getCurrentState(); } @@ -1233,14 +1227,26 @@ class FGActionManager { vertexFocusModeAction.getCurrentState().getUserData()); vertexFocusModeAction.setCurrentActionStateByUserData(selectedState); - String layoutClassName = - saveState.getString(LAYOUT_NAME, layoutAction.getCurrentUserData().getName()); - setLayoutActionStateByClassName(layoutClassName); + FGLayoutProvider layoutProvider = layoutAction.getCurrentUserData(); + SaveState layoutState = saveState.getSaveState(COMPLEX_LAYOUT_NAME); + if (layoutState != null) { + String layoutName = layoutState.getString(LAYOUT_NAME, layoutProvider.getLayoutName()); + String layoutClassName = + layoutState.getString(LAYOUT_CLASS_NAME, layoutProvider.getClass().getName()); + setLayoutActionStateByClassName(layoutClassName, layoutName); + } } void writeConfigState(SaveState saveState) { saveState.putEnum(EDGE_HOVER_HIGHLIGHT, vertexHoverModeAction.getCurrentUserData()); saveState.putEnum(EDGE_SELECTION_HIGHLIGHT, vertexFocusModeAction.getCurrentUserData()); - saveState.putString(LAYOUT_NAME, layoutAction.getCurrentUserData().getName()); + + FGLayoutProvider layoutProvider = layoutAction.getCurrentUserData(); + + SaveState layoutState = new SaveState(COMPLEX_LAYOUT_NAME); + String layoutName = layoutProvider.getLayoutName(); + layoutState.putString(LAYOUT_NAME, layoutName); + layoutState.putString(LAYOUT_CLASS_NAME, layoutProvider.getClass().getName()); + saveState.putSaveState(COMPLEX_LAYOUT_NAME, layoutState); } } diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FunctionGraph.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FunctionGraph.java index bf32398794..e148d08833 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FunctionGraph.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FunctionGraph.java @@ -161,12 +161,10 @@ public class FunctionGraph extends GroupingVisualGraph { settings.clearVertexLocation(vertex); } - // TODO make private? public void clearSavedVertexLocations() { settings.clearVertexLocations(this); } - // TODO make private? public void clearAllUserLayoutSettings() { clearSavedVertexLocations(); settings.clearGroupSettings(this); @@ -585,6 +583,15 @@ public class FunctionGraph extends GroupingVisualGraph { Collection v = getVertices(); Collection e = getEdges(); FunctionGraph newGraph = new FunctionGraph(getFunction(), getSettings(), v, e); + + FGLayout originalLayout = getLayout(); + FGLayout newLayout = originalLayout.cloneLayout(newGraph); + + // setSize() must be called after setGraphLayout() due to callbacks performed when + // setSize() is called + newGraph.setGraphLayout(newLayout); + newLayout.setSize(originalLayout.getSize()); + newGraph.setOptions(getOptions()); return newGraph; } diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/ExperimentalLayoutProvider.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/ExperimentalLayoutProvider.java index 0844e72bc8..09064373bd 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/ExperimentalLayoutProvider.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/ExperimentalLayoutProvider.java @@ -19,7 +19,7 @@ import javax.swing.Icon; import resources.ResourceManager; -public abstract class ExperimentalLayoutProvider extends FGLayoutProvider { +public abstract class ExperimentalLayoutProvider extends FGLayoutProviderExtensionPoint { private static final Icon ICON = ResourceManager.loadImage("images/package_development.png"); diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/jung/renderer/FGArticulatedEdgeTransformer.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/FGLayoutProviderExtensionPoint.java similarity index 55% rename from Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/jung/renderer/FGArticulatedEdgeTransformer.java rename to Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/FGLayoutProviderExtensionPoint.java index b28dc5241c..0b8e51477a 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/jung/renderer/FGArticulatedEdgeTransformer.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/FGLayoutProviderExtensionPoint.java @@ -13,25 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.app.plugin.core.functiongraph.graph.jung.renderer; +package ghidra.app.plugin.core.functiongraph.graph.layout; import ghidra.app.plugin.core.functiongraph.graph.FGEdge; +import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph; import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex; -import ghidra.graph.viewer.shape.ArticulatedEdgeTransformer; -import ghidra.program.model.symbol.FlowType; +import ghidra.graph.viewer.layout.LayoutProviderExtensionPoint; -public class FGArticulatedEdgeTransformer extends ArticulatedEdgeTransformer { - - @Override - public int getOverlapOffset(FGEdge edge) { - - FlowType flowType = edge.getFlowType(); - if (!flowType.isUnConditional() && flowType.isJump()) { - return -OVERLAPPING_EDGE_OFFSET; - } - else if (flowType.isFallthrough()) { - return OVERLAPPING_EDGE_OFFSET; - } - return 0; - } +/** + * A version of {@link FGLayoutProvider} that is discoverable at runtime. Layouts that do not wish + * to be discoverable should implement {@link FGLayoutProvider} directly, not this interface. + */ +//@formatter:off +public abstract class FGLayoutProviderExtensionPoint + extends FGLayoutProvider + implements LayoutProviderExtensionPoint { +//@formatter:on } diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/jungrapht/JgtLayoutFactory.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/jungrapht/JgtLayoutFactory.java new file mode 100644 index 0000000000..c219b4d224 --- /dev/null +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/jungrapht/JgtLayoutFactory.java @@ -0,0 +1,175 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.functiongraph.graph.layout.jungrapht; + +import static ghidra.service.graph.LayoutAlgorithmNames.*; + +import java.awt.Shape; +import java.util.Comparator; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.jungrapht.visualization.layout.algorithms.*; +import org.jungrapht.visualization.layout.algorithms.LayoutAlgorithm.Builder; +import org.jungrapht.visualization.layout.algorithms.sugiyama.Layering; +import org.jungrapht.visualization.layout.algorithms.util.VertexBoundsFunctionConsumer; +import org.jungrapht.visualization.layout.model.Rectangle; + +import ghidra.app.plugin.core.functiongraph.graph.FGEdge; +import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex; +import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer; +import ghidra.util.Msg; + +/** + * A class that supplies Jung graph layouts to the Function Graph API. + * + * @param the vertex type + * @param the edge type + */ +public class JgtLayoutFactory { + + private static List layoutNames = List.of( + COMPACT_HIERARCHICAL, + HIERACHICAL, + MIN_CROSS_TOP_DOWN, + MIN_CROSS_LONGEST_PATH, + MIN_CROSS_NETWORK_SIMPLEX, + MIN_CROSS_COFFMAN_GRAHAM, + VERT_MIN_CROSS_TOP_DOWN, + VERT_MIN_CROSS_LONGEST_PATH, + VERT_MIN_CROSS_NETWORK_SIMPLEX, + VERT_MIN_CROSS_COFFMAN_GRAHAM); + + private Predicate favoredEdgePredicate; + private Comparator edgeTypeComparator; + private Predicate rootPredicate; + + public JgtLayoutFactory(Comparator comparator, Predicate favoredEdgePredicate, + Predicate rootPredicate) { + this.edgeTypeComparator = comparator; + this.favoredEdgePredicate = favoredEdgePredicate; + this.rootPredicate = rootPredicate; + } + + public static List getSupportedLayoutNames() { + return layoutNames; + } + + public LayoutAlgorithm getLayout(String name) { + + Builder layoutBuilder = doGetLayout(name); + LayoutAlgorithm layout = layoutBuilder.build(); + + if (layout instanceof TreeLayout) { + ((TreeLayout) layout).setRootPredicate(rootPredicate); + } + + if (layout instanceof VertexBoundsFunctionConsumer) { + @SuppressWarnings("unchecked") + VertexBoundsFunctionConsumer boundsLayout = + (VertexBoundsFunctionConsumer) layout; + Function vertexBoundsFunction = new FGVertexShapeFunction(); + boundsLayout.setVertexBoundsFunction(vertexBoundsFunction); + } + + // we should not need to set the max level, since our graphs do not get too many vertices + // layoutAlgorithm.setMaxLevelCrossFunction(...); + + return layout; + } + + private LayoutAlgorithm.Builder doGetLayout(String name) { + switch (name) { + case COMPACT_HIERARCHICAL: + return TidierTreeLayoutAlgorithm + . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator); + case HIERACHICAL: + return EdgeAwareTreeLayoutAlgorithm + . edgeAwareBuilder(); + case MIN_CROSS_TOP_DOWN: + return EiglspergerLayoutAlgorithm + . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) + .layering(Layering.TOP_DOWN) + .threaded(false); + case MIN_CROSS_LONGEST_PATH: + return EiglspergerLayoutAlgorithm + . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) + .layering(Layering.LONGEST_PATH) + .threaded(false); + case MIN_CROSS_NETWORK_SIMPLEX: + return EiglspergerLayoutAlgorithm + . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) + .layering(Layering.NETWORK_SIMPLEX) + .threaded(false); + case MIN_CROSS_COFFMAN_GRAHAM: + return EiglspergerLayoutAlgorithm + . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) + .layering(Layering.COFFMAN_GRAHAM) + .threaded(false); + case VERT_MIN_CROSS_TOP_DOWN: + return EiglspergerLayoutAlgorithm + . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) + .favoredEdgePredicate(favoredEdgePredicate) + .layering(Layering.TOP_DOWN) + .threaded(false); + case VERT_MIN_CROSS_LONGEST_PATH: + return EiglspergerLayoutAlgorithm + . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) + .favoredEdgePredicate(favoredEdgePredicate) + .layering(Layering.LONGEST_PATH) + .threaded(false); + case VERT_MIN_CROSS_NETWORK_SIMPLEX: + return EiglspergerLayoutAlgorithm + . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) + .favoredEdgePredicate(favoredEdgePredicate) + .layering(Layering.NETWORK_SIMPLEX) + .threaded(false); + case VERT_MIN_CROSS_COFFMAN_GRAHAM: + return EiglspergerLayoutAlgorithm + . edgeAwareBuilder() + .edgeComparator(edgeTypeComparator) + .favoredEdgePredicate(favoredEdgePredicate) + .layering(Layering.COFFMAN_GRAHAM) + .threaded(false); + default: + Msg.error(this, "Unknown graph layout type: '" + name + "'"); + return null; + } + } + + private class FGVertexShapeFunction implements Function { + + private VisualGraphVertexShapeTransformer vgShaper = + new VisualGraphVertexShapeTransformer<>(); + + @Override + public Rectangle apply(FGVertex v) { + + Shape shape = vgShaper.apply(v); + java.awt.Rectangle r = shape.getBounds(); + return Rectangle.of(r.x, r.y, r.width, r.height); + } + } +} diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/jungrapht/JgtNamedLayout.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/jungrapht/JgtNamedLayout.java new file mode 100644 index 0000000000..9cba10df65 --- /dev/null +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/jungrapht/JgtNamedLayout.java @@ -0,0 +1,259 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.functiongraph.graph.layout.jungrapht; + +import java.awt.Dimension; +import java.awt.geom.Point2D; +import java.util.*; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.jgrapht.graph.AbstractBaseGraph; +import org.jgrapht.graph.DefaultGraphType; +import org.jungrapht.visualization.layout.algorithms.LayoutAlgorithm; +import org.jungrapht.visualization.layout.algorithms.util.EdgeArticulationFunctionSupplier; +import org.jungrapht.visualization.layout.model.LayoutModel; +import org.jungrapht.visualization.layout.model.Point; + +import ghidra.app.plugin.core.functiongraph.graph.FGEdge; +import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph; +import ghidra.app.plugin.core.functiongraph.graph.layout.AbstractFGLayout; +import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex; +import ghidra.graph.VisualGraph; +import ghidra.graph.viewer.layout.*; +import ghidra.program.model.symbol.FlowType; +import ghidra.program.model.symbol.RefType; +import ghidra.util.exception.CancelledException; + +/** + * A layout that delegates work to the Jung layout specified in the constructor. + */ +public class JgtNamedLayout extends AbstractFGLayout { + + private static Function> DUMMY_ARTICULATOR = e -> Collections.emptyList(); + + JgtNamedLayout(FunctionGraph graph, String layoutName) { + super(graph, layoutName); + } + + @Override + protected AbstractVisualGraphLayout createClonedFGLayout( + FunctionGraph newGraph) { + return new JgtNamedLayout(newGraph, layoutName); + } + + @Override + protected Point2D getVertexLocation(FGVertex v, Column col, Row row, + java.awt.Rectangle bounds) { + return getCenteredVertexLocation(v, col, row, bounds); + } + + @Override + protected GridLocationMap performInitialGridLayout( + VisualGraph visualGraph) throws CancelledException { + + FGEdgeComparator edgeComparator = new FGEdgeComparator(); + Predicate favoredEdgePredicate = getFavoredEdgePredicate(); + Predicate rootPredicate = null; + + JgtLayoutFactory layoutProvider = + new JgtLayoutFactory<>(edgeComparator, favoredEdgePredicate, rootPredicate); + + LayoutAlgorithm layout = layoutProvider.getLayout(layoutName); + + FGTempGraph jGraph = buildGraph(visualGraph); + + VisualGraphLayout vgLayout = visualGraph.getLayout(); + Dimension layoutSize = vgLayout.getSize(); + + LayoutModel layoutModel = + LayoutModel. builder() + .graph(jGraph) + .size(layoutSize.width, layoutSize.height) + .build(); + + layoutModel.accept(layout); + + GridLocationMap grid = convertToGrid(jGraph, layoutModel, layout); + + return grid; + } + + private GridLocationMap convertToGrid(FGTempGraph jGraph, + LayoutModel layoutModel, + LayoutAlgorithm layoutAlgorithm) + throws CancelledException { + + GridLocationMap grid = new GridLocationMap<>(); + + Map columns = new TreeMap<>(); + Map rows = new TreeMap<>(); + + Set jungVertices = jGraph.vertexSet(); + for (FGVertex fgVertex : jungVertices) { + monitor.checkCanceled(); + + Point point = layoutModel.get(fgVertex); + columns.put(point.x, 0); + rows.put(point.y, 0); + } + + Function> articulator = getArticulator(layoutAlgorithm); + Set edges = jGraph.edgeSet(); + for (FGEdge fgEdge : edges) { + monitor.checkCanceled(); + + List ariculations = articulator.apply(fgEdge); + for (Point point : ariculations) { + columns.put(point.x, 0); + rows.put(point.y, 0); + } + } + + // translate the real coordinates to grid coordinates (row and column indices) + int counter = 0; + for (Double x : columns.keySet()) { + monitor.checkCanceled(); + columns.put(x, counter++); + } + + counter = 0; + for (Double y : rows.keySet()) { + monitor.checkCanceled(); + rows.put(y, counter++); + } + + jungVertices = jGraph.vertexSet(); + for (FGVertex fgVertex : jungVertices) { + monitor.checkCanceled(); + + Point point = layoutModel.get(fgVertex); + grid.set(fgVertex, rows.get(point.y), columns.get(point.x)); + } + + edges = jGraph.edgeSet(); + for (FGEdge fgEdge : edges) { + monitor.checkCanceled(); + + List newPoints = new ArrayList<>(); + + List articulations = articulator.apply(fgEdge); + for (Point point : articulations) { + + Integer col = columns.get(point.x); + Integer row = rows.get(point.y); + newPoints.add(new java.awt.Point(col, row)); + } + + // The jung layout will provide articulations at the vertex points. We do not want to + // use these values, since we may move the vertices during layout. Our renderer will + // connect the articulation endpoints to the vertices when drawing, so we do not need + // these points provided by jung. + if (!articulations.isEmpty()) { + newPoints.remove(0); + newPoints.remove(newPoints.size() - 1); + } + + grid.setArticulations(fgEdge, newPoints); + } + + return grid; + } + + private Function> getArticulator( + LayoutAlgorithm layout) { + + if (layout instanceof EdgeArticulationFunctionSupplier) { + @SuppressWarnings("unchecked") + EdgeArticulationFunctionSupplier supplier = + (EdgeArticulationFunctionSupplier) layout; + return supplier.getEdgeArticulationFunction(); + } + + return DUMMY_ARTICULATOR; + } + + private FGTempGraph buildGraph(VisualGraph visualGraph) { + + FGTempGraph tempGraph = new FGTempGraph(); + + Collection vertices = visualGraph.getVertices(); + for (FGVertex v : vertices) { + tempGraph.addVertex(v); + } + + Collection edges = visualGraph.getEdges(); + for (FGEdge e : edges) { + tempGraph.addEdge(e.getStart(), e.getEnd(), e); + } + + return tempGraph; + } + + private Predicate getFavoredEdgePredicate() { + return e -> e.getFlowType().equals(RefType.FALL_THROUGH); + } + + private class FGTempGraph extends AbstractBaseGraph { + + protected FGTempGraph() { + super(null, null, DefaultGraphType.directedPseudograph()); + } + + } + + private class FGEdgeComparator implements Comparator { + + // TODO we can update the edge priority used when layout out the graph, which is what the + // generic graphing does. In order to change edge priorities, we would have to verify + // the effects on the layout are desirable for the Function Graph. + // private Map edgePriorityMap = new HashMap<>(); + + public FGEdgeComparator() { + + /* + + // populate map with RefType values; defined in priority order + + int priority = 0; + edgePriorityMap.put("Fall-Through", priority++); + edgePriorityMap.put("Conditional-Return", priority++); + edgePriorityMap.put("Unconditional-Jump", priority++); + edgePriorityMap.put("Conditional-Jump", priority++); + edgePriorityMap.put("Unconditional-Call", priority++); + edgePriorityMap.put("Conditional-Call", priority++); + edgePriorityMap.put("Terminator", priority++); + edgePriorityMap.put("Computed", priority++); + edgePriorityMap.put("Indirection", priority++); + edgePriorityMap.put("Entry", priority++); + + */ + } + + @Override + public int compare(FGEdge e1, FGEdge e2) { + return priority(e1).compareTo(priority(e2)); + } + + private Integer priority(FGEdge e) { + FlowType type = e.getFlowType(); + if (type == RefType.FALL_THROUGH) { + return 1; // lower is more preferred + } + return 10; + } + } +} diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/jungrapht/JgtNamedLayoutProvider.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/jungrapht/JgtNamedLayoutProvider.java new file mode 100644 index 0000000000..6b75f2a6b6 --- /dev/null +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/jungrapht/JgtNamedLayoutProvider.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.app.plugin.core.functiongraph.graph.layout.jungrapht; + +import javax.swing.Icon; + +import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph; +import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayout; +import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * A layout provider that allows us to specify a Jung layout by name. + */ +public class JgtNamedLayoutProvider extends FGLayoutProvider { + + private String layoutName; + + public JgtNamedLayoutProvider(String layoutName) { + this.layoutName = layoutName; + } + + @Override + public String getLayoutName() { + return layoutName; + } + + @Override + public Icon getActionIcon() { + return null; // no good icon + } + + @Override + public int getPriorityLevel() { + // low priority than other layouts; other layouts use 200, 101 and 100 + return 75; + } + + @Override + public FGLayout getFGLayout(FunctionGraph graph, TaskMonitor monitor) + throws CancelledException { + JgtNamedLayout layout = new JgtNamedLayout(graph, layoutName); + layout.setTaskMonitor(monitor); + return layout; + } + + @Override + public String toString() { + return layoutName; + } +} diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGController.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGController.java index ba72707128..6a4515e331 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGController.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGController.java @@ -51,7 +51,6 @@ import ghidra.framework.options.ToolOptions; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.util.OptionsService; import ghidra.graph.viewer.*; -import ghidra.graph.viewer.layout.LayoutProvider; import ghidra.program.model.address.*; import ghidra.program.model.listing.*; import ghidra.program.model.symbol.*; @@ -675,12 +674,9 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi return; } - @SuppressWarnings("rawtypes") - Class previousLayoutClass = previousLayout.getClass(); - @SuppressWarnings("rawtypes") - Class newLayoutClass = newLayout.getClass(); - - if (previousLayoutClass == newLayoutClass) { + String previousLayoutName = previousLayout.getLayoutName(); + String newLayoutName = newLayout.getLayoutName(); + if (previousLayoutName.equals(newLayoutName)) { view.relayout(); } else { diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/util/job/UngroupAllVertexFunctionGraphJob.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/util/job/UngroupAllVertexFunctionGraphJob.java index 1a72a89291..ac21c0a7ab 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/util/job/UngroupAllVertexFunctionGraphJob.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/util/job/UngroupAllVertexFunctionGraphJob.java @@ -34,9 +34,13 @@ public class UngroupAllVertexFunctionGraphJob implements GraphJob { @Override public void execute(GraphJobListener listener) { - controller.ungroupAllVertices(); - isFinished = true; - listener.jobFinished(this); + try { + controller.ungroupAllVertices(); + } + finally { + isFinished = true; + listener.jobFinished(this); + } } @Override diff --git a/Ghidra/Features/FunctionGraphDecompilerExtension/src/main/java/ghidra/app/plugin/core/functiongraph/graph/jung/renderer/DNLArticulatedEdgeTransformer.java b/Ghidra/Features/FunctionGraphDecompilerExtension/src/main/java/ghidra/app/plugin/core/functiongraph/graph/jung/renderer/DNLArticulatedEdgeTransformer.java deleted file mode 100644 index 78f780469b..0000000000 --- a/Ghidra/Features/FunctionGraphDecompilerExtension/src/main/java/ghidra/app/plugin/core/functiongraph/graph/jung/renderer/DNLArticulatedEdgeTransformer.java +++ /dev/null @@ -1,28 +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.app.plugin.core.functiongraph.graph.jung.renderer; - -import ghidra.app.plugin.core.functiongraph.graph.FGEdge; - -public class DNLArticulatedEdgeTransformer extends FGArticulatedEdgeTransformer { - - @Override - public int getOverlapOffset(FGEdge edge) { - // we position all locations ourself manually--no need to offset them to avoid overlapping - return 0; - } - -} diff --git a/Ghidra/Features/FunctionGraphDecompilerExtension/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/DecompilerNestedLayout.java b/Ghidra/Features/FunctionGraphDecompilerExtension/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/DecompilerNestedLayout.java index 5b9facc738..7bb0ba38ea 100644 --- a/Ghidra/Features/FunctionGraphDecompilerExtension/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/DecompilerNestedLayout.java +++ b/Ghidra/Features/FunctionGraphDecompilerExtension/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/DecompilerNestedLayout.java @@ -27,14 +27,11 @@ import org.apache.commons.collections4.BidiMap; import org.apache.commons.collections4.bidimap.DualHashBidiMap; import org.apache.commons.collections4.map.LazyMap; -import com.google.common.base.Function; - import edu.uci.ics.jung.visualization.renderers.Renderer.EdgeLabel; import ghidra.app.decompiler.DecompInterface; import ghidra.app.decompiler.DecompileOptions; import ghidra.app.plugin.core.functiongraph.graph.FGEdge; import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph; -import ghidra.app.plugin.core.functiongraph.graph.jung.renderer.DNLArticulatedEdgeTransformer; import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex; import ghidra.app.plugin.core.functiongraph.graph.vertex.GroupedFunctionGraphVertex; import ghidra.graph.VisualGraph; @@ -102,11 +99,6 @@ public class DecompilerNestedLayout extends AbstractFGLayout { } } - @Override - public Function getEdgeShapeTransformer() { - return new DNLArticulatedEdgeTransformer(); - } - @Override public EdgeLabel getEdgeLabelRenderer() { return new DNLEdgeLabelRenderer<>(getCondenseFactor()); diff --git a/Ghidra/Features/FunctionGraphDecompilerExtension/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/DecompilerNestedLayoutProvider.java b/Ghidra/Features/FunctionGraphDecompilerExtension/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/DecompilerNestedLayoutProvider.java index 259ddbe3ed..72314e8917 100644 --- a/Ghidra/Features/FunctionGraphDecompilerExtension/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/DecompilerNestedLayoutProvider.java +++ b/Ghidra/Features/FunctionGraphDecompilerExtension/src/main/java/ghidra/app/plugin/core/functiongraph/graph/layout/DecompilerNestedLayoutProvider.java @@ -22,7 +22,7 @@ import ghidra.framework.options.Options; import ghidra.util.task.TaskMonitor; import resources.ResourceManager; -public class DecompilerNestedLayoutProvider extends FGLayoutProvider { +public class DecompilerNestedLayoutProvider extends FGLayoutProviderExtensionPoint { private static final Icon ICON = ResourceManager.loadImage("images/function_graph_code_flow.png"); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateDockingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateDockingAction.java index 4fc055dff0..769c705e06 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateDockingAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateDockingAction.java @@ -55,6 +55,7 @@ public abstract class MultiStateDockingAction extends DockingAction { private MultipleActionDockingToolbarButton multipleButton; private boolean performActionOnPrimaryButtonClick = true; + private Icon defaultIcon; private boolean useCheckboxForIcons; // A listener that will get called when the button (not the popup) is clicked. Toolbar @@ -139,6 +140,17 @@ public abstract class MultiStateDockingAction extends DockingAction { this.useCheckboxForIcons = useCheckboxForIcons; } + /** + * Sets the icon to use if the active action state does not supply an icon. This is useful if + * you wish for your action states to not use icon, but desire the action itself to have an + * icon. + * + * @param icon the icon + */ + public void setDefaultIcon(Icon icon) { + this.defaultIcon = icon; + } + @Override public final void actionPerformed(ActionContext context) { if (!performActionOnPrimaryButtonClick) { @@ -270,6 +282,11 @@ public abstract class MultiStateDockingAction extends DockingAction { if (icon != null) { return icon; } + + if (defaultIcon != null) { + return defaultIcon; + } + return EMPTY_ICON; } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/SaveState.java b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/SaveState.java index 6abf44cc7f..dfd51848ce 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/SaveState.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/SaveState.java @@ -1411,6 +1411,11 @@ public class SaveState { return getAsType(name, null, SaveState.class); } + @Override + public String toString() { + return XmlUtilities.toString(saveToXml()); + } + private T getAsType(String name, T defaultValue, Class clazz) { if (map.containsKey(name)) { Object value = map.get(name); diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/job/AbstractAnimatorJob.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/job/AbstractAnimatorJob.java index 178be0eee9..d5044352bf 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/job/AbstractAnimatorJob.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/job/AbstractAnimatorJob.java @@ -88,7 +88,14 @@ public abstract class AbstractAnimatorJob implements GraphJob { void start() { trace("start() - " + getClass().getSimpleName()); - animator = createAnimator(); + try { + animator = createAnimator(); + } + catch (Throwable t) { + Msg.error(this, "Unexepected exception creating animator", t); + emergencyFinish(); + return; + } trace("\tcreated animator - " + animator); if (animator == null) { @@ -143,6 +150,27 @@ public abstract class AbstractAnimatorJob implements GraphJob { } } + private void emergencyFinish() { + trace("emergencyFinish()"); + if (isFinished) { + trace("\talready finished"); + return; // already called + } + + isFinished = true; + + // a null listener implies we were shortcut before we were started + trace("\tmaybe notify finished..."); + if (finishedListener != null) { + trace("\tlistener is not null--calling"); + finishedListener.jobFinished(this); + } + + if (busyListener != null) { + busyListener.setBusy(false); + } + } + protected void stop() { trace("stop()"); diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/job/FitGraphToViewJob.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/job/FitGraphToViewJob.java index e890d54081..626b553bad 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/job/FitGraphToViewJob.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/job/FitGraphToViewJob.java @@ -73,8 +73,12 @@ public class FitGraphToViewJob> @Override public void execute(GraphJobListener listener) { - doExecute(); - listener.jobFinished(this); + try { + doExecute(); + } + finally { + listener.jobFinished(this); + } } private void doExecute() { diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/AbstractLayoutProvider.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/AbstractLayoutProvider.java index 1da480df76..88430d7ef5 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/AbstractLayoutProvider.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/AbstractLayoutProvider.java @@ -40,7 +40,7 @@ public abstract class AbstractLayoutProvider, G extends VisualGraph> - implements LayoutProvider { + implements LayoutProviderExtensionPoint { //@formatter:on @Override diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/AbstractVisualGraphLayout.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/AbstractVisualGraphLayout.java index 46ef566245..bfee9c5aaf 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/AbstractVisualGraphLayout.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/AbstractVisualGraphLayout.java @@ -326,11 +326,12 @@ public abstract class AbstractVisualGraphLayout> edgeLayoutArticulations = positionEdgeArticulationsInLayoutSpace( - transformer, vertexLayoutLocations, edges, layoutLocations); + Map> edgeLayoutArticulations = + positionEdgeArticulationsInLayoutSpace(transformer, vertexLayoutLocations, edges, + layoutLocations); if (isCondensed) { - // note: some layouts will not condense the edges, as they perform custom routing + // note: some layouts will not condense the edges, as they perform custom routing List> rows = gridLocations.rows(); condenseEdges(rows, edgeLayoutArticulations, centerX, centerY); } diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/Column.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/Column.java index 1f7b430d4e..59ab7045c3 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/Column.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/Column.java @@ -51,7 +51,14 @@ public class Column { @Override public String toString() { - return getClass().getSimpleName() + "[x=" + x + ", width=" + width + ", padded width=" + - getPaddedWidth(false) + "]"; + + //@formatter:off + return getClass().getSimpleName() + "{\n" + + "\tcolumn: " + index + ",\n" + + "\tx: " + x + ",\n" + + "\twidth: " + width + ",\n" + + "\tpadded width: " + getPaddedWidth(false) + "\n" + + "}"; + //@formatter:on } } diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/LayoutProvider.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/LayoutProvider.java index cf5715171c..bba9c2305d 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/LayoutProvider.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/LayoutProvider.java @@ -20,7 +20,6 @@ import javax.swing.Icon; import ghidra.graph.VisualGraph; import ghidra.graph.viewer.VisualEdge; import ghidra.graph.viewer.VisualVertex; -import ghidra.util.classfinder.ExtensionPoint; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -40,9 +39,7 @@ import ghidra.util.task.TaskMonitor; //@formatter:off public interface LayoutProvider, - G extends VisualGraph> - - extends ExtensionPoint { + G extends VisualGraph> { //@formatter:on /** diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/LayoutProviderExtensionPoint.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/LayoutProviderExtensionPoint.java new file mode 100644 index 0000000000..0afae4b057 --- /dev/null +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/LayoutProviderExtensionPoint.java @@ -0,0 +1,39 @@ +/* ### + * 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.viewer.layout; + +import ghidra.graph.VisualGraph; +import ghidra.graph.viewer.VisualEdge; +import ghidra.graph.viewer.VisualVertex; +import ghidra.util.classfinder.ExtensionPoint; + +/** + * A version of {@link LayoutProvider} that is discoverable at runtime. Layouts that do not wish + * to be discoverable should implement {@link LayoutProvider} directly, not this interface. + * + * @param the vertex type + * @param the edge type + * @param the graph type + */ +//@formatter:off +public interface LayoutProviderExtensionPoint, + G extends VisualGraph> + + extends LayoutProvider, ExtensionPoint { +//@formatter:on + +} diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/Row.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/Row.java index 527f9ffefa..9a3e1eec73 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/Row.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/layout/Row.java @@ -30,6 +30,8 @@ import ghidra.graph.viewer.GraphViewerUtils; * *

This class maintains a collection of vertices on this row, organized by column index. You * can get the column of a vertex from {@link #getColumn(Object) getColumn(V)}. + * + * @param the vertex type */ public class Row { @@ -170,9 +172,16 @@ public class Row { @Override public String toString() { - return getClass().getSimpleName() + "[row=" + index + ", y=" + y + ", height=" + height + - ", padded height=" + getPaddedHeight(false) + ", column count=" + getColumnCount() + - "]"; + + //@formatter:off + return getClass().getSimpleName() + "{\n" + + "\trow: " + index + ",\n" + + "\ty: " + y + ",\n" + + "\theight: " + height + ",\n" + + "\tpadded height: " + getPaddedHeight(false) + ",\n" + + "\tcolumn count: " + getColumnCount() + "\n" + + "}"; + //@formatter:on } void dispose() { diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/renderer/ArticulatedEdgeRenderer.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/renderer/ArticulatedEdgeRenderer.java index bcc984c1f6..420767c458 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/renderer/ArticulatedEdgeRenderer.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/renderer/ArticulatedEdgeRenderer.java @@ -20,19 +20,15 @@ import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; import java.util.List; -import com.google.common.base.Function; - import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.visualization.Layer; import edu.uci.ics.jung.visualization.RenderContext; import ghidra.graph.viewer.*; import ghidra.graph.viewer.edge.VisualEdgeRenderer; -import ghidra.graph.viewer.shape.ArticulatedEdgeTransformer; public class ArticulatedEdgeRenderer> extends VisualEdgeRenderer { - @SuppressWarnings("unchecked") @Override public Shape getEdgeShape(RenderContext rc, Graph graph, E e, float x1, float y1, float x2, float y2, boolean isLoop, Shape vertexShape) { @@ -44,18 +40,14 @@ public class ArticulatedEdgeRenderer edgeShapeTransformer = rc.getEdgeShapeTransformer(); - if (edgeShapeTransformer instanceof ArticulatedEdgeTransformer) { - offset = ((ArticulatedEdgeTransformer) edgeShapeTransformer).getOverlapOffset(e); - } + // TODO investigate using the transformer directly + // Function edgeShapeTransformer = rc.getEdgeShapeTransformer(); List articulations = e.getArticulationPoints(); - offset = updateOffsetForLeftOrRightHandSizeEdge(rc, offset, x1, articulations); for (Point2D point : articulations) { double x = point.getX(); double y = point.getY(); - Point2D offsetPoint = new Point2D.Double(x + offset, y + offset); + Point2D offsetPoint = new Point2D.Double(x, y); point = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, offsetPoint); x = point.getX(); @@ -69,26 +61,4 @@ public class ArticulatedEdgeRenderer rc, int offset, float x, - List articulations) { - - int size = articulations.size(); - if (size == 0) { - // no articulations or start to destination only, with no angles - return offset; - } - - Point2D start = articulations.get(0); - start = rc.getMultiLayerTransformer().transform(Layer.LAYOUT, start); - double delta = x - start.getX(); - if (delta == 0) { - // don't move the edge when it is directly below the vertex (this prevents having - // a slightly skewed/misaligned edge) - return 0; - } - - boolean isLeft = delta > 0; - return isLeft ? -offset : offset; - } } diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/shape/ArticulatedEdgeTransformer.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/shape/ArticulatedEdgeTransformer.java index 3d8e9a4638..1fb83889cb 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/shape/ArticulatedEdgeTransformer.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/viewer/shape/ArticulatedEdgeTransformer.java @@ -19,7 +19,8 @@ import java.awt.Shape; import java.awt.geom.*; import java.util.List; -import edu.uci.ics.jung.visualization.decorators.ParallelEdgeShapeTransformer; +import com.google.common.base.Function; + import ghidra.graph.viewer.*; import ghidra.util.Msg; import ghidra.util.SystemUtilities; @@ -30,25 +31,13 @@ import ghidra.util.SystemUtilities; * @param the edge type */ public class ArticulatedEdgeTransformer> - extends ParallelEdgeShapeTransformer { - - protected static final int OVERLAPPING_EDGE_OFFSET = 10; + implements Function { /** - * Returns a value by which to offset edges that overlap. This is used to make the edges - * easier to see. + * Get the shape for this edge * - * @param edge the edge - * @return the offset value - */ - public int getOverlapOffset(E edge) { - // not sure what the correct default behavior is - return OVERLAPPING_EDGE_OFFSET; - } - - /** - * Get the shape for this edge, returning either the shared instance or, in - * the case of self-loop edges, the Loop shared instance. + * @param e the edge + * @return the edge shape */ @Override public Shape apply(E e) { @@ -76,7 +65,9 @@ public class ArticulatedEdgeTransformer rc, Layout layout, V vertex) { Graph graph = layout.getGraph(); - if (!rc.getVertexIncludePredicate().apply( - Context., V> getInstance(graph, vertex))) { + if (!rc.getVertexIncludePredicate() + .apply( + Context., V> getInstance(graph, vertex))) { return; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/RefType.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/RefType.java index 99e290654a..c2b1622055 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/RefType.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/RefType.java @@ -78,58 +78,120 @@ public abstract class RefType { static final byte __DYNAMICDATA = 127; public static final FlowType INVALID = - new FlowType.Builder(__INVALID, "INVALID").setHasFall().build(); + new FlowType.Builder(__INVALID, "INVALID") + .setHasFall() + .build(); public static final FlowType FLOW = - new FlowType.Builder(__UNKNOWNFLOW, "FLOW").setHasFall().build(); + new FlowType.Builder(__UNKNOWNFLOW, "FLOW") + .setHasFall() + .build(); public static final FlowType FALL_THROUGH = - new FlowType.Builder(__FALL_THROUGH, "FALL_THROUGH").setHasFall().build(); + new FlowType.Builder(__FALL_THROUGH, "FALL_THROUGH") + .setHasFall() + .build(); public static final FlowType UNCONDITIONAL_JUMP = - new FlowType.Builder(__UNCONDITIONAL_JUMP, "UNCONDITIONAL_JUMP").setIsJump().build(); - public static final FlowType CONDITIONAL_JUMP = new FlowType.Builder(__CONDITIONAL_JUMP, - "CONDITIONAL_JUMP").setHasFall().setIsJump().setIsConditional().build(); - public static final FlowType UNCONDITIONAL_CALL = new FlowType.Builder(__UNCONDITIONAL_CALL, - "UNCONDITIONAL_CALL").setHasFall().setIsCall().build(); - public static final FlowType CONDITIONAL_CALL = new FlowType.Builder(__CONDITIONAL_CALL, - "CONDITIONAL CALL").setHasFall().setIsCall().setIsConditional().build(); + new FlowType.Builder(__UNCONDITIONAL_JUMP, "UNCONDITIONAL_JUMP") + .setIsJump() + .build(); + public static final FlowType CONDITIONAL_JUMP = + new FlowType.Builder(__CONDITIONAL_JUMP, "CONDITIONAL_JUMP") + .setHasFall() + .setIsJump() + .setIsConditional() + .build(); + public static final FlowType UNCONDITIONAL_CALL = + new FlowType.Builder(__UNCONDITIONAL_CALL, "UNCONDITIONAL_CALL") + .setHasFall() + .setIsCall() + .build(); + public static final FlowType CONDITIONAL_CALL = + new FlowType.Builder(__CONDITIONAL_CALL, "CONDITIONAL_CALL") + .setHasFall() + .setIsCall() + .setIsConditional() + .build(); public static final FlowType TERMINATOR = - new FlowType.Builder(__TERMINATOR, "TERMINATOR").setIsTerminal().build(); + new FlowType.Builder(__TERMINATOR, "TERMINATOR") + .setIsTerminal() + .build(); public static final FlowType COMPUTED_JUMP = - new FlowType.Builder(__COMPUTED_JUMP, "COMPUTED_JUMP").setIsJump().setIsComputed().build(); + new FlowType.Builder(__COMPUTED_JUMP, "COMPUTED_JUMP") + .setIsJump() + .setIsComputed() + .build(); public static final FlowType CONDITIONAL_TERMINATOR = - new FlowType.Builder(__CONDITIONAL_TERMINATOR, - "CONDITIONAL_TERMINATOR").setHasFall().setIsTerminal().setIsConditional().build(); - public static final FlowType COMPUTED_CALL = new FlowType.Builder(__COMPUTED_CALL, - "COMPUTED_CALL").setHasFall().setIsCall().setIsComputed().build(); - public static final FlowType CALL_TERMINATOR = new FlowType.Builder(__CALL_TERMINATOR, - "CALL_TERMINATOR").setIsCall().setIsTerminal().build(); + new FlowType.Builder(__CONDITIONAL_TERMINATOR, "CONDITIONAL_TERMINATOR") + .setHasFall() + .setIsTerminal() + .setIsConditional() + .build(); + public static final FlowType COMPUTED_CALL = + new FlowType.Builder(__COMPUTED_CALL, "COMPUTED_CALL") + .setHasFall() + .setIsCall() + .setIsComputed() + .build(); + public static final FlowType CALL_TERMINATOR = + new FlowType.Builder(__CALL_TERMINATOR, "CALL_TERMINATOR") + .setIsCall() + .setIsTerminal() + .build(); public static final FlowType COMPUTED_CALL_TERMINATOR = - new FlowType.Builder(__COMPUTED_CALL_TERMINATOR, - "COMPUTED_CALL_TERMINATOR").setIsCall().setIsTerminal().setIsComputed().build(); + new FlowType.Builder(__COMPUTED_CALL_TERMINATOR, "COMPUTED_CALL_TERMINATOR") + .setIsCall() + .setIsTerminal() + .setIsComputed() + .build(); public static final FlowType CONDITIONAL_CALL_TERMINATOR = - new FlowType.Builder(__CONDITIONAL_CALL_TERMINATOR, - "CONDITIONAL_CALL_TERMINATOR").setIsCall().setIsTerminal().setIsConditional().build(); + new FlowType.Builder(__CONDITIONAL_CALL_TERMINATOR, "CONDITIONAL_CALL_TERMINATOR") + .setIsCall() + .setIsTerminal() + .setIsConditional() + .build(); public static final FlowType CONDITIONAL_COMPUTED_CALL = new FlowType.Builder( - __CONDITIONAL_COMPUTED_CALL, - "CONDITIONAL_COMPUTED_CALL").setHasFall().setIsCall().setIsComputed().setIsConditional().build(); - public static final FlowType CONDITIONAL_COMPUTED_JUMP = new FlowType.Builder( - __CONDITIONAL_COMPUTED_JUMP, - "CONDITIONAL_COMPUTED_JUMP").setHasFall().setIsJump().setIsComputed().setIsConditional().build(); - public static final FlowType JUMP_TERMINATOR = new FlowType.Builder(__JUMP_TERMINATOR, - "JUMP_TERMINATOR").setIsJump().setIsTerminal().build(); + __CONDITIONAL_COMPUTED_CALL, "CONDITIONAL_COMPUTED_CALL") + .setHasFall() + .setIsCall() + .setIsComputed() + .setIsConditional() + .build(); + public static final FlowType CONDITIONAL_COMPUTED_JUMP = + new FlowType.Builder(__CONDITIONAL_COMPUTED_JUMP, "CONDITIONAL_COMPUTED_JUMP") + .setHasFall() + .setIsJump() + .setIsComputed() + .setIsConditional() + .build(); + public static final FlowType JUMP_TERMINATOR = + new FlowType.Builder(__JUMP_TERMINATOR, "JUMP_TERMINATOR") + .setIsJump() + .setIsTerminal() + .build(); public static final FlowType INDIRECTION = - new FlowType.Builder(__INDIRECTION, "INDIRECTION").build(); + new FlowType.Builder(__INDIRECTION, "INDIRECTION") + .build(); public static final FlowType CALL_OVERRIDE_UNCONDITIONAL = - new FlowType.Builder(__CALL_OVERRIDE_UNCONDITIONAL, - "CALL_OVERRIDE_UNCONDITIONAL").setHasFall().setIsCall().setIsOverride().build(); + new FlowType.Builder(__CALL_OVERRIDE_UNCONDITIONAL, "CALL_OVERRIDE_UNCONDITIONAL") + .setHasFall() + .setIsCall() + .setIsOverride() + .build(); public static final FlowType JUMP_OVERRIDE_UNCONDITIONAL = - new FlowType.Builder(__JUMP_OVERRIDE_UNCONDITIONAL, - "JUMP_OVERRIDE_UNCONDITIONAL").setIsJump().setIsOverride().build(); + new FlowType.Builder(__JUMP_OVERRIDE_UNCONDITIONAL, "JUMP_OVERRIDE_UNCONDITIONAL") + .setIsJump() + .setIsOverride() + .build(); public static final FlowType CALLOTHER_OVERRIDE_CALL = - new FlowType.Builder(__CALLOTHER_OVERRIDE_CALL, - "CALLOTHER_OVERRIDE_CALL").setHasFall().setIsCall().setIsOverride().build(); + new FlowType.Builder(__CALLOTHER_OVERRIDE_CALL, "CALLOTHER_OVERRIDE_CALL") + .setHasFall() + .setIsCall() + .setIsOverride() + .build(); public static final FlowType CALLOTHER_OVERRIDE_JUMP = - new FlowType.Builder(__CALLOTHER_OVERRIDE_JUMP, - "CALLOTHER_OVERRIDE_JUMP").setIsJump().setIsOverride().build(); + new FlowType.Builder(__CALLOTHER_OVERRIDE_JUMP, "CALLOTHER_OVERRIDE_JUMP") + .setIsJump() + .setIsOverride() + .build(); /** * Reference type is unknown. @@ -154,30 +216,36 @@ public abstract class RefType { * Reference type assigned when data is being read. */ public static final RefType READ = new DataRefType(__READ, "READ", DataRefType.READX); + /** * Reference type assigned when data is being written. */ public static final RefType WRITE = new DataRefType(__WRITE, "WRITE", DataRefType.WRITEX); + /** * Reference type assigned when data is read and written. */ public static final RefType READ_WRITE = new DataRefType(__READ_WRITE, "READ_WRITE", DataRefType.READX | DataRefType.WRITEX); + /** * Reference type assigned when data is being read. */ public static final RefType READ_IND = new DataRefType(__READ_IND, "READ_IND", DataRefType.READX | DataRefType.INDX); + /** * Reference type assigned when data is being written. */ public static final RefType WRITE_IND = new DataRefType(__WRITE_IND, "WRITE_IND", DataRefType.WRITEX | DataRefType.INDX); + /** * Reference type assigned when data is read and written. */ public static final RefType READ_WRITE_IND = new DataRefType(__READ_WRITE_IND, "READ_WRITE_IND", DataRefType.READX | DataRefType.WRITEX | DataRefType.INDX); + /** * Reference type assigned for stack variable being read. * @deprecated use {@link RefType#READ} instead @@ -185,6 +253,7 @@ public abstract class RefType { @Deprecated public static final RefType STACK_READ = new DataRefType(__STACK_READ, "STACK_READ", DataRefType.READX); + /** * Reference type assigned for stack variable being written. * @deprecated use {@link RefType#WRITE} instead @@ -202,16 +271,14 @@ public abstract class RefType { private byte type; private String name; - /** - * Constructor - */ protected RefType(byte type, String name) { this.type = type; this.name = name; } /** - * Get the int value for this RefType object. + * Get the int value for this RefType object + * @return the value */ public byte getValue() { return type; @@ -219,20 +286,23 @@ public abstract class RefType { /** * Returns true if the reference is to data + * @return true if the reference is to data */ public boolean isData() { return false; } /** - * Returns true if the reference is a read. + * Returns true if the reference is a read + * @return true if the reference is a read */ public boolean isRead() { return false; } /** - * Returns true if the reference is a write. + * Returns true if the reference is a write + * @return true if the reference is a write */ public boolean isWrite() { return false; @@ -240,6 +310,7 @@ public abstract class RefType { /** * Returns true if the reference is indirect + * @return true if the reference is indirect */ public boolean isIndirect() { if (this == INDIRECTION) { @@ -249,22 +320,24 @@ public abstract class RefType { } /** - * Returns true if the reference is an instruction flow reference. + * Returns true if the reference is an instruction flow reference + * @return true if the reference is an instruction flow reference */ public boolean isFlow() { return false; } /** - * Return true if this flow type is one that does not cause - * a break in control flow. + * Return true if this flow type is one that does not cause a break in control flow + * @return if this flow type is one that does not cause a break in control flow */ public final boolean isFallthrough() { return this == FALL_THROUGH; } /** - * Returns true if this flow type can fall through. + * Returns true if this flow type can fall through + * @return true if can fall through */ public boolean hasFallthrough() { return false; @@ -272,6 +345,7 @@ public abstract class RefType { /** * Returns true if the flow is call + * @return true if is a call */ public boolean isCall() { return false; @@ -279,50 +353,60 @@ public abstract class RefType { /** * Returns true if the flow is jump + * @return true if is a jump */ public boolean isJump() { return false; } /** - * Returns true if the flow is an unconditional call or jump. + * Returns true if the flow is an unconditional call or jump + * @return true if unconditional */ public boolean isUnConditional() { return !isConditional(); } /** - * Returns true if the flow is a conditional call or jump. + * Returns true if the flow is a conditional call or jump + * @return true if is conditional */ public boolean isConditional() { return false; } /** - * Returns true if the flow is a computed call or compute jump. + * Returns true if the flow is a computed call or compute jump + * @return true if is computed */ public boolean isComputed() { return false; } /** - * returns true if this instruction terminates. + * Returns true if this instruction terminates + * @return true if terminal */ public boolean isTerminal() { return false; } /** - * - * @return true precisely when the reference is an overriding reference + * True if this is an override reference + * @return true if this is an override reference */ public boolean isOverride() { return false; } /** - * @see java.lang.Object#equals(java.lang.Object) + * Returns name of ref-type + * @return the name */ + public String getName() { + return name; + } + @Override public boolean equals(Object obj) { if (obj == null || !getClass().equals(obj.getClass())) { @@ -332,28 +416,13 @@ public abstract class RefType { return type == other.type; } - /** - * @see java.lang.Object#hashCode() - */ @Override public int hashCode() { return type; } - /** - * - * @see java.lang.Object#toString() - */ @Override public String toString() { return name; } - - /** - * Returns name of ref-type - */ - public String getName() { - return name; - } - }