mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 09:49:23 +02:00
GP-926 - Function Graph - Layouts - Created generic
layout provider to use jung layouts by name
This commit is contained in:
parent
2da717d56a
commit
c8e359ddec
28 changed files with 882 additions and 254 deletions
|
@ -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')
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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<FGLayoutProvider> findLayouts() {
|
||||
List<FGLayoutProvider> instances = ClassSearcher.getInstances(FGLayoutProvider.class);
|
||||
return instances;
|
||||
|
||||
List<FGLayoutProvider> list = new ArrayList<>();
|
||||
|
||||
// add discovered layouts
|
||||
List<FGLayoutProvider> instances =
|
||||
ClassSearcher.getInstances(FGLayoutProvider.class);
|
||||
list.addAll(instances);
|
||||
|
||||
// add hand-picked, generated layout providers
|
||||
List<String> jgtLayoutNames = JgtLayoutFactory.getSupportedLayoutNames();
|
||||
for (String name : jgtLayoutNames) {
|
||||
list.add(new JgtNamedLayoutProvider(name));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<EdgeDisplayType> vertexHoverModeAction;
|
||||
private MultiStateDockingAction<EdgeDisplayType> vertexFocusModeAction;
|
||||
|
||||
private MultiStateDockingAction<Class<? extends FGLayoutProvider>> layoutAction;
|
||||
private MultiStateDockingAction<FGLayoutProvider> 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<? extends FGLayoutProvider> currentUserData = getCurrentUserData();
|
||||
FGLayoutProvider currentUserData = getCurrentUserData();
|
||||
changeLayout(currentUserData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionStateChanged(
|
||||
ActionState<Class<? extends FGLayoutProvider>> newActionState,
|
||||
public void actionStateChanged(ActionState<FGLayoutProvider> 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<ActionState<Class<? extends FGLayoutProvider>>> 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<ActionState<FGLayoutProvider>> actionStates =
|
||||
loadActionStatesForLayoutProviders();
|
||||
|
||||
for (ActionState<Class<? extends FGLayoutProvider>> actionState : actionStates) {
|
||||
for (ActionState<FGLayoutProvider> actionState : actionStates) {
|
||||
layoutAction.addActionState(actionState);
|
||||
}
|
||||
|
||||
provider.addLocalAction(layoutAction);
|
||||
}
|
||||
|
||||
private void changeLayout(Class<? extends FGLayoutProvider> layoutClass) {
|
||||
FGLayoutProvider layoutInstance = getLayoutInstance(layoutClass);
|
||||
if (layoutInstance == null) {
|
||||
return;
|
||||
}
|
||||
controller.changeLayout(layoutInstance);
|
||||
private void changeLayout(FGLayoutProvider layout) {
|
||||
controller.changeLayout(layout);
|
||||
}
|
||||
|
||||
private List<ActionState<Class<? extends FGLayoutProvider>>> loadActionStatesForLayoutProviders() {
|
||||
private List<ActionState<FGLayoutProvider>> loadActionStatesForLayoutProviders() {
|
||||
|
||||
List<FGLayoutProvider> layoutInstances = plugin.getLayoutProviders();
|
||||
List<ActionState<Class<? extends FGLayoutProvider>>> list = new ArrayList<>();
|
||||
List<ActionState<FGLayoutProvider>> list = new ArrayList<>();
|
||||
HelpLocation layoutHelpLocation =
|
||||
new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout");
|
||||
for (FGLayoutProvider layout : layoutInstances) {
|
||||
|
||||
ActionState<Class<? extends FGLayoutProvider>> layoutState = new ActionState<>(
|
||||
layout.getLayoutName(), layout.getActionIcon(), layout.getClass());
|
||||
ActionState<FGLayoutProvider> 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<? extends FGLayoutProvider> layoutClass) {
|
||||
FGLayoutProvider layoutInstance = null;
|
||||
try {
|
||||
Constructor<? extends FGLayoutProvider> 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<? extends FGLayoutProvider>) 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<ActionState<FGLayoutProvider>> states = layoutAction.getAllActionStates();
|
||||
for (ActionState<FGLayoutProvider> 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<Class<? extends FGLayoutProvider>> state) {
|
||||
void setCurrentActionState(ActionState<FGLayoutProvider> state) {
|
||||
layoutAction.setCurrentActionState(state);
|
||||
}
|
||||
|
||||
ActionState<Class<? extends FGLayoutProvider>> getCurrentLayoutState() {
|
||||
ActionState<FGLayoutProvider> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -161,12 +161,10 @@ public class FunctionGraph extends GroupingVisualGraph<FGVertex, FGEdge> {
|
|||
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<FGVertex, FGEdge> {
|
|||
Collection<FGVertex> v = getVertices();
|
||||
Collection<FGEdge> 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;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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<FGVertex, FGEdge> {
|
||||
|
||||
@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<FGVertex, FGEdge, FunctionGraph> {
|
||||
//@formatter:on
|
||||
}
|
|
@ -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 <V> the vertex type
|
||||
* @param <E> the edge type
|
||||
*/
|
||||
public class JgtLayoutFactory<V extends FGVertex, E extends FGEdge> {
|
||||
|
||||
private static List<String> 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<E> favoredEdgePredicate;
|
||||
private Comparator<E> edgeTypeComparator;
|
||||
private Predicate<V> rootPredicate;
|
||||
|
||||
public JgtLayoutFactory(Comparator<E> comparator, Predicate<E> favoredEdgePredicate,
|
||||
Predicate<V> rootPredicate) {
|
||||
this.edgeTypeComparator = comparator;
|
||||
this.favoredEdgePredicate = favoredEdgePredicate;
|
||||
this.rootPredicate = rootPredicate;
|
||||
}
|
||||
|
||||
public static List<String> getSupportedLayoutNames() {
|
||||
return layoutNames;
|
||||
}
|
||||
|
||||
public LayoutAlgorithm<V> getLayout(String name) {
|
||||
|
||||
Builder<V, ?, ?> layoutBuilder = doGetLayout(name);
|
||||
LayoutAlgorithm<V> layout = layoutBuilder.build();
|
||||
|
||||
if (layout instanceof TreeLayout) {
|
||||
((TreeLayout<V>) layout).setRootPredicate(rootPredicate);
|
||||
}
|
||||
|
||||
if (layout instanceof VertexBoundsFunctionConsumer) {
|
||||
@SuppressWarnings("unchecked")
|
||||
VertexBoundsFunctionConsumer<FGVertex> boundsLayout =
|
||||
(VertexBoundsFunctionConsumer<FGVertex>) layout;
|
||||
Function<FGVertex, Rectangle> 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<V, ?, ?> doGetLayout(String name) {
|
||||
switch (name) {
|
||||
case COMPACT_HIERARCHICAL:
|
||||
return TidierTreeLayoutAlgorithm
|
||||
.<V, E> edgeAwareBuilder()
|
||||
.edgeComparator(edgeTypeComparator);
|
||||
case HIERACHICAL:
|
||||
return EdgeAwareTreeLayoutAlgorithm
|
||||
.<V, E> edgeAwareBuilder();
|
||||
case MIN_CROSS_TOP_DOWN:
|
||||
return EiglspergerLayoutAlgorithm
|
||||
.<V, E> edgeAwareBuilder()
|
||||
.edgeComparator(edgeTypeComparator)
|
||||
.layering(Layering.TOP_DOWN)
|
||||
.threaded(false);
|
||||
case MIN_CROSS_LONGEST_PATH:
|
||||
return EiglspergerLayoutAlgorithm
|
||||
.<V, E> edgeAwareBuilder()
|
||||
.edgeComparator(edgeTypeComparator)
|
||||
.layering(Layering.LONGEST_PATH)
|
||||
.threaded(false);
|
||||
case MIN_CROSS_NETWORK_SIMPLEX:
|
||||
return EiglspergerLayoutAlgorithm
|
||||
.<V, E> edgeAwareBuilder()
|
||||
.edgeComparator(edgeTypeComparator)
|
||||
.layering(Layering.NETWORK_SIMPLEX)
|
||||
.threaded(false);
|
||||
case MIN_CROSS_COFFMAN_GRAHAM:
|
||||
return EiglspergerLayoutAlgorithm
|
||||
.<V, E> edgeAwareBuilder()
|
||||
.edgeComparator(edgeTypeComparator)
|
||||
.layering(Layering.COFFMAN_GRAHAM)
|
||||
.threaded(false);
|
||||
case VERT_MIN_CROSS_TOP_DOWN:
|
||||
return EiglspergerLayoutAlgorithm
|
||||
.<V, E> edgeAwareBuilder()
|
||||
.edgeComparator(edgeTypeComparator)
|
||||
.favoredEdgePredicate(favoredEdgePredicate)
|
||||
.layering(Layering.TOP_DOWN)
|
||||
.threaded(false);
|
||||
case VERT_MIN_CROSS_LONGEST_PATH:
|
||||
return EiglspergerLayoutAlgorithm
|
||||
.<V, E> edgeAwareBuilder()
|
||||
.edgeComparator(edgeTypeComparator)
|
||||
.favoredEdgePredicate(favoredEdgePredicate)
|
||||
.layering(Layering.LONGEST_PATH)
|
||||
.threaded(false);
|
||||
case VERT_MIN_CROSS_NETWORK_SIMPLEX:
|
||||
return EiglspergerLayoutAlgorithm
|
||||
.<V, E> edgeAwareBuilder()
|
||||
.edgeComparator(edgeTypeComparator)
|
||||
.favoredEdgePredicate(favoredEdgePredicate)
|
||||
.layering(Layering.NETWORK_SIMPLEX)
|
||||
.threaded(false);
|
||||
case VERT_MIN_CROSS_COFFMAN_GRAHAM:
|
||||
return EiglspergerLayoutAlgorithm
|
||||
.<V, E> 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<FGVertex, Rectangle> {
|
||||
|
||||
private VisualGraphVertexShapeTransformer<FGVertex> 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<FGEdge, List<Point>> DUMMY_ARTICULATOR = e -> Collections.emptyList();
|
||||
|
||||
JgtNamedLayout(FunctionGraph graph, String layoutName) {
|
||||
super(graph, layoutName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AbstractVisualGraphLayout<FGVertex, FGEdge> createClonedFGLayout(
|
||||
FunctionGraph newGraph) {
|
||||
return new JgtNamedLayout(newGraph, layoutName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Point2D getVertexLocation(FGVertex v, Column col, Row<FGVertex> row,
|
||||
java.awt.Rectangle bounds) {
|
||||
return getCenteredVertexLocation(v, col, row, bounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GridLocationMap<FGVertex, FGEdge> performInitialGridLayout(
|
||||
VisualGraph<FGVertex, FGEdge> visualGraph) throws CancelledException {
|
||||
|
||||
FGEdgeComparator edgeComparator = new FGEdgeComparator();
|
||||
Predicate<FGEdge> favoredEdgePredicate = getFavoredEdgePredicate();
|
||||
Predicate<FGVertex> rootPredicate = null;
|
||||
|
||||
JgtLayoutFactory<FGVertex, FGEdge> layoutProvider =
|
||||
new JgtLayoutFactory<>(edgeComparator, favoredEdgePredicate, rootPredicate);
|
||||
|
||||
LayoutAlgorithm<FGVertex> layout = layoutProvider.getLayout(layoutName);
|
||||
|
||||
FGTempGraph jGraph = buildGraph(visualGraph);
|
||||
|
||||
VisualGraphLayout<FGVertex, FGEdge> vgLayout = visualGraph.getLayout();
|
||||
Dimension layoutSize = vgLayout.getSize();
|
||||
|
||||
LayoutModel<FGVertex> layoutModel =
|
||||
LayoutModel.<FGVertex> builder()
|
||||
.graph(jGraph)
|
||||
.size(layoutSize.width, layoutSize.height)
|
||||
.build();
|
||||
|
||||
layoutModel.accept(layout);
|
||||
|
||||
GridLocationMap<FGVertex, FGEdge> grid = convertToGrid(jGraph, layoutModel, layout);
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
private GridLocationMap<FGVertex, FGEdge> convertToGrid(FGTempGraph jGraph,
|
||||
LayoutModel<FGVertex> layoutModel,
|
||||
LayoutAlgorithm<FGVertex> layoutAlgorithm)
|
||||
throws CancelledException {
|
||||
|
||||
GridLocationMap<FGVertex, FGEdge> grid = new GridLocationMap<>();
|
||||
|
||||
Map<Double, Integer> columns = new TreeMap<>();
|
||||
Map<Double, Integer> rows = new TreeMap<>();
|
||||
|
||||
Set<FGVertex> 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<FGEdge, List<Point>> articulator = getArticulator(layoutAlgorithm);
|
||||
Set<FGEdge> edges = jGraph.edgeSet();
|
||||
for (FGEdge fgEdge : edges) {
|
||||
monitor.checkCanceled();
|
||||
|
||||
List<Point> 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<java.awt.Point> newPoints = new ArrayList<>();
|
||||
|
||||
List<Point> 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<FGEdge, List<Point>> getArticulator(
|
||||
LayoutAlgorithm<FGVertex> layout) {
|
||||
|
||||
if (layout instanceof EdgeArticulationFunctionSupplier) {
|
||||
@SuppressWarnings("unchecked")
|
||||
EdgeArticulationFunctionSupplier<FGEdge> supplier =
|
||||
(EdgeArticulationFunctionSupplier<FGEdge>) layout;
|
||||
return supplier.getEdgeArticulationFunction();
|
||||
}
|
||||
|
||||
return DUMMY_ARTICULATOR;
|
||||
}
|
||||
|
||||
private FGTempGraph buildGraph(VisualGraph<FGVertex, FGEdge> visualGraph) {
|
||||
|
||||
FGTempGraph tempGraph = new FGTempGraph();
|
||||
|
||||
Collection<FGVertex> vertices = visualGraph.getVertices();
|
||||
for (FGVertex v : vertices) {
|
||||
tempGraph.addVertex(v);
|
||||
}
|
||||
|
||||
Collection<FGEdge> edges = visualGraph.getEdges();
|
||||
for (FGEdge e : edges) {
|
||||
tempGraph.addEdge(e.getStart(), e.getEnd(), e);
|
||||
}
|
||||
|
||||
return tempGraph;
|
||||
}
|
||||
|
||||
private Predicate<FGEdge> getFavoredEdgePredicate() {
|
||||
return e -> e.getFlowType().equals(RefType.FALL_THROUGH);
|
||||
}
|
||||
|
||||
private class FGTempGraph extends AbstractBaseGraph<FGVertex, FGEdge> {
|
||||
|
||||
protected FGTempGraph() {
|
||||
super(null, null, DefaultGraphType.directedPseudograph());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class FGEdgeComparator implements Comparator<FGEdge> {
|
||||
|
||||
// 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<String, Integer> 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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<? extends LayoutProvider> previousLayoutClass = previousLayout.getClass();
|
||||
@SuppressWarnings("rawtypes")
|
||||
Class<? extends LayoutProvider> newLayoutClass = newLayout.getClass();
|
||||
|
||||
if (previousLayoutClass == newLayoutClass) {
|
||||
String previousLayoutName = previousLayout.getLayoutName();
|
||||
String newLayoutName = newLayout.getLayoutName();
|
||||
if (previousLayoutName.equals(newLayoutName)) {
|
||||
view.relayout();
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue