diff --git a/codecut-gui/src/main/help/help/topics/skeleton/help.html b/codecut-gui/src/main/help/help/topics/skeleton/help.html
new file mode 100644
index 0000000..1f9d6a1
--- /dev/null
+++ b/codecut-gui/src/main/help/help/topics/skeleton/help.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+ Skeleton Help File for a Module
+
+
+
+
+ Skeleton Help File for a Module
+
+ This is a simple skeleton help topic. For a better description of what should and should not
+ go in here, see the "sample" Ghidra extension in the Extensions/Ghidra directory, or see your
+ favorite help topic. In general, language modules do not have their own help topics.
+
+
diff --git a/codecut-gui/src/main/java/codecutguiv2/ChecklistProvider.java b/codecut-gui/src/main/java/codecutguiv2/ChecklistProvider.java
new file mode 100644
index 0000000..31aed1c
--- /dev/null
+++ b/codecut-gui/src/main/java/codecutguiv2/ChecklistProvider.java
@@ -0,0 +1,215 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+
+package codecutguiv2;
+
+import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.swing.AbstractButton;
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.DefaultTableModel;
+import javax.swing.table.TableModel;
+
+import docking.ActionContext;
+import docking.WindowPosition;
+import ghidra.app.context.ProgramActionContext;
+import ghidra.app.services.BlockModelService;
+import ghidra.feature.vt.api.db.TableColumn;
+import ghidra.framework.plugintool.ComponentProviderAdapter;
+import ghidra.framework.plugintool.PluginTool;
+import ghidra.program.model.listing.Program;
+import ghidra.program.model.symbol.Namespace;
+import ghidra.util.HelpLocation;
+import ghidra.util.table.GhidraTable;
+import ghidra.util.table.GhidraTableCellRenderer;
+import ghidra.util.table.GhidraThreadedTablePanel;
+import graphcut.GraphCutProvider;
+import resources.ResourceManager;
+
+public class ChecklistProvider extends ComponentProviderAdapter implements ActionListener {
+
+ private static final ImageIcon ICON = ResourceManager.loadImage("images/textfield.png");
+ private static final String BUTTON_STRING = "Apply Changes";
+ private static final String RESET_STRING = "Reset Graph Filter";
+
+ private CodeCutGUIPlugin plugin;
+ private GraphCutProvider graphProvider;
+ GhidraTable table;
+ DefaultTableModel model;
+
+ private JPanel boxPanel;
+
+ ChecklistProvider(CodeCutGUIPlugin plugin){
+ super(plugin.getTool(), "Namespaces in Graph", plugin.getName(), ProgramActionContext.class);
+ this.plugin = plugin;
+ this.graphProvider = plugin.getGraphProvider();
+
+ setIcon(ICON);
+ addToToolbar();
+ setHelpLocation(new HelpLocation(plugin.getName(), "CodeCut_Table"));
+ setWindowGroup("codecutTable");
+ setIntraGroupPosition(WindowPosition.BOTTOM);
+
+ boxPanel = new JPanel();
+ boxPanel.setLayout(new BoxLayout(boxPanel, BoxLayout.Y_AXIS));
+
+ Object[] columnNames = {"Namespace", "Added to Graph"};
+ Object[][] data = {};
+ model = new DefaultTableModel(data, columnNames) {
+
+ @Override
+ public boolean isCellEditable(int row, int column) {
+ if(column == 1) {
+ return true;
+ }
+ return false;
+ }
+ };
+ table = new GhidraTable(model) {
+ @Override
+ public Class getColumnClass(int column) {
+ switch(column) {
+ case 0:
+ return Namespace.class;
+ case 1:
+ return Boolean.class;
+ default:
+ return String.class;
+ }
+ }
+ };
+ table.getTableHeader().setReorderingAllowed(false);
+ table.setPreferredScrollableViewportSize(table.getPreferredSize());
+ table.setDefaultRenderer(Namespace.class, new DefaultTableCellRenderer() {
+ @Override
+ public void setValue(Object value) {
+ setText(((Namespace) value).getName());
+ }
+ });
+ JScrollPane scrollPane = new JScrollPane(table);
+ boxPanel.add(scrollPane);
+ boxPanel.setSize(boxPanel.getPreferredSize());
+
+
+ JPanel buttonPane = new JPanel();
+
+ JButton applyButton = new JButton(BUTTON_STRING);
+ applyButton.setVerticalTextPosition(AbstractButton.CENTER);
+ applyButton.setActionCommand(BUTTON_STRING);
+ applyButton.addActionListener(this);
+
+ JButton resetButton = new JButton(RESET_STRING);
+ resetButton.setVerticalTextPosition(AbstractButton.CENTER);
+ resetButton.setActionCommand(RESET_STRING);
+ resetButton.addActionListener(this);
+
+ buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
+ buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
+ buttonPane.add(Box.createHorizontalGlue());
+ buttonPane.add(applyButton);
+ buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
+ buttonPane.add(resetButton);
+ boxPanel.add(buttonPane);
+
+ setIntraGroupPosition(WindowPosition.RIGHT);
+ buildTable();
+ }
+
+ void buildTable() {
+ model.setRowCount(0);
+ SetinGraph = graphProvider.getNamespacesInFilter();
+ for(Namespace ns: inGraph) {
+ model.addRow(new Object[]{ns, true});
+ }
+ }
+
+ void dispose() {
+ plugin = null;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if(BUTTON_STRING.equals(e.getActionCommand())) {
+ Vector data = model.getDataVector();
+ for(int i = 0; i < model.getRowCount(); i++) {
+ Namespace victim = (Namespace)data.elementAt(i).elementAt(0);
+ if(!(boolean)data.elementAt(i).elementAt(1)) {
+ graphProvider.removeFromWhitelist(victim);
+ }
+ }
+ }
+ else if(RESET_STRING.equals(e.getActionCommand())) {
+ graphProvider.resetWhitelist();
+ }
+
+ buildTable();
+ }
+
+ @Override
+ public JComponent getComponent() {
+ return boxPanel;
+ }
+
+ @Override
+ public ActionContext getActionContext(MouseEvent event) {
+ Program program = plugin.getProgram();
+ if(program == null) {
+ return null;
+ }
+ return new ProgramActionContext(this, program);
+ }
+
+ public void open() {
+ if (!isVisible()) {
+ setVisible(true);
+ }
+ }
+
+}
+
diff --git a/codecut-gui/src/main/java/codecutguiv2/CodeCutGUIPlugin.java b/codecut-gui/src/main/java/codecutguiv2/CodeCutGUIPlugin.java
index ddfce88..eac7b98 100644
--- a/codecut-gui/src/main/java/codecutguiv2/CodeCutGUIPlugin.java
+++ b/codecut-gui/src/main/java/codecutguiv2/CodeCutGUIPlugin.java
@@ -27,6 +27,7 @@
* under Contract Number N66001-20-C-4024.
*/
+
package codecutguiv2;
import java.awt.Cursor;
@@ -56,6 +57,7 @@ import ghidra.app.decompiler.component.DecompilerController;
import ghidra.app.events.ProgramActivatedPluginEvent;
import ghidra.app.events.ProgramLocationPluginEvent;
import ghidra.app.plugin.PluginCategoryNames;
+import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.plugin.core.decompile.DecompilerProvider;
import ghidra.app.plugin.core.symboltree.actions.*;
import ghidra.app.script.GhidraScript;
@@ -65,11 +67,14 @@ import ghidra.app.services.GoToService;
//import ghidra.app.services.DecExtendService;
import ghidra.app.util.SymbolInspector;
import ghidra.framework.model.*;
+import ghidra.framework.options.Options;
+import ghidra.framework.options.OptionsChangeListener;
import ghidra.framework.options.SaveState;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.framework.preferences.Preferences;
+import ghidra.graph.viewer.options.VisualGraphOptions;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.address.AddressRangeImpl;
@@ -88,9 +93,12 @@ import ghidra.util.HTMLUtilities;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.Swing;
+import ghidra.util.SystemUtilities;
+import ghidra.util.bean.opteditor.OptionsVetoException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.table.GhidraTable;
import ghidra.util.task.SwingUpdateManager;
+import graphcut.*;
import ghidra.util.task.*;
import ghidra.util.datastruct.*;
import resources.Icons;
@@ -112,13 +120,14 @@ import static ghidra.program.util.ProgramEvent.*;
" provides navigation to the symbols in the Code Browser, and " +
"allows symbols to be renamed and deleted. This plugin also " +
"shows references to a symbol. Filters can be set " +
- "to show subsets of the symbols.",
+ "to show subsets of the symbols." +
+ " Allows the graphing of namespaces and their relations.",
servicesRequired = { GoToService.class, BlockModelService.class },
eventsProduced = { ProgramLocationPluginEvent.class },
eventsConsumed = { ProgramActivatedPluginEvent.class }
)
//@formatter:on
-public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener {
+public class CodeCutGUIPlugin extends ProgramPlugin implements DomainObjectListener, OptionsChangeListener{
final static Cursor WAIT_CURSOR = new Cursor(Cursor.WAIT_CURSOR);
final static Cursor NORM_CURSOR = new Cursor(Cursor.DEFAULT_CURSOR);
@@ -154,6 +163,20 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener {
private Map suggestedModuleNames;
//private DecExtendService decExtService;
+ //GraphCut Variables
+ public static final String GRAPH_NAME = "CodeCut Object Graph";
+ static final String SHOW_PROVIDER_ACTION_NAME = "Display CodeCut Object Graph";
+ public static final HelpLocation DEFAULT_HELP =
+ new HelpLocation(CodeCutGUIPlugin.class.getSimpleName(),
+ CodeCutGUIPlugin.class.getSimpleName());
+ private GraphCutProvider graphProvider;
+ private VisualGraphOptions vgOptions = new VisualGraphOptions();
+ private static final int MIN_UPDATE_DELAY = 750;
+ private SwingUpdateManager locationUpdater = new SwingUpdateManager(MIN_UPDATE_DELAY, () ->{
+ doLocationChanged();
+ });
+ private ChecklistProvider checklistProvider;
+
public CodeCutGUIPlugin(PluginTool tool) {
super(tool);
@@ -180,21 +203,29 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener {
createProvider = new CreateProvider(this);
combineProvider = new CombineProvider(this);
decompProvider = new DecompileRangeProvider(this);
-
+ graphProvider = new GraphCutProvider(tool, this);
+ checklistProvider = new ChecklistProvider(this);
+
createNamespaceActions();
createSymActions();
createRefActions();
createMapActions();
createExportActions();
-
+ createGraphActions();
+ initializeGraphOptions();
+ createChecklistActions();
+
//decExtService = tool.getService(DecExtendService.class);
//if (decExtService == null) {
// Msg.info(new Object(), "ERROR: Decompiler Extension is not installed");
//}
-
+
inspector = new SymbolInspector(getTool(), symProvider.getComponent());
}
+
+
+
/**
* Tells a plugin that it is no longer needed.
* The plugin should remove itself from anything that
@@ -250,20 +281,27 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener {
suggestedModuleNames.clear();
suggestedModuleNames = null;
}
+
+ graphProvider.dispose();
+ checklistProvider.dispose();
+
}
@Override
public void readConfigState(SaveState saveState) {
symProvider.readConfigState(saveState);
+ graphProvider.readConfigState(saveState);
}
@Override
public void writeConfigState(SaveState saveState) {
symProvider.writeConfigState(saveState);
+ graphProvider.writeConfigState(saveState);
}
@Override
public void processEvent(PluginEvent event) {
+ super.processEvent(event);
if (event instanceof ProgramActivatedPluginEvent) {
ProgramActivatedPluginEvent progEvent = (ProgramActivatedPluginEvent) event;
Program oldProg = currentProgram;
@@ -439,6 +477,10 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener {
SymbolProvider getSymbolProvider() {
return symProvider;
}
+
+ GraphCutProvider getGraphProvider() {
+ return graphProvider;
+ }
ReferenceProvider getReferenceProvider() {
return refProvider;
@@ -1415,4 +1457,131 @@ public class CodeCutGUIPlugin extends Plugin implements DomainObjectListener {
}
}
+
+ private void createGraphActions() {
+ DockingAction showProviderAction = new DockingAction(SHOW_PROVIDER_ACTION_NAME, getName(), true) {
+ @Override
+ public void actionPerformed(ActionContext context) {
+ graphProvider.setVisible(true);
+ }
+ };
+ tool.addAction(showProviderAction);
+
+ DockingAction addToGraphAction = new DockingAction("Add Namespace to Graph", getName(), KeyBindingType.SHARED) {
+ @Override
+ public void actionPerformed(ActionContext context) {
+ graphProvider.addToWhitelist(symProvider.getCurrentSymbol().getParentNamespace());
+ checklistProvider.buildTable();
+ }
+
+ @Override
+ public boolean isEnabledForContext(ActionContext context) {
+ return symProvider.getCurrentSymbol() != null;
+ }
+ };
+ addToGraphAction.setPopupMenuData(
+ new MenuData(new String[] { "Add Namespace to Graph" }, "0"));
+ addToGraphAction.setDescription("Add this Namespace to the CodeCut Graph");
+ tool.addLocalAction(symProvider, addToGraphAction);
+
+ DockingAction displayCodeCutGraphAction =
+ new DockingAction("Display_CodeCut_Graph", this.getName()) {
+ @Override
+ public boolean isEnabledForContext(ActionContext context) {
+ return symProvider.getCurrentSymbol() != null;
+ }
+
+ @Override
+ public void actionPerformed(ActionContext context) {
+ graphProvider.setVisible(true);
+ }
+ };
+ MenuData graphModule = new MenuData(new String[] {ToolConstants.MENU_GRAPH, "Display CodeCut Object Graph"}, null, "Display CodeCut Object Graph");
+ graphModule.setMenuSubGroup("1");
+ displayCodeCutGraphAction.setMenuBarData(graphModule);
+ displayCodeCutGraphAction.setHelpLocation(null);
+ displayCodeCutGraphAction.setAddToAllWindows(true);
+ tool.addAction(displayCodeCutGraphAction);
+
+ }
+
+ void showProvider() {
+ graphProvider.setVisible(true);
+ }
+
+ public Address getCurrentAddress() {
+ if (currentLocation == null) {
+ return null;
+ }
+ return currentLocation.getAddress();
+ }
+
+ public VisualGraphOptions getOptions() {
+ return vgOptions;
+ }
+
+ public ProgramLocation getCurrentLocation() {
+ return currentLocation;
+ }
+
+ private void initializeGraphOptions() {
+ ToolOptions options = tool.getOptions(ToolConstants.GRAPH_OPTIONS);
+ options.addOptionsChangeListener(this);
+
+ HelpLocation help = new HelpLocation(getName(), "Options");
+
+ Options graphOptions = options.getOptions(GRAPH_NAME);
+ vgOptions.registerOptions(graphOptions, help);
+ vgOptions.loadOptions(graphOptions);
+ graphProvider.optionsChanged();
+ }
+
+ @Override
+ public void optionsChanged(ToolOptions options, String optionName, Object oldValue, Object newValue) throws OptionsVetoException {
+
+ Options graphOptions = options.getOptions(GRAPH_NAME);
+ vgOptions.loadOptions(graphOptions);
+ graphProvider.optionsChanged();
+ }
+
+ @Override
+ public void locationChanged(ProgramLocation loc) {
+ locationUpdater.update();
+ }
+
+ private void doLocationChanged() {
+ graphProvider.locationChanged(getCurrentLocation());
+ }
+
+ public void handleProviderLocationChanged(ProgramLocation location) {
+ GoToService goTo = getGoToService();
+ if (goTo == null) {
+ return;
+ }
+
+ SystemUtilities.runSwingLater(() -> {
+ goTo.goTo(location);
+ });
+ }
+
+ public void createChecklistActions() {
+ String popupGroup = "0";
+
+ DockingAction showChecklistAction = new DockingAction("Show Namespaces in Graph Filter", getName(), KeyBindingType.SHARED) {
+ @Override
+ public void actionPerformed(ActionContext context) {
+ checklistProvider.open();
+ checklistProvider.buildTable();
+ }
+
+ @Override
+ public boolean isEnabledForContext(ActionContext context) {
+ return symProvider.getCurrentSymbol() != null;
+ }
+ };
+ showChecklistAction.setPopupMenuData(
+ new MenuData(new String[] { "Show Namespaces in Graph Filter" }, popupGroup));
+ showChecklistAction.setDescription("Show Namespaces that have been added to the CodeCut Graph");
+ tool.addLocalAction(symProvider, showChecklistAction);
+ }
}
diff --git a/codecut-gui/src/main/java/graphcut/EmptyGraphCutData.java b/codecut-gui/src/main/java/graphcut/EmptyGraphCutData.java
new file mode 100644
index 0000000..3b39839
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/EmptyGraphCutData.java
@@ -0,0 +1,88 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+/*
+ * Borrows from Ghidra file /Features Graph FunctionCalls/src/main/java/functioncalls/plugin/EmptyFcgData.java
+ */
+
+package graphcut;
+
+import ghidra.graph.viewer.GraphPerspectiveInfo;
+import ghidra.program.model.symbol.Namespace;
+
+/**
+ * empty data used to avoid null checks
+ */
+public class EmptyGraphCutData implements GraphCutData {
+
+ @Override
+ public Namespace getNamespace() {
+ throw new UnsupportedOperationException("Empty data has no namespace");
+ }
+
+ @Override
+ public boolean isNamespace(Namespace ns) {
+ return false;
+ }
+
+ @Override
+ public GraphCutGraph getGraph() {
+ throw new UnsupportedOperationException("Empty data has no graph");
+ }
+
+ @Override
+ public NamespaceEdgeCache getNamespaceEdgeCache() {
+ throw new UnsupportedOperationException("Empty data has no namespace edge cache");
+ }
+
+ @Override
+ public boolean hasResults() {
+ return false;
+ }
+
+ @Override
+ public void dispose() {
+ //nothing
+ }
+
+ @Override
+ public boolean isInitialized() {
+ return false;
+ }
+
+ @Override
+ public GraphPerspectiveInfo getGraphPerspective(){
+ throw new UnsupportedOperationException("Empty data does not need view information");
+ }
+
+ @Override
+ public void setGraphPerspective(GraphPerspectiveInfo info) {
+ throw new UnsupportedOperationException("Empty data does not need view information");
+ }
+}
diff --git a/codecut-gui/src/main/java/graphcut/GraphCutComponent.java b/codecut-gui/src/main/java/graphcut/GraphCutComponent.java
new file mode 100644
index 0000000..08f11a3
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/GraphCutComponent.java
@@ -0,0 +1,126 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+/*
+ * Heavily Borrows from /Features Graph FunctionCalls/src/main/java/functioncalls/graph/view/FcgComponent.java
+ */
+
+package graphcut;
+
+import edu.uci.ics.jung.visualization.renderers.Renderer;
+import functioncalls.graph.FcgEdge;
+import functioncalls.graph.FcgVertex;
+import generic.theme.GColor;
+import ghidra.graph.viewer.GraphComponent;
+import ghidra.graph.viewer.GraphViewer;
+import ghidra.graph.viewer.SatelliteGraphViewer;
+import ghidra.graph.viewer.VisualGraphViewUpdater;
+import ghidra.graph.viewer.edge.VisualEdgeRenderer;
+import ghidra.graph.viewer.layout.VisualGraphLayout;
+import ghidra.graph.viewer.renderer.VisualVertexSatelliteRenderer;
+import ghidra.graph.viewer.vertex.VisualVertexRenderer;
+
+/**
+ * A graph component for GraphCut
+ */
+public class GraphCutComponent extends GraphComponent {
+
+ private GraphCutGraph gcGraph;
+
+ private GraphCutVertexPaintTransformer vertexPaintTransformer =
+ new GraphCutVertexPaintTransformer(GraphCutVertex.DEFAULT_VERTEX_SHAPE_COLOR);
+
+ private GraphCutEdgePaintTransformer edgePaintTransformer =
+ new GraphCutEdgePaintTransformer(new GColor("color.bg.plugin.fcg.edge.primary.direct"),
+ new GColor("color.bg.plugin.fcg.edge.primary.indirect"));
+
+ private GraphCutEdgePaintTransformer selectedEdgePaintTransformer =
+ new GraphCutEdgePaintTransformer(new GColor("color.bg.plugin.fcg.edge.primary.direct.selected"),
+ new GColor("color.bg.plugin.fcg.edge.primary.indirect.selected"));
+
+ private GraphCutEdgePaintTransformer satelliteEdgePaintTransformer =
+ new GraphCutEdgePaintTransformer(new GColor("color.bg.plugin.fcg.edge.satellite.direct"),
+ new GColor("color.bg.plugin.fcg.edge.satellite.indirect"));
+
+ GraphCutComponent(GraphCutGraph g){
+ setGraph(g);
+ build();
+ }
+
+ @Override
+ protected GraphCutVertex getInitialVertex() {
+ return gcGraph.getSource();
+ }
+
+ @Override
+ protected void decoratePrimaryViewer(GraphViewer viewer,
+ VisualGraphLayout layout) {
+
+ super.decoratePrimaryViewer(viewer, layout);
+
+ Renderer renderer = viewer.getRenderer();
+ VisualVertexRenderer vertexRenderer =
+ (VisualVertexRenderer) renderer.getVertexRenderer();
+ vertexRenderer.setVertexFillPaintTransformer(vertexPaintTransformer);
+
+ VisualEdgeRenderer edgeRenderer =
+ (VisualEdgeRenderer) renderer.getEdgeRenderer();
+ edgeRenderer.setDrawColorTransformer(edgePaintTransformer);
+ edgeRenderer.setSelectedColorTransformer(selectedEdgePaintTransformer);
+ }
+
+ @Override
+ protected void decorateSatelliteViewer(SatelliteGraphViewer viewer,
+ VisualGraphLayout layout) {
+
+ super.decorateSatelliteViewer(viewer, layout);
+
+ Renderer renderer = viewer.getRenderer();
+ VisualVertexSatelliteRenderer vertexRenderer =
+ (VisualVertexSatelliteRenderer) renderer.getVertexRenderer();
+ vertexRenderer.setVertexFillPaintTransformer(vertexPaintTransformer);
+
+ VisualEdgeRenderer edgeRenderer =
+ (VisualEdgeRenderer) renderer.getEdgeRenderer();
+ edgeRenderer.setDrawColorTransformer(satelliteEdgePaintTransformer);
+ }
+
+ @Override
+ public void dispose() {
+ gcGraph = null;
+ super.dispose();
+ }
+
+ @Override
+ public VisualGraphViewUpdater getViewUpdater(){
+ return super.getViewUpdater();
+ }
+
+
+}
diff --git a/codecut-gui/src/main/java/graphcut/GraphCutData.java b/codecut-gui/src/main/java/graphcut/GraphCutData.java
new file mode 100644
index 0000000..54456f6
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/GraphCutData.java
@@ -0,0 +1,97 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+/*
+ * Borrows from Ghidra file /Features Graph FunctionCalls/src/main/java/functioncalls/plugin/FcgData.java
+ */
+
+
+package graphcut;
+
+import ghidra.graph.viewer.GraphPerspectiveInfo;
+import ghidra.program.model.symbol.Namespace;
+
+/**
+ * Allows us to retrieve and work on the graph. Makes caching data simple
+ */
+public interface GraphCutData {
+
+ /**
+ * The namespace of this data
+ * @return the namespace
+ */
+ Namespace getNamespace();
+
+ /**
+ * The graph of this data
+ * @return the graph
+ */
+ GraphCutGraph getGraph();
+
+ /**
+ * Returns the cache of edges. Not in the graph but used to track existing edges that are not yet in the graph.
+ * @return
+ */
+ NamespaceEdgeCache getNamespaceEdgeCache();
+
+ /**
+ * True if this data has a valid namespace
+ * @return true if this data has a valid namespace
+ */
+ boolean hasResults();
+
+ /**
+ * False if the graph in this data has not yet been loaded
+ */
+ boolean isInitialized();
+
+ /**
+ * Dispose the contents of this data
+ */
+ void dispose();
+
+ /**
+ * Returns the view's graph perspective. this is used by the view to restore itself.
+ * @return the view's graph perspective
+ */
+ GraphPerspectiveInfo getGraphPerspective();
+
+ /**
+ * Set the view information for this graph data
+ * @param info the perspective to set
+ */
+ void setGraphPerspective(GraphPerspectiveInfo info);
+
+ /**
+ * Returns true if this data's namespace is equal to the given one
+ * @param ns the namespace to test
+ * @return true if this data's namespace is equal to the given one
+ */
+ boolean isNamespace(Namespace ns);
+}
diff --git a/codecut-gui/src/main/java/graphcut/GraphCutDataFactory.java b/codecut-gui/src/main/java/graphcut/GraphCutDataFactory.java
new file mode 100644
index 0000000..e9a5c01
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/GraphCutDataFactory.java
@@ -0,0 +1,79 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+/*
+ * Borrows from /Features Graph FunctionCalls/src/main/java/functioncalls/plugin/FcgDataFactory.java
+ */
+
+
+package graphcut;
+
+import com.google.common.cache.*;
+
+import ghidra.program.model.symbol.Namespace;
+
+/**
+ * A factory that will create GraphCutGraph data objects for a given namespace
+ */
+public class GraphCutDataFactory {
+
+ private LoadingCache cache;
+
+ GraphCutDataFactory(RemovalListener listener){
+
+ cache = CacheBuilder
+ .newBuilder()
+ .maximumSize(5)
+ .removalListener(listener)
+ .build(new CacheLoader () {
+ @Override
+ public GraphCutData load(Namespace ns) throws Exception {
+ return new ValidGraphCutData(ns, new GraphCutGraph());
+ }
+ });
+ }
+
+ GraphCutData create(Namespace ns) {
+ if (ns == null) {
+ return new EmptyGraphCutData();
+ }
+
+ GraphCutData data = cache.getUnchecked(ns);
+ return data;
+ }
+
+ void remove(Namespace ns) {
+ cache.invalidate(ns);
+ }
+
+ void dispose() {
+ cache.invalidateAll();
+ }
+
+}
diff --git a/codecut-gui/src/main/java/graphcut/GraphCutEdge.java b/codecut-gui/src/main/java/graphcut/GraphCutEdge.java
new file mode 100644
index 0000000..77fb474
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/GraphCutEdge.java
@@ -0,0 +1,70 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+package graphcut;
+
+import ghidra.graph.viewer.edge.AbstractVisualEdge;
+import graphcut.GraphCutVertex;
+/**
+ * A GraphCut Edge
+ */
+
+public class GraphCutEdge extends AbstractVisualEdge {
+
+ public GraphCutEdge(GraphCutVertex start, GraphCutVertex end) {
+ super(start, end);
+ }
+
+ @SuppressWarnings("unchecked")
+
+ @Override
+ public GraphCutEdge cloneEdge(GraphCutVertex start, GraphCutVertex end) {
+ return new GraphCutEdge(start, end);
+ }
+
+ /**
+ * Returns true if an edge is direct from a lower level.
+ * @return true if this edge is a direct edge
+ */
+ public boolean isDirectEdge() {
+ GraphCutLevel startLevel = getStart().getLevel();
+ GraphCutLevel endLevel = getEnd().getLevel();
+ if(startLevel.isSource() || endLevel.isSource()) {
+ return true;
+ }
+
+ GraphCutLevel parent = startLevel.parent();
+ if (parent.equals(endLevel)) {
+ return true;
+ }
+
+ GraphCutLevel child = startLevel.child();
+ return child.equals(endLevel);
+ }
+}
diff --git a/codecut-gui/src/main/java/graphcut/GraphCutEdgePaintTransformer.java b/codecut-gui/src/main/java/graphcut/GraphCutEdgePaintTransformer.java
new file mode 100644
index 0000000..eb83d2b
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/GraphCutEdgePaintTransformer.java
@@ -0,0 +1,76 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+/*
+ * Heavily borrows from Ghidra file /Features Graph FunctionCalls/src/main/java/functioncalls/graph/renderer/FcgEdgePaintTransformer.java
+ */
+
+
+package graphcut;
+
+import com.google.common.base.Function;
+import java.awt.Color;
+import ghidra.util.ColorUtils;
+
+public class GraphCutEdgePaintTransformer implements Function {
+ private Color directColor;
+ private Color indirectColor;
+
+ private Color[] directColorWithAlpha = new Color[10];
+
+ public GraphCutEdgePaintTransformer(Color directColor, Color indirectColor) {
+ this.directColor = directColor;
+ this.indirectColor = indirectColor;
+
+ directColorWithAlpha = alphatize(directColor);
+ }
+
+ private Color[] alphatize(Color c) {
+ Color[] alphad = new Color[10];
+ alphad[0] = c;
+ for (int i = 1; i < 10; i++) {
+ double newAlpha = 255 - (i * 25.5);
+ alphad[i] = ColorUtils.withAlpha(c, (int) newAlpha);
+ }
+ return alphad;
+ }
+
+ @Override
+ public Color apply(GraphCutEdge e) {
+ if (e.isDirectEdge()) {
+ return getDirectEdgeColor(e);
+ }
+
+ return indirectColor;
+ }
+
+ private Color getDirectEdgeColor(GraphCutEdge e) {
+ return directColor;
+ }
+}
diff --git a/codecut-gui/src/main/java/graphcut/GraphCutEmphasizeEdgesJob.java b/codecut-gui/src/main/java/graphcut/GraphCutEmphasizeEdgesJob.java
new file mode 100644
index 0000000..6dd66c9
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/GraphCutEmphasizeEdgesJob.java
@@ -0,0 +1,67 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+/*
+ * Borrows from Ghidra file /Features Graph FunctionCalls/src/main/java/functioncalls/graph/job/FcgEmphasizeEdgesJob.java
+ */
+
+
+package graphcut;
+
+import java.util.Set;
+
+import ghidra.graph.job.AbstractGraphVisibilityTransitionJob;
+import ghidra.graph.viewer.GraphViewer;
+
+public class GraphCutEmphasizeEdgesJob extends AbstractGraphVisibilityTransitionJob {
+
+ private Set edges;
+
+ public GraphCutEmphasizeEdgesJob(GraphViewer viewer, Set edges) {
+ super(viewer, true);
+ this.edges = edges;
+ }
+
+ @Override
+ protected void updateOpacity(double percentComplete) {
+
+ double remaining = percentComplete;
+ if(percentComplete > 0.5){
+ remaining = 1 - percentComplete;
+ }
+
+ double modified = remaining * 10;
+
+ for (GraphCutEdge e : edges) {
+ e.setEmphasis(modified);
+ }
+
+ }
+
+}
diff --git a/codecut-gui/src/main/java/graphcut/GraphCutExpandingVertexCollection.java b/codecut-gui/src/main/java/graphcut/GraphCutExpandingVertexCollection.java
new file mode 100644
index 0000000..58c0f43
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/GraphCutExpandingVertexCollection.java
@@ -0,0 +1,192 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+/*
+ * Borrows from Ghidra file /Features Graph FunctionCalls/src/main/java/functioncalls/graph/job/FcgExpandingVertexCollection.java
+ */
+
+
+package graphcut;
+
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import functioncalls.graph.FcgDirection;
+
+import org.apache.commons.collections4.IterableUtils;
+import org.apache.commons.collections4.map.LazyMap;
+
+import ghidra.graph.viewer.GraphViewer;
+import util.CollectionUtils;
+
+public class GraphCutExpandingVertexCollection {
+
+ private Comparator vertexComparator =
+ (v1,v2) -> v1.getID() > v2.getID() ? +1 : v1.getID() < v2.getID() ? -1 : 0;
+ private Comparator sourceVertexComparator = this::compareVerticesByLayoutPosition;
+
+ private Map> newVerticesBySource = LazyMap.lazyMap(
+ new TreeMap<>(sourceVertexComparator), () -> new TreeSet<>(vertexComparator));
+
+ private GraphViewer viewer;
+ private GraphCutLevel parentLevel;
+ private GraphCutLevel expandingLevel;
+ private Set newVertices;
+ private Set newEdges;
+ private Set indirectEdges = Collections.emptySet();
+ private boolean isIncoming;
+ private Iterable sources;
+
+ public GraphCutExpandingVertexCollection(Iterable sources, GraphCutLevel parentLevel, GraphCutLevel expandingLevel,
+ Set newVertices, Set newEdges, Set allParentLevelEdges, boolean isIncoming,
+ GraphViewer viewer) {
+
+ this.sources = sources;
+ this.parentLevel = parentLevel;
+ this.newVertices = newVertices;
+ this.newEdges = newEdges;
+ this.isIncoming = isIncoming;
+ this.viewer = viewer;
+ this.expandingLevel = expandingLevel;
+
+ for(GraphCutEdge e: allParentLevelEdges) {
+
+ GraphCutVertex start = e.getStart();
+ GraphCutVertex end = e.getEnd();
+ GraphCutLevel startLevel = start.getLevel();
+ GraphCutLevel endLevel = end.getLevel();
+
+ if (expandingLevel.equals(startLevel)) {
+ if(expandingLevel.equals(endLevel)) {
+ newVerticesBySource.get(start).add(end);
+ newVerticesBySource.get(end).add(start);
+ }
+ else {
+ newVerticesBySource.get(end).add(start);
+ }
+ }
+ else {
+ newVerticesBySource.get(start).add(end);
+ }
+ }
+ }
+
+ private int compareVerticesByLayoutPosition(GraphCutVertex v1, GraphCutVertex v2) {
+
+ Layout layout = viewer.getGraphLayout();
+
+ GraphCutLevel l1 = v1.getLevel();
+ GraphCutLevel l2 = v2.getLevel();
+
+ int result = l1.compareTo(l2);
+ if (result != 0) {
+
+ if(l1.equals(parentLevel)) {
+ return -1;
+ }
+ if(l2.equals(parentLevel)) {
+ return 1;
+ }
+
+ return result;
+ }
+
+ Point2D p1 = layout.apply(v1);
+ Point2D p2 = layout.apply(v2);
+ return (int) (p1.getX() - p2.getX());
+ }
+
+ public List getVerticesByLevel(GraphCutLevel level){
+
+ Set existingVertices = newVerticesBySource.keySet();
+
+ List verticesAtLevel = existingVertices.stream().filter(v -> v.getLevel().equals(level)).collect(Collectors.toList());
+ return verticesAtLevel;
+ }
+
+ public List getAllVerticesAtNewLevel(){
+
+ Set existingVertices = newVerticesBySource.keySet();
+ LinkedHashSet sortedVertices = existingVertices
+ .stream()
+ .map(v -> newVerticesBySource.get(v))
+ .flatMap(set -> set.stream())
+ .filter(v -> v.getLevel().equals(expandingLevel))
+ .collect(Collectors.toCollection(LinkedHashSet::new));
+ return new ArrayList<>(sortedVertices);
+
+ }
+
+ public Set getNewVertices(){
+ return newVertices;
+ }
+
+ public Set getIndirectEdges() {
+ return indirectEdges;
+ }
+
+ public Iterable getNewEdges(){
+ return IterableUtils.chainedIterable(newEdges, indirectEdges);
+ }
+
+ public int getNewEdgeCount() {
+ return newEdges.size() + indirectEdges.size();
+ }
+
+ public void setIndirectEdges(Set indirectEdges) {
+ this.indirectEdges = CollectionUtils.asSet(indirectEdges);
+ }
+
+ public GraphCutLevel getExpandingLevel() {
+ return expandingLevel;
+ }
+
+ public FcgDirection getExpandingDirection() {
+ return expandingLevel.getDirection();
+ }
+
+ public Iterable getSources(){
+ return sources;
+ }
+
+ public boolean isIncoming() {
+ return isIncoming;
+ }
+
+}
diff --git a/codecut-gui/src/main/java/graphcut/GraphCutExpansionListener.java b/codecut-gui/src/main/java/graphcut/GraphCutExpansionListener.java
new file mode 100644
index 0000000..6e7c16d
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/GraphCutExpansionListener.java
@@ -0,0 +1,50 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+
+package graphcut;
+
+
+public interface GraphCutExpansionListener {
+
+ /**
+ * Show or hide those vertices that are on incoming edges to v
+ *
+ * @param v the vertex
+ */
+ public void toggleIncomingVertices(GraphCutVertex v);
+
+ /**
+ * Show or hide those vertices that are on outgoing edges to v
+ *
+ * @param v the vertex
+ */
+ public void toggleOutgoingVertices(GraphCutVertex v);
+
+}
diff --git a/codecut-gui/src/main/java/graphcut/GraphCutGraph.java b/codecut-gui/src/main/java/graphcut/GraphCutGraph.java
new file mode 100644
index 0000000..25f753c
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/GraphCutGraph.java
@@ -0,0 +1,177 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+/*
+ * Heavily Borrows from Ghidra file /Features Graph FunctionCalls/src/main/java/functioncalls/graph/FunctionCallGraph.java
+ */
+
+
+package graphcut;
+
+import java.util.*;
+
+import org.apache.commons.collections4.IterableUtils;
+import org.apache.commons.collections4.map.LazyMap;
+
+import functioncalls.graph.FcgDirection;
+import ghidra.graph.graphs.FilteringVisualGraph;
+import ghidra.graph.viewer.layout.VisualGraphLayout;
+import ghidra.program.model.symbol.Namespace;
+
+
+/*
+ * A graph for the GraphCut Plugin
+ */
+public class GraphCutGraph extends FilteringVisualGraph {
+
+ private VisualGraphLayout layout;
+ private GraphCutVertex source;
+ private Map verticesByNamespace = new HashMap<>();
+ private Comparator vertexComparator =
+ (v1,v2) -> v1.getID() > v2.getID() ? +1 : v1.getID() < v2.getID() ? -1 : 0;
+ private Map> verticesByLevel =
+ LazyMap.lazyMap(new HashMap<>(), () -> new TreeSet<>(vertexComparator));
+
+ /**
+ * Sets the source vertex from which the graph originates
+ * @param the source vertex
+ */
+ public void setSource(GraphCutVertex source) {
+ if (this.source != null) {
+ throw new IllegalStateException("Cannot change graph source once it has been created");
+ }
+
+ this.source = source;
+ addVertex(source);
+ }
+
+ /**
+ * Returns the vertex from which the graph is created
+ * @return source vertex
+ */
+ public GraphCutVertex getSource() {
+ return source;
+ }
+
+ /**
+ * returns whether there is a vertex for the given namespace
+ * @param ns Namespace
+ * @return True if graph contains a vertex for the namespace
+ */
+ public boolean containsNamespace(Namespace ns) {
+ return verticesByNamespace.containsKey(ns);
+ }
+
+ /**
+ * Returns the vertex for the given namespace
+ * @param ns Namespace
+ * @return the vertex for the given namespace
+ */
+ public GraphCutVertex getVertex(Namespace ns) {
+ return verticesByNamespace.get(ns);
+ }
+
+
+ /**
+ * Return all vertices in the given level
+ * @param level the level to retrieve
+ * @return all vertices in the given level
+ */
+ public Iterable getVerticesByLevel(GraphCutLevel level){
+ return IterableUtils.unmodifiableIterable(verticesByLevel.get(level));
+ }
+
+ /**
+ * Returns the level furthest from the source node in the given direction
+ * @param direction the direction to search
+ * @return the furthest level
+ */
+ public GraphCutLevel getLargestLevel(FcgDirection direction) {
+ GraphCutLevel greatest = new GraphCutLevel(1, direction);
+
+ Set keys = verticesByLevel.keySet();
+ for (GraphCutLevel level : keys) {
+ if (level.getDirection() != direction) {
+ continue;
+ }
+
+ if (level.getRow() > greatest.getRow()) {
+ greatest = level;
+ }
+ }
+ return greatest;
+ }
+
+ @Override
+ public VisualGraphLayout getLayout(){
+ return layout;
+ }
+
+ @Override
+ public GraphCutGraph copy() {
+
+ GraphCutGraph newGraph = new GraphCutGraph();
+ for (GraphCutVertex v : vertices.keySet()) {
+ newGraph.addVertex(v);
+ }
+
+ for (GraphCutEdge e : edges.keySet()) {
+ newGraph.addEdge(e);
+ }
+
+ return newGraph;
+ }
+
+ public void setLayout(VisualGraphLayout layout) {
+ this.layout = layout;
+ }
+
+ @Override
+ protected void verticesAdded(Collection added) {
+ for (GraphCutVertex v : added) {
+ Namespace ns = v.getNamespace();
+ verticesByNamespace.put(ns, v);
+ verticesByLevel.get(v.getLevel()).add(v);
+ }
+ super.verticesAdded(added);
+ }
+
+ @Override
+ protected void verticesRemoved(Collection removed) {
+ for (GraphCutVertex v : removed) {
+ Namespace ns = v.getNamespace();
+ verticesByNamespace.remove(ns);
+ verticesByLevel.get(v.getLevel()).remove(v);
+ }
+ }
+
+}
+
+
+
diff --git a/codecut-gui/src/main/java/graphcut/GraphCutLayout.java b/codecut-gui/src/main/java/graphcut/GraphCutLayout.java
new file mode 100644
index 0000000..ba30a74
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/GraphCutLayout.java
@@ -0,0 +1,137 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+/*
+ * Borrows heavily from Ghidra File /Features Graph FunctionCalls/src/main/java/functioncalls/graph/layout/BowTieLayout.java
+ */
+
+
+package graphcut;
+
+import java.awt.Rectangle;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import ghidra.graph.VisualGraph;
+import ghidra.graph.viewer.layout.AbstractVisualGraphLayout;
+import ghidra.graph.viewer.layout.Column;
+import ghidra.graph.viewer.layout.GridLocationMap;
+import ghidra.graph.viewer.layout.LayoutPositions;
+import ghidra.graph.viewer.layout.Row;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.task.TaskMonitor;
+
+public class GraphCutLayout extends AbstractVisualGraphLayout {
+
+ protected GraphCutLayout(GraphCutGraph graph, String name){
+ super(graph, name);
+ }
+
+ public AbstractVisualGraphLayout createClonedLayout(VisualGraph newGraph){
+ if (!(newGraph instanceof GraphCutGraph)) {
+ throw new IllegalArgumentException("Must pass a GraphCut Graph to clone");
+ }
+
+ GraphCutLayout newLayout = new GraphCutLayout((GraphCutGraph) newGraph, getLayoutName());
+ return newLayout;
+ }
+
+ @Override
+ protected Point2D getVertexLocation(GraphCutVertex v, Column col, Row row, Rectangle bounds) {
+ return getCenteredVertexLocation(v, col, row, bounds);
+ }
+
+ @Override
+ public boolean isCondensedLayout() {
+ return false;
+ }
+
+ @Override
+ public GraphCutGraph getVisualGraph() {
+ return (GraphCutGraph) getGraph();
+ }
+
+ @Override
+ protected GridLocationMap performInitialGridLayout(VisualGraph g) throws CancelledException {
+ if (!(g instanceof GraphCutGraph)) {
+ throw new IllegalArgumentException("This layout can only be used with the GraphCutGraph");
+ }
+
+ return layoutGraphCutGraph((GraphCutGraph)g);
+ }
+
+ @Override
+ public LayoutPositions calculateLocations(VisualGraph g, TaskMonitor taskMonitor){
+ LayoutPositions locs = super.calculateLocations(g, taskMonitor);
+ return locs;
+ }
+
+ private GridLocationMap layoutGraphCutGraph(GraphCutGraph g){
+ GridLocationMap grid = new GridLocationMap<>();
+
+ GraphCutVertex source = Objects.requireNonNull(g.getSource());
+ // Incoming nodes on top
+ // Sorted by id
+
+ List inEdges = new ArrayList<>(g.getInEdges(source));
+ List inVertices = inEdges.stream().map(e -> e.getStart()).collect(Collectors.toList());
+ inVertices.sort((v1,v2) -> v1.getID() > v2.getID() ? +1 : v1.getID() < v2.getID() ? -1 : 0);
+
+ //first row -> incoming nodes
+ int row = 0;
+ for(int col = 0; col < inVertices.size(); col++) {
+ GraphCutVertex v = inVertices.get(col);
+ grid.set(v, row, col);
+ }
+
+ //middle row -> source
+ row = 1;
+ grid.set(source, row, 0);
+
+ //bottom row -> outgoing nodes
+ List outEdges = new ArrayList<>(g.getOutEdges(source));
+ List outVertices = outEdges.stream().map(e -> e.getEnd()).collect(Collectors.toList());
+
+ outVertices.removeAll(inVertices); //prevent cycles
+ outVertices.sort((v1,v2) -> v1.getID() > v2.getID() ? +1 : v1.getID() < v2.getID() ? -1 : 0);
+ row = 2;
+ for (int col = 0; col < outVertices.size(); col++) {
+ GraphCutVertex v = outVertices.get(col);
+ grid.set(v, row, col);
+ }
+
+ grid.centerRows();
+ return grid;
+
+ }
+
+}
diff --git a/codecut-gui/src/main/java/graphcut/GraphCutLayoutExpandVerticesJob.java b/codecut-gui/src/main/java/graphcut/GraphCutLayoutExpandVerticesJob.java
new file mode 100644
index 0000000..3ebf3b9
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/GraphCutLayoutExpandVerticesJob.java
@@ -0,0 +1,393 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+/*
+ * Borrows from Ghidra file /Features Graph FunctionCalls/src/main/java/functioncalls/graph/job/BowTieExpandVerticesJob.java
+ */
+
+package graphcut;
+
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.base.Function;
+
+import edu.uci.ics.jung.algorithms.layout.Layout;
+import edu.uci.ics.jung.visualization.RenderContext;
+import ghidra.graph.job.AbstractGraphTransitionJob;
+import ghidra.graph.viewer.GraphViewer;
+import ghidra.graph.viewer.GraphViewerUtils;
+import ghidra.util.Msg;
+
+
+
+// bowTieExpandVerticesJob
+public class GraphCutLayoutExpandVerticesJob extends AbstractGraphTransitionJob {
+
+ private boolean incoming;
+ private GraphCutLevel expandingLevel;
+ private GraphCutExpandingVertexCollection newVertexCollection;
+
+ public GraphCutLayoutExpandVerticesJob(GraphViewer viewer,
+ GraphCutExpandingVertexCollection newVertexCollection, boolean useAnimation) {
+ super(viewer, useAnimation);
+
+ this.newVertexCollection = newVertexCollection;
+ this.incoming = newVertexCollection.isIncoming();
+ this.expandingLevel = newVertexCollection.getExpandingLevel();
+
+ if(!(graphLayout instanceof GraphCutLayout)) {
+ throw new IllegalArgumentException("The current graph layout must be the GraphCut Layout to use this job");
+ }
+
+ Msg.trace(this, "\n Layout Expand Job - new vertices: " + newVertexCollection.getNewVertices());
+ }
+
+ @Override
+ protected boolean isTooBigToAnimate() {
+ return graph.getVertexCount() > 1000;
+ }
+
+ @Override
+ protected void updateOpacity(double percentComplete) {
+ double x = percentComplete;
+ double x2 = x*x;
+ double remaining = 1-percentComplete;
+ double y = x2 - remaining;
+
+ Set newVertices = newVertexCollection.getNewVertices();
+
+ double vertexAlpha = x;
+ double edgeAlpha = Math.max(y, 0);
+ for(GraphCutVertex v : newVertices) {
+ v.setAlpha(vertexAlpha);
+ }
+
+ Iterable newEdges = newVertexCollection.getNewEdges();
+ for(GraphCutEdge e : newEdges) {
+ e.setAlpha(edgeAlpha);
+ }
+
+ }
+
+ @Override
+ public boolean canShortcut() {
+ return true;
+ }
+
+ @Override
+ public void shortcut() {
+ isShortcut = true;
+
+ if(vertexLocations.isEmpty()) {
+ initializeVertexLocations();
+ }
+
+ stop();
+ }
+
+ @Override
+ protected void initializeVertexLocations() {
+ Map destinationLocations = createDestinationLocation();
+ vertexLocations.putAll(destinationLocations);
+ }
+
+ private Map createDestinationLocation(){
+
+ Map finalDestinations = arrangeNewVertices();
+
+ Map transitions = new HashMap<>();
+ GraphCutLevel parentLevel = expandingLevel.parent();
+ Iterable newEdges = newVertexCollection.getNewEdges();
+ Set newVertices = newVertexCollection.getNewVertices();
+ for(GraphCutEdge e : newEdges) {
+ GraphCutVertex newVertex = incoming ? e.getStart() : e.getEnd();
+ if(!finalDestinations.containsKey(newVertex)) {
+ continue;
+ }
+ if(!newVertices.contains(newVertex)) {
+ continue;
+ }
+
+ GraphCutVertex existingVertex = incoming ? e.getEnd() : e.getStart();
+ GraphCutLevel existingLevel = existingVertex.getLevel();
+ if (!existingLevel.equals(parentLevel)) {
+ continue;
+ }
+
+ Point2D start = (Point2D) toLocation(existingVertex).clone();
+ Point2D end = finalDestinations.get(newVertex);
+
+ TransitionPoints trans = new TransitionPoints(start, end);
+ transitions.put(newVertex, trans);
+ }
+ return transitions;
+
+ }
+
+
+ private Map arrangeNewVertices(){
+
+ GraphCutLayout bowTie = (GraphCutLayout) graphLayout;
+ boolean isCondensed = bowTie.isCondensedLayout();
+ int widthPadding = isCondensed ? GraphViewerUtils.EXTRA_LAYOUT_COLUMN_SPACING_CONDENSED :
+ GraphViewerUtils.EXTRA_LAYOUT_COLUMN_SPACING;
+
+ widthPadding *= expandingLevel.getDistance();
+ int heightPadding = calculateHeightPadding(isCondensed);
+
+ GraphCutLevel parentLevel = expandingLevel.parent();
+ List parentLevelVertices = newVertexCollection.getVerticesByLevel(parentLevel);
+ if(parentLevelVertices.isEmpty()) {
+ return Collections.emptyMap();
+ }
+
+ Rectangle existingRowBounds = getBounds(parentLevelVertices);
+ Msg.trace(this, "existing row bounds " + existingRowBounds);
+ double existingY = existingRowBounds.y;
+ double existingCenterX = existingRowBounds.x + (existingRowBounds.width/2);
+
+ List allLevelVertices = newVertexCollection.getAllVerticesAtNewLevel();
+ double newRowWidth = getWidth(allLevelVertices, widthPadding);
+ double newRowHeight = getHeight(allLevelVertices);
+ double newRowX = existingCenterX - (newRowWidth/2);
+
+ double newRowY = 0;
+ if(newVertexCollection.isIncoming()) {
+ newRowY = existingY - newRowHeight - heightPadding;
+ }
+ else {
+ newRowY = existingY +existingRowBounds.height + heightPadding;
+ }
+
+ Msg.trace(this, "new row bounds " + new Rectangle2D.Double(newRowX, newRowY, newRowWidth, newRowHeight));
+
+ Map locations = getExistingLocations(allLevelVertices);
+ if(!locations.isEmpty()) {
+ return locations;
+ }
+
+ RenderContext renderContext = viewer.getRenderContext();
+ Function super GraphCutVertex, Shape> shaper = renderContext.getVertexShapeTransformer();
+
+ double x = newRowX;
+ double y = newRowY;
+
+ int n = allLevelVertices.size();
+
+ //Dynamic Layout
+ Set placed = new HashSet<>();
+ Set newVertices = newVertexCollection.getNewVertices();
+ //Make shallow copy
+ List allLevelVerticesOrig = new ArrayList<>();
+ for(GraphCutVertex v : allLevelVertices) {
+ allLevelVerticesOrig.add(v);
+ }
+ // 1. Place visible vertices
+ for(GraphCutVertex v: allLevelVerticesOrig) {
+ if(v.visible) {
+ GraphCutVertex tmpVertex = allLevelVertices.get(v.layoutIndex);
+ int tmpIndex = allLevelVertices.indexOf(v);
+ allLevelVertices.set(v.layoutIndex, v);
+ allLevelVertices.set(tmpIndex, tmpVertex);
+ placed.add(v);
+ }
+ }
+ // 2. Place newVertices
+ for(GraphCutVertex v: newVertices) {
+ if(placed.contains(v)) {
+ continue;
+ }
+ placed.add(v);
+ int index = findEmptyLayoutIndex(allLevelVertices);
+ v.layoutIndex = index;
+ v.visible = true;
+ GraphCutVertex tmpVertex = allLevelVertices.get(index);
+ int tmpIndex = allLevelVertices.indexOf(v);
+ allLevelVertices.set(tmpIndex, tmpVertex);
+ allLevelVertices.set(v.layoutIndex, v);
+
+ }
+
+ // 3. Place all invisible vertices randomly
+ int curr = 0;
+ for(GraphCutVertex v: allLevelVerticesOrig) {
+ if(placed.contains(v)) {
+ continue;
+ }
+ //find empty spot
+ while(allLevelVertices.get(curr).visible) {
+ curr++;
+ }
+ allLevelVertices.set(curr, v);
+ }
+
+
+
+ for(int i = 0; i < n; i++) {
+ GraphCutVertex v = allLevelVertices.get(i);
+ Rectangle myBounds = shaper.apply(v).getBounds();
+ double myHalf = myBounds.width / 2;
+
+ double nextHalf = 0;
+ boolean isLast = i == n-1;
+ if(!isLast) {
+ GraphCutVertex nextV = allLevelVertices.get(i+1);
+ Rectangle nextBounds = shaper.apply(nextV).getBounds();
+ nextHalf = nextBounds.width/2;
+ }
+
+ Point2D p = new Point2D.Double(x,y);
+ locations.put(v, p);
+
+ double vWidth = myHalf + widthPadding + nextHalf;
+ Msg.trace(this, v + " at x,width: "+x+","+vWidth);
+ x+=vWidth;
+ }
+
+ return locations;
+ }
+
+ private int findEmptyLayoutIndex(List vertices) {
+ //Check middle
+ int mid = vertices.size()/2;
+ if(!vertices.get(mid).visible) {
+ return mid;
+ }
+
+ // start checking both sides
+ int left = mid-1;
+ int right = mid+1;
+ while(left >= 0 || right <=vertices.size()-1) {
+ if(left>=0 && !vertices.get(left).visible) {
+ return left;
+ }
+ left--;
+ if(right <= vertices.size()-1 && !vertices.get(right).visible) {
+ return right;
+ }
+ right++;
+
+ }
+ return -1;
+ }
+
+
+ private int calculateHeightPadding(boolean isCondensed) {
+
+ int basePadding = isCondensed ? GraphViewerUtils.EXTRA_LAYOUT_ROW_SPACING_CONDENSED
+ : GraphViewerUtils.EXTRA_LAYOUT_ROW_SPACING;
+
+ double separationFactor = expandingLevel.getDistance();
+
+ List allLevelVertices = newVertexCollection.getAllVerticesAtNewLevel();
+ int count = allLevelVertices.size();
+
+ double to = 1.25;
+ double power = Math.pow(separationFactor, to);
+ int maxPadding = (int) (basePadding * power);
+
+ int delta = maxPadding - basePadding;
+ double percent = Math.min(count / 20f, 1);
+ int padding = basePadding + (int) (delta * percent);
+ return padding;
+ }
+
+
+ private Map getExistingLocations(List vertices){
+ Map locations = new HashMap<>();
+ for(GraphCutVertex v: vertices) {
+ Point2D p = toLocation(v);
+ if (p.getX() == 0 && p.getY() == 0) {
+ return new HashMap<>();
+ }
+
+ locations.put(v, (Point2D) p.clone());
+ }
+ return locations;
+ }
+
+
+
+ private Rectangle getBounds(List vertices) {
+ RenderContext renderContext = viewer.getRenderContext();
+ Function super GraphCutVertex, Shape> shaper = renderContext.getVertexShapeTransformer();
+
+ Layout layout = viewer.getGraphLayout();
+
+ Rectangle area = null;
+ for (GraphCutVertex v : vertices) {
+ Rectangle bounds = shaper.apply(v).getBounds();
+ Point2D loc = layout.apply(v);
+ int x = (int) loc.getX();
+ int y = (int) loc.getY();
+ bounds.setLocation(x,y);
+ if(area == null) {
+ area = bounds;
+ }
+ area.add(bounds);
+ }
+
+ return area;
+ }
+
+
+ private int getWidth(List vertices, int widthPadding) {
+ RenderContext renderContext = viewer.getRenderContext();
+ Function super GraphCutVertex, Shape> shaper = renderContext.getVertexShapeTransformer();
+
+ int width = 0;
+ for (GraphCutVertex v : vertices) {
+ width += shaper.apply(v).getBounds().width + widthPadding;
+ }
+
+ return width;
+ }
+
+ private int getHeight(List vertices) {
+ RenderContext renderContext = viewer.getRenderContext();
+ Function super GraphCutVertex, Shape> shaper = renderContext.getVertexShapeTransformer();
+
+ int height = 0;
+ for (GraphCutVertex v: vertices) {
+ height = Math.max(height, shaper.apply(v).getBounds().height);
+ }
+ return height;
+ }
+
+}
diff --git a/codecut-gui/src/main/java/graphcut/GraphCutLayoutProvider.java b/codecut-gui/src/main/java/graphcut/GraphCutLayoutProvider.java
new file mode 100644
index 0000000..daf99db
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/GraphCutLayoutProvider.java
@@ -0,0 +1,68 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+/*
+ * Borrows from Ghidra file /Features Graph FunctionCalls/src/main/java/functioncalls/graph/layout/BowTieLayoutProvider.java
+ */
+
+package graphcut;
+
+import javax.swing.Icon;
+
+import generic.theme.GIcon;
+import ghidra.graph.viewer.layout.AbstractLayoutProvider;
+import ghidra.graph.viewer.layout.VisualGraphLayout;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.task.TaskMonitor;
+
+/**
+ * A layout provider for GraphCut
+ */
+public class GraphCutLayoutProvider extends AbstractLayoutProvider {
+ public static final String NAME = "GraphCut Layout";
+ private static final Icon DEFAULT_ICON = new GIcon("icon.plugin.fcg.layout.bow.tie");
+
+ @Override
+ public VisualGraphLayout getLayout(GraphCutGraph graph, TaskMonitor taskMonitor) throws CancelledException{
+
+ GraphCutLayout layout = new GraphCutLayout(graph, NAME);
+ initVertexLocations(graph, layout);
+ return layout;
+ }
+
+ @Override
+ public String getLayoutName() {
+ return NAME;
+ }
+
+ @Override
+ public Icon getActionIcon() {
+ return DEFAULT_ICON;
+ }
+}
diff --git a/codecut-gui/src/main/java/graphcut/GraphCutLevel.java b/codecut-gui/src/main/java/graphcut/GraphCutLevel.java
new file mode 100644
index 0000000..e150187
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/GraphCutLevel.java
@@ -0,0 +1,193 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+/*
+ * Heavily Borrowed from Ghidra file /Features Graph FunctionCalls/src/main/java/functioncalls/graph/FcgEdge.java
+ */
+
+
+package graphcut;
+
+import static functioncalls.graph.FcgDirection.*;
+
+import functioncalls.graph.FcgDirection;
+
+
+// A container class that represents a GraphCut row.
+
+public class GraphCutLevel implements Comparable {
+
+ private int row;
+ private FcgDirection direction;
+
+ public static GraphCutLevel sourceLevel() {
+ return new GraphCutLevel(0, IN_AND_OUT);
+ }
+
+ public GraphCutLevel(int distance, FcgDirection direction) {
+ this.row = toRow(distance);
+ this.direction = direction;
+
+ if (row == 0) {
+ throw new IllegalArgumentException("Graph Cut uses a 1-based row system");
+ }
+
+ if (row == 1 && direction != IN_AND_OUT) {
+ throw new IllegalArgumentException("Row 1 must be FcgDirection.IN_AND_OUT");
+ }
+ }
+
+ private int toRow(int distance) {
+ int oneBased = distance + 1;
+ return (direction == OUT) ? -oneBased : oneBased;
+ }
+
+ public int getRow() {
+ return row;
+ }
+
+ public int getDistance() {
+ return Math.abs(row) - 1;
+ }
+
+ public FcgDirection getDirection() {
+ return direction;
+ }
+
+ /**
+ * Returns true if this level is level 1
+ * @return true if this level represents the source level
+ */
+ public boolean isSource() {
+ return direction.isSource();
+ }
+
+ public GraphCutLevel parent() {
+ if (direction == IN_AND_OUT) {
+ // undefined--we are the parent of all
+ throw new IllegalArgumentException(
+ "To get the parent of the source level you must use the constructor directly");
+ }
+
+ int newDistance = getDistance() - 1;
+ FcgDirection newDirection = direction;
+ if (newDistance == 0) {
+ newDirection = IN_AND_OUT;
+ }
+ return new GraphCutLevel(newDistance, newDirection);
+ }
+
+ public GraphCutLevel child() {
+ if (direction == IN_AND_OUT) {
+ // undefined--this node goes in both directions
+ throw new IllegalArgumentException(
+ "To get the child of the source level you " + "must use the constructor directly");
+ }
+
+ return child(direction);
+ }
+
+ public boolean isParentOf(GraphCutLevel other) {
+ if (isSource()) {
+ return other.getDistance() == 1;
+ }
+
+ if (direction != other.direction) {
+ return false;
+ }
+
+ // e.g., row 2 - row 1 = 1
+ return other.getDistance() - getDistance() == 1;
+ }
+
+ public boolean isChildOf(GraphCutLevel other) {
+ return other.isParentOf(this);
+ }
+
+ public GraphCutLevel child(FcgDirection newDirection) {
+ if (newDirection == IN_AND_OUT) {
+ // undefined--IN_AND_OUT goes in both directions
+ throw new IllegalArgumentException("Direction cannot be IN_AND_OUT");
+ }
+
+ int newDistance = getDistance() + 1;
+ return new GraphCutLevel(newDistance, newDirection);
+ }
+
+ @Override
+ public String toString() {
+ return direction + " - row " + Integer.toString(getRelativeRow());
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((direction == null) ? 0 : direction.hashCode());
+ result = prime * result + row;
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+
+ GraphCutLevel other = (GraphCutLevel) obj;
+ if (direction != other.direction) {
+ return false;
+ }
+ if (row != other.row) {
+ return false;
+ }
+ return true;
+ }
+
+ private int getRelativeRow() {
+ return direction == OUT ? -row : row;
+ }
+
+ @Override
+ public int compareTo(GraphCutLevel l2) {
+
+ int result = getDirection().compareTo(l2.getDirection());
+ if (result != 0) {
+ return result;
+ }
+
+ return -(getRelativeRow() - l2.getRelativeRow());
+ }
+}
diff --git a/codecut-gui/src/main/java/graphcut/GraphCutProvider.java b/codecut-gui/src/main/java/graphcut/GraphCutProvider.java
new file mode 100644
index 0000000..a3570b7
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/GraphCutProvider.java
@@ -0,0 +1,1286 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+/*
+ * Borrows from Ghidra file /Features Graph FunctionCalls/src/main/java/functioncalls/plugin/FcgProvider.java
+ */
+
+
+package graphcut;
+
+import static functioncalls.graph.FcgDirection.*;
+
+import java.awt.*;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.util.*;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import javax.swing.*;
+
+import org.apache.commons.collections4.IterableUtils;
+
+import com.google.common.cache.RemovalNotification;
+
+import codecutguiv2.CodeCutGUIPlugin;
+import docking.*;
+import docking.action.*;
+import docking.menu.ActionState;
+import docking.menu.MultiStateDockingAction;
+import docking.widgets.EventTrigger;
+import docking.widgets.OptionDialog;
+import functioncalls.graph.*;
+import ghidra.app.context.NavigationActionContext;
+import ghidra.graph.VisualGraphComponentProvider;
+import ghidra.graph.viewer.*;
+import ghidra.graph.viewer.actions.VgVertexContext;
+import ghidra.graph.viewer.event.mouse.VertexMouseInfo;
+import ghidra.graph.viewer.layout.*;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.listing.*;
+import ghidra.program.model.symbol.Namespace;
+import ghidra.program.util.ProgramLocation;
+import ghidra.util.HelpLocation;
+import ghidra.util.SystemUtilities;
+import ghidra.util.exception.CancelledException;
+import ghidra.util.task.TaskMonitor;
+import resources.Icons;
+import resources.ResourceManager;
+import util.CollectionUtils;
+
+/**
+ * The primary component provider for the GraphCutPlugin
+ */
+public class GraphCutProvider
+ extends VisualGraphComponentProvider {
+
+ private static final ImageIcon ICON = ResourceManager.loadImage("images/function_graph.png");
+
+ // A limit for displayed references
+ public static final int MAX_REFERENCES = 100;
+
+ private static final String TOOLBAR_GROUP_A = "A";
+ private static final String TOOLBAR_GROUP_B = "B";
+
+ private static final String MENU_GROUP_EXPAND = "A";
+ private static final String MENU_GROUP_GRAPH = "B";
+
+ private static final String NAME = "CodeCut Graph";
+
+ private JComponent component;
+ private CodeCutGUIPlugin plugin;
+
+ private GraphCutView view;
+ private LayoutProvider defaultLayoutProvider = new GraphCutLayoutProvider();
+ private LayoutProvider layoutProvider;
+ private Set> layouts = new HashSet<>();
+
+ private GraphCutDataFactory dataFactory;
+ private GraphCutData graphData;
+
+ private GraphCutExpansionListener expansionListener = new ExpansionListener();
+
+ private Predicate unfiltered = v -> true;
+ private Predicate edgeNotInGraphFilter = e -> !graphData.getGraph().containsEdge(e);
+ private Predicate vertexInGraphFilter = v -> graphData.getGraph().containsVertex(v);
+
+ private ToggleDockingAction navigateIncomingToggleAction;
+
+ public HashMap> functionsByNamespace = new HashMap<>();
+ public Set FilterWhitelist = new HashSet<>();
+
+ public GraphCutProvider(Tool tool, CodeCutGUIPlugin plugin) {
+ super(tool, NAME, plugin.getName());
+ this.plugin = plugin;
+
+
+ dataFactory = new GraphCutDataFactory(this::graphDataCacheRemoved);
+ graphData = dataFactory.create(null);
+
+ buildComponent();
+
+ // If you want icon in toolbar and key shortcut
+ //setIcon(ICON);
+ //addToToolbar();
+ //setKeyBinding(new KeyBindingData(KeyEvent.VK_G, DockingUtils.CONTROL_KEY_MODIFIER_MASK));
+
+ setWindowMenuGroup(CodeCutGUIPlugin.GRAPH_NAME);
+ setWindowGroup(CodeCutGUIPlugin.GRAPH_NAME);
+ setDefaultWindowPosition(WindowPosition.WINDOW);
+
+ setHelpLocation(CodeCutGUIPlugin.DEFAULT_HELP);
+
+ addToTool();
+ addSatelliteFeature();
+
+ createLayouts();
+ createActions();
+
+ }
+
+ private void buildFunctionsByNamespace(){
+ ProgramLocation loc = plugin.getCurrentLocation();
+ Program p = loc.getProgram();
+ FunctionManager fm = p.getFunctionManager();
+ FunctionIterator it = fm.getFunctions(true);
+
+ while (it.hasNext()) {
+ Function f = it.next();
+ Namespace parent = f.getParentNamespace();
+ if(functionsByNamespace.get(parent) == null) {
+ //key does not exist yet
+ functionsByNamespace.put(parent, new ArrayList());
+ }
+
+ functionsByNamespace.get(parent).add(f);
+ }
+
+ }
+
+
+ @Override
+ public GraphCutView getView() {
+ return view;
+ }
+
+ @Override
+ public void componentShown() {
+ installGraph();
+ }
+
+ public void optionsChanged() {
+ view.optionsChanged();
+ }
+
+ public void locationChanged(ProgramLocation loc) {
+ if(!navigateIncomingToggleAction.isSelected()) {
+ return;
+ }
+
+ if(loc == null) {
+ setNamespace(null);
+ return;
+ }
+
+ Program p = loc.getProgram();
+ FunctionManager fm = p.getFunctionManager();
+ Function f = fm.getFunctionContaining(loc.getAddress());
+ if(f == null) {
+ return;
+ }
+ Namespace ns = f.getParentNamespace();
+ setNamespace(ns);
+ }
+
+ void setNamespace(Namespace ns) {
+ if(graphData.isNamespace(ns)) {
+ return;
+ }
+
+ saveCurrentGraphPerspective();
+ createAndInstallGraph(ns);
+ updateTitle();
+ }
+
+ private void saveCurrentGraphPerspective() {
+ if (!isVisible()) {
+ return;
+ }
+ if(!graphData.hasResults()) {
+ return;
+ }
+ if(view.getGraphComponent().isUninitialized()) {
+ return;
+ }
+
+ GraphPerspectiveInfo info = view.generateGraphPerspective();
+ graphData.setGraphPerspective(info);
+ }
+
+ private void updateTitle() {
+ setTitle(NAME);
+ String subTitle = null;
+ if(graphData.hasResults()) {
+ GraphCutGraph graph = graphData.getGraph();
+ subTitle = graphData.getNamespace().getName() + " ("+graph.getVertexCount() + " namespaces; " + graph.getEdgeCount() + " edges)";
+ }
+ setSubTitle(subTitle);
+ }
+
+ public void rebuildCurrentGraph() {
+ if (!graphData.hasResults()) {
+ return;
+ }
+
+ //Mark all nodes as invisible
+ GraphCutGraph g = graphData.getGraph();
+ Iterator it = g.getAllVertices();
+ while(it.hasNext()) {
+ GraphCutVertex v = it.next();
+ v.visible = false;
+ }
+
+ Namespace namespace = graphData.getNamespace();
+ dataFactory.remove(namespace);
+ createAndInstallGraph(namespace);
+ }
+
+ private void createAndInstallGraph(Namespace namespace) {
+ graphData = dataFactory.create(namespace);
+ if(!isVisible()) {
+ return;
+ }
+ installGraph();
+ }
+
+ private void installGraph() {
+ if (!graphData.hasResults()) {
+ Address address = plugin.getCurrentAddress();
+ if(address == null) {
+ view.showErrorView("No namespace selected");
+ }
+ else {
+ view.showErrorView("No namespace containing " +address);
+ }
+ return;
+ }
+
+ buildFunctionsByNamespace();
+
+ if(graphData.isInitialized()) {
+ view.setGraph(graphData.getGraph());
+ view.setGraphPerspective(graphData.getGraphPerspective());
+ return;
+ }
+
+ GraphCutGraph graph = graphData.getGraph();
+ setLayout(graph);
+
+ GraphCutLevel source = GraphCutLevel.sourceLevel();
+ GraphCutVertex sourceVertex = new GraphCutVertex(graphData.getNamespace(), source, expansionListener);
+ graph.setSource(sourceVertex);
+ trackNamespaceEdges(sourceVertex);
+
+ view.setGraph(graph);
+ GraphCutComponent gc = view.getGraphComponent();
+ gc.setVertexFocused(sourceVertex);
+
+ if(sourceVertex.canExpandIncomingReferences()) {
+ expand(sourceVertex, IN);
+ }
+
+ if(sourceVertex.canExpandOutgoingReferences()) {
+ expand(sourceVertex, OUT);
+ }
+
+ }
+
+ private GraphCutVertex getOrCreateVertex(Namespace ns, GraphCutLevel level) {
+ GraphCutGraph graph = graphData.getGraph();
+ GraphCutVertex v = graph.getVertex(ns);
+ if(v != null) {
+ return v;
+ }
+
+ v = new GraphCutVertex(ns, level, expansionListener);
+ trackNamespaceEdges(v);
+ return v;
+ }
+
+ private void graphDataCacheRemoved(RemovalNotification notification) {
+ GraphCutData data = notification.getValue();
+ data.dispose();
+ }
+
+ private void setLayout(GraphCutGraph g) {
+ try {
+ VisualGraphLayout layout =
+ layoutProvider.getLayout(g, TaskMonitor.DUMMY);
+ g.setLayout(layout);
+ view.setLayoutProvider(layoutProvider);
+ }
+ catch(CancelledException e) {
+ // can't happen
+ }
+ }
+
+ private void buildComponent() {
+ view = new GraphCutView(plugin.getOptions());
+
+ //double click happened
+ view.setVertexClickListener((v, info) -> {
+ if(!isNavigatableArea(info)) {
+ return false;
+ }
+
+ Namespace ns = v.getNamespace();
+ ArrayList fun_arr = functionsByNamespace.get(ns);
+ if(fun_arr == null || fun_arr.isEmpty()) {
+ return true;
+ }
+
+ Function f = fun_arr.get(0);
+ Address entry = f.getEntryPoint();
+ Program p = f.getProgram();
+ plugin.handleProviderLocationChanged(new ProgramLocation(p, entry));
+ return true;
+ });
+
+ view.setTooltipProvider(new GraphCutTooltipProvider());
+
+ JComponent viewComponent = view.getViewComponent();
+ component = new JPanel(new BorderLayout());
+ component.add(viewComponent, BorderLayout.CENTER);
+ }
+
+ private boolean isNavigatableArea(VertexMouseInfo info) {
+ Component clickedComponent = info.getClickedComponent();
+ if(clickedComponent instanceof JButton) {
+ return false;
+ }
+
+ int buffer = 10;
+ MouseEvent e = info.getTranslatedMouseEvent();
+ Point p = e.getPoint();
+ if(p.x < buffer || p.y < buffer) {
+ return false;
+ }
+
+ Rectangle bounds = clickedComponent.getBounds();
+ if (bounds.width - p.x < buffer || bounds.height - p.y < buffer) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public JComponent getComponent() {
+ return component;
+ }
+
+ @Override
+ public void dispose() {
+ dataFactory.dispose();
+ graphData.dispose();
+ functionsByNamespace.clear();
+ FilterWhitelist.clear();
+ super.dispose();
+ }
+
+ @Override
+ public Class> getContextType(){
+ return NavigationActionContext.class;
+ }
+
+ public GraphCutGraph getGraph() {
+ if(graphData.hasResults()) {
+ return graphData.getGraph();
+ }
+ return null;
+ }
+
+ private void createLayouts() {
+ //Off the shelf layouts
+ layouts.addAll(JungLayoutProviderFactory.createLayouts());
+
+ //specialized layout
+ layouts.add(defaultLayoutProvider);
+ layoutProvider = defaultLayoutProvider;
+ }
+
+ private void createActions() {
+
+ addLayoutAction();
+
+ DockingAction collapseIn = new CollapseAction("Hide Incoming Edges", IN);
+ DockingAction collapseOut = new CollapseAction("Hide Outgoing Edges", OUT);
+
+ DockingAction expandIn = new ExpandAction("Show Incoming Edges", IN);
+ DockingAction expandOut = new ExpandAction("Show Outgoing Edges", OUT);
+
+ DockingAction collapseLevelIn = new CollapseLevelAction("Hide Incoming Level Edges", IN);
+ DockingAction collapseLevelOut = new CollapseLevelAction("Hide Outgoing Level Edges", OUT);
+
+ DockingAction expandLevelIn = new ExpandLevelAction("Show Incoming Level Edges", IN);
+ DockingAction expandLevelOut = new ExpandLevelAction("Show Outgoing Level Edges", OUT);
+
+ // ExpandLevelAction
+
+ addLocalAction(collapseIn);
+ addLocalAction(collapseOut);
+ addLocalAction(collapseLevelIn);
+ addLocalAction(collapseLevelOut);
+ addLocalAction(expandIn);
+ addLocalAction(expandOut);
+ addLocalAction(expandLevelIn);
+ addLocalAction(expandLevelOut);
+
+ navigateIncomingToggleAction =
+ new ToggleDockingAction("Navigate on Incoming Location Changes", plugin.getName()) {
+ @Override
+ public void actionPerformed(ActionContext context) {
+ //nothing
+ }
+
+ @Override
+ public void setSelected(boolean newValue) {
+ super.setSelected(newValue);
+ if(isSelected()) {
+ locationChanged(plugin.getCurrentLocation());
+ }
+ }
+ };
+
+ navigateIncomingToggleAction.setSelected(true);
+ navigateIncomingToggleAction.setToolBarData(
+ new ToolBarData(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON, TOOLBAR_GROUP_A));
+ navigateIncomingToggleAction.setDescription(
+ "Incoming Navigation Toggle On - change the graphed " +
+ "namespace on Listing navigation events" +
+ " Toggled Off - don't change the graph on Listing navigation events");
+ navigateIncomingToggleAction.setHelpLocation(
+ new HelpLocation(plugin.getName(), "Navigation_Incoming"));
+ addLocalAction(navigateIncomingToggleAction);
+
+ DockingAction graphNamespaceAction =
+ new DockingAction("Graph Node Namespace Calls", plugin.getName()) {
+
+ @Override
+ public void actionPerformed(ActionContext context) {
+ VgVertexContext vContext = getVertexContext(context);
+ GraphCutVertex v = vContext.getVertex();
+ setNamespace(v.getNamespace());
+ }
+
+ @Override
+ public boolean isEnabledForContext(ActionContext context) {
+ VgVertexContext vContext = getVertexContext(context);
+ if(vContext == null) {
+ return false;
+ }
+
+ GraphCutVertex v = vContext.getVertex();
+ Namespace namespace = v.getNamespace();
+ Namespace graphedNamespace = graphData.getNamespace();
+
+ boolean isEnabled = !namespace.equals(graphedNamespace);
+ if(isEnabled) {
+ setPopupMenuData(
+ new MenuData(new String[] {"Graph '" + namespace.getName() + "'"},
+ MENU_GROUP_GRAPH));
+
+ }
+ return isEnabled;
+ }
+ };
+
+ graphNamespaceAction.setPopupMenuData(
+ new MenuData(new String [] {"Graph Namespace"}, MENU_GROUP_GRAPH));
+ addLocalAction(graphNamespaceAction);
+
+ }
+
+ private Collection getGraphEdges(GraphCutVertex v, FcgDirection direction){
+
+ GraphCutGraph graph = graphData.getGraph();
+ if(direction == IN) {
+ return graph.getInEdges(v);
+ }
+ return graph.getOutEdges(v);
+ }
+
+ //return edges that we know about, but may not be graphed
+ private Set getModelEdges(Iterable vertices, GraphCutLevel level, Predicate filter){
+
+ FcgDirection direction = level.getDirection();
+ if (direction == IN) {
+ return getIncomingEdges(vertices, level, filter);
+ }
+ return getOutgoingEdges(vertices, level, filter);
+ }
+
+ private VgVertexContext getVertexContext(ActionContext c){
+ if(!(c instanceof VgVertexContext)) {
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ VgVertexContext vContext = (VgVertexContext) c;
+ return vContext;
+ }
+
+
+ //============================================================================
+ // Layout Methods
+ //============================================================================
+
+ static final String RELAYOUT_GRAPH_ACTION_NAME = "Relayout Graph";
+
+ private void addLayoutAction() {
+ DockingAction resetGraphAction = new DockingAction("Reset Graph", plugin.getName()) {
+ @Override
+ public void actionPerformed(ActionContext context) {
+ int choice = OptionDialog.showYesNoDialog(getComponent(), "Reset graph?", "Erase all vertex position information?");
+ if(choice != OptionDialog.YES_OPTION) {
+ return;
+ }
+
+ rebuildCurrentGraph();
+ }
+ };
+ resetGraphAction.setToolBarData(new ToolBarData(Icons.REFRESH_ICON));
+ resetGraphAction
+ .setDescription("Resets the graph -- All positioning will be lost");
+ resetGraphAction.setHelpLocation(new HelpLocation("GraphCutPlugin", "Relayout_Graph"));
+
+ addLocalAction(resetGraphAction);
+
+ MultiStateDockingAction> layoutAction =
+ new MultiStateDockingAction<>(RELAYOUT_GRAPH_ACTION_NAME, plugin.getName()) {
+
+ @Override
+ public void actionPerformed(ActionContext context) {
+ LayoutProvider currentUserData = getCurrentUserData();
+ changeLayout(currentUserData);
+ }
+
+ @Override
+ public void actionStateChanged(
+ ActionState> newActionState,
+ EventTrigger trigger) {
+ changeLayout(newActionState.getUserData());
+ }
+ };
+
+ layoutAction.setGroup(TOOLBAR_GROUP_B);
+
+ addLayoutProviders(layoutAction);
+
+ //can also addLocalAction(layoutAction) for debug
+ }
+
+ private void addLayoutProviders(MultiStateDockingAction> layoutAction) {
+
+ for(LayoutProvider l: layouts) {
+ layoutAction.addActionState(new ActionState<>(l.getLayoutName(), l.getActionIcon(), l));
+ }
+
+ layoutAction.setCurrentActionStateByUserData(defaultLayoutProvider);
+ }
+
+ private void changeLayout(LayoutProvider provider) {
+ this.layoutProvider = provider;
+ if(isVisible()) {
+ rebuildCurrentGraph();
+ }
+ }
+
+ private Iterable getVerticesByLevel(GraphCutLevel level){
+ GraphCutGraph graph = graphData.getGraph();
+ return graph.getVerticesByLevel(level);
+ }
+
+ private void trackNamespaceEdges(GraphCutVertex v) {
+ Namespace ns = v.getNamespace();
+ NamespaceEdgeCache edgeCache = graphData.getNamespaceEdgeCache();
+ if(edgeCache.isTracked(ns)) {
+ return; //already tracked
+ }
+ edgeCache.setTracked(ns);
+
+ Set calling = nsGetCallingNamespaces(ns);
+ int count = calling.size();
+ if (count > MAX_REFERENCES) {
+ v.setTooManyIncomingReferences(true);
+ v.setHasIncomingReferences(true);
+ }
+ else {
+ trackNamespaceIncomingEdges(v, calling);
+ v.setHasIncomingReferences(!calling.isEmpty());
+ }
+
+ Set called = nsGetCalledNamespaces(ns);
+ count = called.size();
+ if(count > MAX_REFERENCES) {
+ v.setTooManyOutgoingReferences(true);
+ v.setHasOutgoingReferences(true);
+ }
+ else {
+ trackNamespaceOutgoingEdges(v,called);
+ v.setHasOutgoingReferences(true);
+ }
+ }
+
+ private void trackNamespaceOutgoingEdges(GraphCutVertex v, Set local_calledNamespaces) {
+ NamespaceEdgeCache edgeCache = graphData.getNamespaceEdgeCache();
+ Namespace ns = v.getNamespace();
+ for(Namespace callee : local_calledNamespaces) {
+ edgeCache.get(ns).add(new NamespaceEdge(ns, callee));
+ }
+ }
+
+ private void trackNamespaceIncomingEdges(GraphCutVertex v, Set local_callingNamespaces) {
+ NamespaceEdgeCache edgeCache = graphData.getNamespaceEdgeCache();
+ Namespace ns = v.getNamespace();
+ for(Namespace caller : local_callingNamespaces) {
+ edgeCache.get(ns).add(new NamespaceEdge(caller, ns));
+ }
+ }
+
+
+ /**
+ * The equivalent of f.getCallingFunctions but for namespaces
+ * @param ns the namespace
+ * @return namespaces that "call" this namespace
+ */
+ public Set nsGetCallingNamespaces(Namespace ns){
+ Set set = new HashSet();
+
+ for(Function f: functionsByNamespace.get(ns)) { // for every function in our namespace
+ Set calling = f.getCallingFunctions(TaskMonitor.DUMMY);
+ for(Function called: calling) { // look at every function that calls it
+ Namespace parent = called.getParentNamespace();
+ boolean wantToAdd = FilterWhitelist.isEmpty() || FilterWhitelist.contains(parent);
+ if(wantToAdd) {
+ set.add(parent); //add their namespace to the "calling namespaces"
+ }
+ }
+ }
+ return set;
+ }
+
+
+ /**
+ * the equivalent of f.getCalledFunctions but for namespaces
+ * @param ns the namespace
+ * @return namespaces that this namespace "calls"
+ */
+ public Set nsGetCalledNamespaces(Namespace ns){
+ Set set = new HashSet();
+
+ for(Function f: functionsByNamespace.get(ns)) { // for every function in our namespace
+ Set called = f.getCalledFunctions(TaskMonitor.DUMMY);
+ for(Function calling: called) { //look at every function that it calls
+ Namespace parent = calling.getParentNamespace();
+ boolean wantToAdd = FilterWhitelist.isEmpty() || FilterWhitelist.contains(parent);
+ if(wantToAdd) {
+ set.add(parent);// add their namespace to the "called namespaces"
+ }
+ }
+ }
+ return set;
+ }
+
+ //============================================================================
+ // Expand/Collapse Methods
+ //============================================================================
+
+ private void expand(GraphCutVertex source, FcgDirection direction) {
+ GraphCutLevel level = source.getLevel();
+ expand(CollectionUtils.asIterable(source), level, direction);
+ }
+
+ private void expand(Iterable sources,GraphCutLevel sourceLevel, FcgDirection direction) {
+ sources = IterableUtils.filteredIterable(sources, v -> v.canExpand());
+ if(IterableUtils.isEmpty(sources)) {
+ return;
+ }
+
+ GraphCutLevel expandingLevel = sourceLevel.child(direction);
+ Set newEdges = getModelEdges(sources, expandingLevel, edgeNotInGraphFilter);
+
+ Iterable sourceSiblings = getVerticesByLevel(sourceLevel);
+ Set parentLevelEdges = getModelEdges(sourceSiblings, expandingLevel, unfiltered);
+
+ Set newVertices = toVertices(newEdges, direction, vertexInGraphFilter.negate());
+ boolean isIncoming = direction == IN;
+ GraphCutExpandingVertexCollection collection =
+ new GraphCutExpandingVertexCollection(sources, sourceLevel, expandingLevel, newVertices, newEdges, parentLevelEdges, isIncoming, view.getPrimaryGraphViewer());
+
+ doExpand(collection);
+ markExpanded(sources, direction, true);
+ }
+
+ private void markExpanded(Iterable vertices, FcgDirection direction, boolean expanded) {
+ if (direction == IN) {
+ markInsExpanded(vertices, expanded);
+ }
+ else {
+ markOutsExpanded(vertices, expanded);
+ }
+
+ component.repaint();
+ }
+
+ private void markInsExpanded(Iterable vertices, boolean expanded) {
+ for(GraphCutVertex v: vertices){
+ if(expanded != v.isIncomingExpanded()) {
+ v.setIncomingExpanded(expanded);
+ }
+ }
+ }
+
+ private void markOutsExpanded(Iterable vertices, boolean expanded) {
+ for(GraphCutVertex v: vertices) {
+ if(expanded != v.isOutgoingExpanded()) {
+ v.setOutgoingExpanded(expanded);
+ }
+ }
+ }
+
+ private void collapseLevel(GraphCutLevel level, FcgDirection direction) {
+
+ GraphCutLevel collapseLevel = level.child(direction);
+ Iterable toRemove = getVerticesAtOrGreaterThan(collapseLevel);
+
+ Set set = util.CollectionUtils.asSet(toRemove.iterator());
+ GraphCutGraph graph = graphData.getGraph();
+ graph.removeVertices(set);
+ component.repaint();
+
+ updateTitle();
+
+ Iterable sources = graph.getVerticesByLevel(level);
+ markExpanded(sources, direction, false);
+ }
+
+ private void collapse(GraphCutVertex v, FcgDirection direction) {
+
+ Collection edges = new HashSet<>(getGraphEdges(v, direction));
+ GraphCutGraph graph = graphData.getGraph();
+ for (GraphCutEdge e: edges) {
+
+ GraphCutVertex other = getOtherEnd(v,e);
+ if(isDependent(v, other, e)) {
+ graph.removeEdge(e);
+
+ collapse(other, direction);
+ removeFromGraph(other);
+ }
+ }
+ markExpanded(CollectionUtils.asIterable(v), direction, false);
+ }
+
+ private GraphCutVertex getOtherEnd(GraphCutVertex v, GraphCutEdge e) {
+ GraphCutVertex start = e.getStart();
+ if(v.equals(start)) {
+ return e.getEnd();
+ }
+ return start;
+ }
+
+ private boolean isDependent(GraphCutVertex parent, GraphCutVertex other, GraphCutEdge e) {
+ GraphCutLevel parentLevel = parent.getLevel();
+ GraphCutLevel otherLevel = other.getLevel();
+ if(!parentLevel.isParentOf(otherLevel)) {
+ return false;
+ }
+
+ //we are a dependent
+ GraphCutGraph g = graphData.getGraph();
+ Collection ins = g.getInEdges(other);
+ for(GraphCutEdge inEdge : ins) {
+ GraphCutVertex start = inEdge.getStart();
+ if(start.equals(parent)) {
+ continue;
+ }
+
+ GraphCutLevel inLevel = start.getLevel();
+ if(!inLevel.equals(parentLevel)) {
+ continue;
+ }
+
+ if(start.isExpanded()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void removeFromGraph(GraphCutVertex v){
+ GraphCutGraph g = graphData.getGraph();
+ g.removeVertex(v);
+ component.repaint();
+ }
+
+ private Set getIncomingEdges(Iterable vertices, GraphCutLevel level, Predicate filter){
+ Map newVertexCache = new HashMap<>();
+ Set result = new HashSet<>();
+ for(GraphCutVertex source: vertices) {
+ Namespace ns = source.getNamespace();
+ Iterable namespaces = getCallingNamespaces(ns);
+ Set callers = toVertices(namespaces, level, newVertexCache);
+ for(GraphCutVertex caller: callers) {
+ GraphCutEdge e = getOrCreateEdge(caller, source);
+ if(!filter.test(e)) {
+ continue;
+ }
+ result.add(e);
+ }
+ }
+ return result;
+ }
+
+ private GraphCutEdge getOrCreateEdge(GraphCutVertex start, GraphCutVertex end) {
+
+ GraphCutGraph graph = graphData.getGraph();
+ Iterable edges = graph.getEdges(start, end);
+ GraphCutEdge e = CollectionUtils.any(edges);
+ if(e != null) {
+ return e;
+ }
+ return new GraphCutEdge(start, end);
+ }
+
+ private Set toStartVertices(Iterable edges, Predicate filter){
+ return CollectionUtils
+ .asStream(edges)
+ .map(e -> e.getStart())
+ .filter(filter)
+ .collect(Collectors.toSet())
+ ;
+ }
+
+ private Set toVertices(Iterable edges, FcgDirection direction, Predicate filter){
+ return direction == IN ? toStartVertices(edges, filter) : toEndVertices(edges, filter);
+ }
+
+ private Set toVertices(Iterable callees, GraphCutLevel level, Map newVertexCache){
+ return CollectionUtils.asStream(callees)
+ .map(ns -> {
+ if (newVertexCache.containsKey(ns)) {
+ return newVertexCache.get(ns);
+ }
+ GraphCutVertex v = getOrCreateVertex(ns, level);
+ newVertexCache.put(ns, v);
+ return v;
+ })
+ .collect(Collectors.toSet());
+ }
+
+
+ private Set toEndVertices(Iterable edges, Predicate filter){
+ return CollectionUtils
+ .asStream(edges)
+ .map(e -> e.getEnd())
+ .filter(filter)
+ .collect(Collectors.toSet());
+ }
+
+ private Set getOutgoingEdges(Iterable vertices, GraphCutLevel level, Predicate filter){
+
+ Map newVertexCache = new HashMap<>();
+ Set result = new HashSet<>();
+ for(GraphCutVertex source: vertices) {
+ Namespace ns = source.getNamespace();
+ Iterable namespaces = getCallerNamespaces(ns);
+ Set callees = toVertices(namespaces, level, newVertexCache);
+ for(GraphCutVertex callee: callees) {
+ GraphCutEdge e = getOrCreateEdge(source, callee);
+ if(!filter.test(e)) {
+ continue;
+ }
+ result.add(e);
+ }
+ }
+ return result;
+ }
+
+ private Iterable getVerticesAtOrGreaterThan(GraphCutLevel level){
+ List> result = new ArrayList<>();
+ GraphCutGraph graph = graphData.getGraph();
+ GraphCutLevel greatestLevel = graph.getLargestLevel(level.getDirection());
+ GraphCutLevel currentLevel = level;
+ while (currentLevel.getRow() <= greatestLevel.getRow()) {
+ Iterable vertices = getVerticesByLevel(currentLevel);
+ result.add(vertices);
+ currentLevel = currentLevel.child();
+ }
+
+ Collections.reverse(result);
+
+ @SuppressWarnings("unchecked")
+ Iterable[] array = result.toArray(new Iterable[result.size()]);
+ return IterableUtils.chainedIterable(array);
+ }
+
+ private void doExpand(GraphCutExpandingVertexCollection collection) {
+ Set newVertices = collection.getNewVertices();
+ GraphCutGraph graph = graphData.getGraph();
+ for (GraphCutVertex v : newVertices) {
+ graph.addVertex(v);
+ }
+
+ Iterable newEdges = collection.getNewEdges();
+ for(GraphCutEdge e : newEdges) {
+ graph.addEdge(e);
+ }
+
+ Set indirectEdges = new HashSet<>();
+ addEdgesToExistingVertices(newVertices, indirectEdges);
+ collection.setIndirectEdges(indirectEdges);
+
+ int newEdgeCount = collection.getNewEdgeCount();
+ if (newEdgeCount == 0) {
+ highlightExistingEdges(collection);
+ return;
+ }
+
+ GraphViewer viewer = view.getPrimaryGraphViewer();
+ GraphCutLayoutExpandVerticesJob job = new GraphCutLayoutExpandVerticesJob(viewer, collection, true);
+ VisualGraphViewUpdater updater = view.getViewUpdater();
+ updater.scheduleViewChangeJob(job);
+ updateTitle();
+
+ String viewName = "GraphCut Graph";
+ viewer.setName(viewName);
+ viewer.getAccessibleContext().setAccessibleName(viewName);
+ }
+
+ private void highlightExistingEdges(GraphCutExpandingVertexCollection collection) {
+
+ GraphViewer viewer = view.getPrimaryGraphViewer();
+ VisualGraphViewUpdater updater = view.getViewUpdater();
+
+ Iterable sources = collection.getSources();
+ GraphCutVertex source = CollectionUtils.any(sources);
+ GraphCutLevel level = source.getLevel();
+ Set existingEdges = getModelEdges(sources, level, unfiltered);
+ GraphCutEmphasizeEdgesJob job = new GraphCutEmphasizeEdgesJob(viewer, existingEdges);
+ updater.scheduleViewChangeJob(job);
+ }
+
+ /**
+ * Calling this method ensures that as vertices appear, edges are added
+ * @param newVertices the vertices being added to the graph
+ * @param newEdges the set to which should be added any new edges
+ */
+ private void addEdgesToExistingVertices(Iterable newVertices, Set newEdges) {
+
+ GraphCutGraph graph = graphData.getGraph();
+ NamespaceEdgeCache cache = graphData.getNamespaceEdgeCache();
+ for(GraphCutVertex v : newVertices) {
+ Namespace ns = v.getNamespace();
+ Set edges = cache.get(ns);
+ for(NamespaceEdge e : edges) {
+ Namespace start = e.getStart();
+ Namespace end = e.getEnd();
+ GraphCutVertex v1 = graph.getVertex(start);
+ GraphCutVertex v2 = graph.getVertex(end);
+
+ if(v1 == null || v2 == null) {
+ continue;
+ }
+
+ if(!graph.containsEdge(v1,v2)) {
+ GraphCutEdge newEdge = new GraphCutEdge(v1,v2);
+ graph.addEdge(newEdge);
+ newEdges.add(newEdge);
+ }
+ }
+ }
+ }
+
+ // this one uses the cache
+ private Iterable getCallingNamespaces(Namespace ns){
+
+ NamespaceEdgeCache edgeCache = graphData.getNamespaceEdgeCache();
+
+ SystemUtilities.assertTrue(edgeCache.isTracked(ns), "Namespace not tracked in cache");
+
+ Set edges = edgeCache.get(ns);
+ Iterable filtered =
+ IterableUtils.filteredIterable(edges, e -> isCalledNamespace(ns, e));
+ Iterable namespaces =
+ IterableUtils.transformedIterable(filtered, e -> e.getStart());
+ return namespaces;
+ }
+
+ // this one uses the cache
+ private Iterable getCallerNamespaces(Namespace ns){
+ NamespaceEdgeCache edgeCache = graphData.getNamespaceEdgeCache();
+
+ SystemUtilities.assertTrue(edgeCache.isTracked(ns), "Namespace not tracked in cache");
+
+ Set edges = edgeCache.get(ns);
+ Iterable filtered =
+ IterableUtils.filteredIterable(edges, e -> isCallingNamespace(ns,e));
+ Iterable namespaces = IterableUtils.transformedIterable(filtered, e -> e.getEnd());
+ return namespaces;
+ }
+
+ private boolean isCallingNamespace(Namespace ns, NamespaceEdge e) {
+ Namespace start = e.getStart();
+ return start.equals(ns);
+ }
+
+ private boolean isCalledNamespace(Namespace ns, NamespaceEdge e) {
+ Namespace end = e.getEnd();
+ return end.equals(ns);
+ }
+
+
+
+ //============================================================================
+ // Inner Classes
+ //============================================================================
+
+ private class ExpansionListener implements GraphCutExpansionListener{
+
+ @Override
+ public void toggleIncomingVertices(GraphCutVertex v) {
+ boolean expanded = v.isIncomingExpanded();
+ if (expanded) {
+ collapse(v, IN);
+ }
+ else {
+ expand(v, IN);
+ }
+ }
+
+ @Override
+ public void toggleOutgoingVertices(GraphCutVertex v) {
+ boolean expanded = v.isOutgoingExpanded();
+ if (expanded) {
+ collapse(v, OUT);
+ }
+ else {
+ expand(v, OUT);
+ }
+ }
+ }
+
+ private abstract class AbstractCollapseAction extends DockingAction{
+ protected FcgDirection direction;
+
+ AbstractCollapseAction(String actionName, FcgDirection direction){
+ super(actionName, plugin.getName());
+ this.direction = direction;
+
+ setPopupMenuData(new MenuData(new String[] {actionName}, MENU_GROUP_EXPAND));
+ setHelpLocation(new HelpLocation("GraphCutPlugin", "Expand_Collapse"));
+ }
+
+ abstract void collapseFromContext(VgVertexContext context);
+
+ @Override
+ public void actionPerformed(ActionContext context) {
+ VgVertexContext vContext = getVertexContext(context);
+ collapseFromContext(vContext);
+ }
+
+ @Override
+ public boolean isEnabledForContext(ActionContext context) {
+ VgVertexContext vContext = getVertexContext(context);
+ if(vContext == null) {
+ return false;
+ }
+
+ GraphCutVertex v = vContext.getVertex();
+ boolean expanded = direction == IN ? v.isIncomingExpanded() : v.isOutgoingExpanded();
+ if(!expanded) {
+ return false;
+ }
+
+ if(!isMyDirection(v.getLevel())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ boolean isMyDirection(GraphCutLevel level) {
+ return level.getDirection() == direction;
+ }
+
+ }
+
+ private class CollapseAction extends AbstractCollapseAction {
+
+ CollapseAction(String actionName, FcgDirection direction){
+ super(actionName, direction);
+ }
+
+ @Override
+ void collapseFromContext(VgVertexContext context) {
+ GraphCutVertex v = context.getVertex();
+ GraphCutLevel level = v.getLevel();
+ collapseLevel(level, direction);
+ }
+
+ @Override
+ boolean isMyDirection(GraphCutLevel level) {
+ if (level.getDirection() == FcgDirection.IN_AND_OUT) {
+ return true;
+ }
+ return level.getDirection() == direction;
+ }
+ }
+
+ private class CollapseLevelAction extends AbstractCollapseAction {
+
+ CollapseLevelAction(String actionName, FcgDirection direction){
+ super(actionName, direction);
+ }
+
+ @Override
+ void collapseFromContext(VgVertexContext context) {
+ GraphCutVertex v = context.getVertex();
+ GraphCutLevel level = v.getLevel();
+ collapseLevel(level, direction);
+ }
+
+ @Override
+ boolean isMyDirection(GraphCutLevel level) {
+ if (level.getDirection() == FcgDirection.IN_AND_OUT) {
+ return true;
+ }
+ return level.getDirection() == direction;
+ }
+ }
+
+ private abstract class AbstractExpandAction extends DockingAction {
+ protected FcgDirection direction;
+
+ AbstractExpandAction(String actionName, FcgDirection direction){
+ super(actionName, plugin.getName());
+ this.direction = direction;
+
+ setPopupMenuData(new MenuData(new String[] {actionName}, MENU_GROUP_EXPAND));
+ setHelpLocation(new HelpLocation("GraphCutPlugin", "Expand_Collapse"));
+ }
+
+ abstract void expandFromContext(VgVertexContext context);
+
+ abstract boolean isExpandable(GraphCutVertex v);
+
+ @Override
+ public void actionPerformed(ActionContext context) {
+ VgVertexContext vContext = getVertexContext(context);
+ expandFromContext(vContext);
+ }
+
+ @Override
+ public boolean isEnabledForContext(ActionContext context) {
+ VgVertexContext vContext = getVertexContext(context);
+ if(vContext == null) {
+ return false;
+ }
+
+ GraphCutVertex v = vContext.getVertex();
+ boolean isExpandable = isExpandable(v);
+ if(!isExpandable) {
+ return false;
+ }
+
+ if(!isMyDirection(v.getLevel())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ boolean isMyDirection(GraphCutLevel level) {
+ return level.getDirection() == direction;
+ }
+ }
+
+ private class ExpandAction extends AbstractExpandAction {
+
+ ExpandAction(String actionName, FcgDirection direction){
+ super(actionName, direction);
+ }
+
+ @Override
+ void expandFromContext(VgVertexContext context) {
+ GraphCutVertex v = context.getVertex();
+ expand(v, direction);
+ }
+
+ @Override
+ boolean isExpandable(GraphCutVertex v) {
+ return v.canExpand();
+ }
+ }
+
+ private class ExpandLevelAction extends AbstractExpandAction {
+ ExpandLevelAction(String actionName, FcgDirection direction){
+ super(actionName, direction);
+ }
+
+ @Override
+ void expandFromContext(VgVertexContext context) {
+ GraphCutVertex v = context.getVertex();
+ GraphCutLevel level = v.getLevel();
+ Iterable vertices = getVerticesByLevel(v.getLevel());
+ expand(vertices, level, direction);
+ }
+
+ @Override
+ boolean isMyDirection(GraphCutLevel level) {
+ if(level.getDirection() == FcgDirection.IN_AND_OUT) {
+ return true;
+ }
+ return level.getDirection() == direction;
+ }
+
+ @Override
+ boolean isExpandable(GraphCutVertex vertex) {
+ Iterable vertices = getVerticesByLevel(vertex.getLevel());
+ if (direction == IN) {
+ return CollectionUtils.asStream(vertices)
+ .anyMatch(GraphCutVertex::canExpandIncomingReferences);
+ }
+ return CollectionUtils.asStream(vertices)
+ .anyMatch(GraphCutVertex::canExpandOutgoingReferences);
+ }
+ }
+
+ public void addToWhitelist(Namespace ns) {
+ FilterWhitelist.add(ns);
+ rebuildCurrentGraph();
+ }
+
+ public Set getNamespacesInFilter() {
+ return FilterWhitelist;
+ }
+
+ public void removeFromWhitelist(Namespace ns) {
+ FilterWhitelist.remove(ns);
+ rebuildCurrentGraph();
+ }
+
+ public void resetWhitelist() {
+ FilterWhitelist.clear();
+ rebuildCurrentGraph();
+ }
+}
diff --git a/codecut-gui/src/main/java/graphcut/GraphCutTooltipProvider.java b/codecut-gui/src/main/java/graphcut/GraphCutTooltipProvider.java
new file mode 100644
index 0000000..4782e33
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/GraphCutTooltipProvider.java
@@ -0,0 +1,68 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+/*
+ * Heavily Borrows from Ghidra file /Features Graph FunctionCalls/src/main/java/functioncalls/graph/renderer/FcgTooltipProvider.java
+ */
+
+
+package graphcut;
+
+import java.awt.Component;
+import java.awt.event.MouseEvent;
+
+import javax.swing.JComponent;
+import javax.swing.*;
+
+import ghidra.graph.viewer.event.mouse.VertexTooltipProvider;
+
+public class GraphCutTooltipProvider implements VertexTooltipProvider {
+
+ @Override
+ public JComponent getTooltip(GraphCutVertex v) {
+ JToolTip tip = new JToolTip();
+ tip.setTipText(v.getName());
+ return tip;
+ }
+
+ @Override
+ public JComponent getTooltip(GraphCutVertex v, GraphCutEdge e) {
+ return null;
+ }
+
+ @Override
+ public String getTooltipText(GraphCutVertex v, MouseEvent e) {
+ Component child = e.getComponent();
+ if(child instanceof JButton) {
+ return ((JButton) child).getToolTipText();
+ }
+ return v.getName();
+ }
+
+}
diff --git a/codecut-gui/src/main/java/graphcut/GraphCutVertex.java b/codecut-gui/src/main/java/graphcut/GraphCutVertex.java
new file mode 100644
index 0000000..e98c214
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/GraphCutVertex.java
@@ -0,0 +1,664 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+/*
+ * Heavily Borrowed from Ghidra file /Features Graph FunctionCalls/src/main/java/functioncalls/graph/FcgVertex.java
+ */
+
+
+package graphcut;
+
+import java.awt.*;
+import java.awt.geom.Area;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Ellipse2D.Double;
+import java.awt.image.BufferedImage;
+import java.util.Objects;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.border.LineBorder;
+
+import ghidra.program.model.symbol.Namespace;
+
+import docking.widgets.EmptyBorderButton;
+import docking.widgets.label.GDLabel;
+import functioncalls.graph.FcgDirection;
+import functioncalls.graph.FcgVertex;
+import generic.theme.GColor;
+import generic.theme.GThemeDefaults.Colors.Palette;
+import generic.theme.Gui;
+import ghidra.graph.viewer.vertex.AbstractVisualVertex;
+import ghidra.graph.viewer.vertex.VertexShapeProvider;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.listing.Function;
+import ghidra.util.StringUtilities;
+import resources.Icons;
+import resources.ResourceManager;
+
+// A GraphCutVertex
+
+public class GraphCutVertex extends AbstractVisualVertex implements VertexShapeProvider {
+
+ public static final Color DEFAULT_VERTEX_SHAPE_COLOR = new GColor("color.bg.plugin.fcg.vertex.default");
+ private static final Color TOO_BIG_VERTEX_SHAPE_COLOR = new GColor("color.bg.plugin.fcg.vertex.toobig");
+
+ //@formatter:on
+
+ public static final Icon NOT_ALLOWED_ICON = Icons.ERROR_ICON;
+ private static final Icon EXPAND_ICON =
+ ResourceManager.getScaledIcon(Icons.EXPAND_ALL_ICON, 10, 10);
+ private static final Icon COLLAPSE_ICON =
+ ResourceManager.getScaledIcon(Icons.COLLAPSE_ALL_ICON, 10, 10);
+
+ // higher numbered layers go on top
+ private static final Integer VERTEX_SHAPE_LAYER = 100;
+ private static final Integer TOGGLE_BUTTON_LAYER = 200;
+ private static final Integer LABEL_LAYER = 300;
+
+ private static final int GAP = 2;
+ private static final int VERTEX_SHAPE_SIZE = 50;
+
+ private static final int MAX_NAME_LENGTH = 30;
+
+ private Namespace namespace;
+
+ private JLayeredPane layeredPane;
+ private JButton toggleInsButton = new EmptyBorderButton(EXPAND_ICON);
+ private JButton toggleOutsButton = new EmptyBorderButton(EXPAND_ICON);
+ private JLabel nameLabel = new GDLabel();
+ private JLabel vertexImageLabel = new GDLabel();
+
+ private Double vertexShape;
+ private Double compactShape;
+ private Shape fullShape;
+
+ // these values are set after construction from external sources
+ private boolean hasIncomingReferences;
+ private boolean hasOutgoingReferences;
+ private boolean tooManyIncomingReferences;
+ private boolean tooManyOutgoingReferences;
+ private boolean incomingExpanded;
+ private boolean outgoingExpanded;
+
+ // set this to true to see borders around the components of this vertex
+ private boolean useDebugBorders = false;
+
+ private Paint inPaint;
+ private Paint outPaint;
+
+ private GraphCutLevel level;
+
+ // Dynamic Layout Variables
+ public int layoutIndex;
+ public boolean visible;
+
+ /**
+ * Constructor
+ * @param namespace Namespace represented by this vertex
+ * @param level The level of this vertex
+ * @param expansionListener listener for expanding connections to this vertex
+ */
+ public GraphCutVertex(Namespace namespace, GraphCutLevel level, GraphCutExpansionListener expansionListener) {
+ this.namespace = namespace;
+ this.level = level;
+ Objects.requireNonNull(expansionListener);
+
+ toggleInsButton.addActionListener(e -> {
+ if (tooManyIncomingReferences) {
+ return;
+ }
+ expansionListener.toggleIncomingVertices(GraphCutVertex.this);
+ });
+
+ toggleOutsButton.addActionListener(e -> {
+ if (tooManyOutgoingReferences) {
+ return;
+ }
+ expansionListener.toggleOutgoingVertices(GraphCutVertex.this);
+ });
+
+ buildUi();
+
+ setTogglesVisible(false);
+ }
+
+ private void createPaints() {
+
+ Color vertexShapeColor = getVertexShapeColor();
+
+ Color lightColor = vertexShapeColor;
+ Color darkColor = Gui.darker(vertexShapeColor);
+ Color darkestColor = Gui.darker(darkColor);
+
+ int offset = 5 * level.getDistance();
+ int half = VERTEX_SHAPE_SIZE / 2;
+ int start = 0;
+ int end = half + offset;
+
+ // paint top-down: dark to light for incoming; light to dark for outgoing
+ inPaint = new LinearGradientPaint(new Point(0, start), new Point(0, end),
+ new float[] { .0f, .2f, 1f }, new Color[] { darkestColor, darkColor, lightColor });
+
+ start = half - offset; // (offset + 10);
+ end = VERTEX_SHAPE_SIZE;
+ outPaint = new LinearGradientPaint(new Point(0, start), new Point(0, end),
+ new float[] { .0f, .8f, 1f }, new Color[] { lightColor, darkColor, darkestColor });
+
+ }
+
+ private void buildUi() {
+
+ createPaints();
+
+ String truncated = StringUtilities.trimMiddle(getName(), MAX_NAME_LENGTH);
+ nameLabel.setText(truncated);
+ buildVertexShape();
+
+ // calculate the needed size
+ layeredPane = new JLayeredPane();
+ Border border = createDebugBorder(new LineBorder(Palette.GOLD, 1));
+ layeredPane.setBorder(border);
+
+ updateLayeredPaneSize();
+
+ // layout the components
+ addVertexShape();
+ addToggleButtons();
+ addNameLabel();
+
+ buildFullShape();
+ }
+
+ private Border createDebugBorder(Border border) {
+ if (useDebugBorders) {
+ return border;
+ }
+ return BorderFactory.createEmptyBorder();
+ }
+
+ private void buildFullShape() {
+
+ // Note: this method assumes all bounds have been set
+ Area parent = new Area();
+
+ Area v = new Area(vertexShape);
+ Area name = new Area(nameLabel.getBounds());
+ parent.add(v);
+ parent.add(name);
+
+ // for now, the buttons only appear on hover, but if we want to avoid clipping when
+ // painting, we need to account for them in the shape's overall bounds
+ Area in = new Area(toggleInsButton.getBounds());
+ Area out = new Area(toggleOutsButton.getBounds());
+ parent.add(in);
+ parent.add(out);
+
+ fullShape = parent;
+ }
+
+ private void updateLayeredPaneSize() {
+
+ //
+ // The overall component size is the total width and height of all components, with any
+ // spacing between them.
+ //
+
+ Dimension shapeSize = vertexImageLabel.getPreferredSize();
+ Dimension nameLabelSize = nameLabel.getPreferredSize();
+ int height = shapeSize.height + GAP + nameLabelSize.height;
+
+ Dimension insSize = toggleInsButton.getPreferredSize();
+ Dimension outsSize = toggleOutsButton.getPreferredSize();
+ int buttonWidth = Math.max(insSize.width, outsSize.width);
+ int offset = buttonWidth / 3; // overlap the vertex shape
+
+ int width = offset + shapeSize.width;
+ width = Math.max(width, nameLabelSize.width);
+
+ layeredPane.setPreferredSize(new Dimension(width, height));
+ }
+
+ private void buildVertexShape() {
+ int w = VERTEX_SHAPE_SIZE;
+ int h = VERTEX_SHAPE_SIZE;
+ Double circle = new Ellipse2D.Double(0, 0, w, h);
+
+ BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g2 = (Graphics2D) image.getGraphics();
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+ FcgDirection direction = level.getDirection();
+ if (direction.isSource()) {
+ g2.setColor(getVertexShapeColor());
+ }
+ else if (direction.isIn()) {
+ g2.setPaint(inPaint);
+ }
+ else {
+ g2.setPaint(outPaint);
+ }
+
+ g2.fill(circle);
+
+ g2.dispose();
+
+ vertexShape = circle;
+ compactShape = (Double) vertexShape.clone();
+ vertexImageLabel.setIcon(new ImageIcon(image));
+
+ Border border = createDebugBorder(new LineBorder(Palette.PINK, 1));
+ vertexImageLabel.setBorder(border);
+ }
+
+ private Color getVertexShapeColor() {
+
+ if (isInDirection() && tooManyIncomingReferences) {
+ return TOO_BIG_VERTEX_SHAPE_COLOR;
+ }
+
+ if (isOutDirection() && tooManyOutgoingReferences) {
+ return TOO_BIG_VERTEX_SHAPE_COLOR;
+ }
+
+ return DEFAULT_VERTEX_SHAPE_COLOR;
+ }
+
+ private boolean isInDirection() {
+ FcgDirection direction = level.getDirection();
+ boolean isIn = direction.isIn() || direction.isSource();
+ return isIn;
+ }
+
+ private boolean isOutDirection() {
+ FcgDirection direction = level.getDirection();
+ boolean isOut = direction.isOut() || direction.isSource();
+ return isOut;
+ }
+
+ private void addVertexShape() {
+
+ Dimension parentSize = layeredPane.getPreferredSize();
+ Dimension size = vertexImageLabel.getPreferredSize();
+
+ // centered
+ int x = (parentSize.width / 2) - (size.width / 2);
+ int y = 0;
+
+ vertexImageLabel.setBounds(x, y, size.width, size.height);
+ Dimension shapeSize = vertexShape.getBounds().getSize();
+
+ // setFrame() will make sure the shape's x,y values are where they need to be
+ // for the later 'full shape' creation
+ vertexShape.setFrame(x, y, shapeSize.width, shapeSize.height);
+ layeredPane.add(vertexImageLabel, VERTEX_SHAPE_LAYER);
+ }
+
+ private void addNameLabel() {
+ Border border = createDebugBorder(new LineBorder(Palette.GREEN, 1));
+ nameLabel.setBorder(border);
+
+ Rectangle parentBounds = vertexImageLabel.getBounds();
+ Dimension size = nameLabel.getPreferredSize();
+
+ int x = (parentBounds.x + (parentBounds.width / 2)) - (size.width / 2);
+ int y = parentBounds.y + parentBounds.height + GAP;
+ nameLabel.setBounds(x, y, size.width, size.height);
+ layeredPane.add(nameLabel, LABEL_LAYER);
+
+ }
+
+ private void addToggleButtons() {
+
+ // hide the button background
+ toggleInsButton.setBackground(Palette.NO_COLOR);
+ toggleOutsButton.setBackground(Palette.NO_COLOR);
+
+ // This is needed for Flat Dark theme to work correctly, due to the fact that it wants to
+ // paint its parent background when the button is opaque. The parent background will get
+ // painted over any items that lie between the button and the parent.
+ toggleInsButton.setOpaque(false);
+ toggleOutsButton.setOpaque(false);
+
+ Rectangle parentBounds = vertexImageLabel.getBounds();
+ Dimension size = toggleInsButton.getPreferredSize();
+
+ // upper toggle; upper-left
+ int x = parentBounds.x - (size.width / 3);
+ int y = 0;
+ toggleInsButton.setBounds(x, y, size.width, size.height);
+ layeredPane.add(toggleInsButton, TOGGLE_BUTTON_LAYER);
+
+ // lower toggle; lower-left, lined-up with the vertex shape
+ size = toggleOutsButton.getPreferredSize();
+ Dimension vertexSize = parentBounds.getSize();
+ y = vertexSize.height - size.height;
+ toggleOutsButton.setBounds(x, y, size.width, size.height);
+ layeredPane.add(toggleOutsButton, TOGGLE_BUTTON_LAYER);
+ }
+
+ public String getName() {
+ return namespace.getName();
+ }
+
+ public Namespace getNamespace() {
+ return namespace;
+ }
+
+ public GraphCutLevel getLevel() {
+ return level;
+ }
+
+ public int getDegree() {
+ return level.getRow();
+ }
+
+ public FcgDirection getDirection() {
+ return level.getDirection();
+ }
+
+ public JButton getIncomingToggleButton() {
+ return toggleInsButton;
+ }
+
+ public JButton getOutgoingToggleButton() {
+ return toggleOutsButton;
+ }
+
+ public void setIncomingExpanded(boolean setExpanded) {
+ validateIncomingExpandedState(setExpanded);
+
+ this.incomingExpanded = setExpanded;
+ toggleInsButton.setIcon(setExpanded ? COLLAPSE_ICON : EXPAND_ICON);
+ String hideShow = setExpanded ? "hide" : "show";
+ toggleInsButton.setToolTipText("Click to " + hideShow + " incoming edges");
+ }
+
+ private void validateOutgoingExpandedState(boolean isExpanding) {
+ if (isExpanding) {
+ if (!canExpandOutgoingReferences()) {
+ throw new IllegalStateException("Vertex cannot be expanded: " + this);
+ }
+ return;
+ }
+ // collapsing
+ if (!isOutgoingExpanded()) {
+ throw new IllegalStateException("Vertex cannot be collapsed: " + this);
+ }
+ }
+
+ private void validateIncomingExpandedState(boolean expanding) {
+
+ if (expanding) {
+ if (!canExpandIncomingReferences()) {
+ throw new IllegalStateException("Vertex cannot be expanded: " + this);
+ }
+ return;
+ }
+
+ // collapsing
+ if (!isIncomingExpanded()) {
+ throw new IllegalStateException("Vertex cannot be collapsed: " + this);
+ }
+ }
+
+ /**
+ * Returns true if this vertex is showing all edges in the incoming direction
+ *
+ * @return true if this vertex is showing all edges in the incoming direction
+ */
+ public boolean isIncomingExpanded() {
+ return incomingExpanded;
+ }
+
+ /**
+ * Sets to true if this vertex is showing all edges in the outgoing direction
+ *
+ * @param setExpanded true if this vertex is showing all edges in the outgoing direction
+ */
+ public void setOutgoingExpanded(boolean setExpanded) {
+
+ validateOutgoingExpandedState(setExpanded);
+
+ this.outgoingExpanded = setExpanded;
+ toggleOutsButton.setIcon(setExpanded ? COLLAPSE_ICON : EXPAND_ICON);
+ String hideShow = setExpanded ? "hide" : "show";
+ toggleInsButton.setToolTipText("Click to " + hideShow + " outgoing edges");
+ }
+
+
+ /**
+ * Returns true if this vertex is showing all edges in the outgoing direction
+ *
+ * @return true if this vertex is showing all edges in the outgoing direction
+ */
+ public boolean isOutgoingExpanded() {
+ return outgoingExpanded;
+ }
+
+ /**
+ * Returns whether this vertex is fully expanded in its current direction
+ *
+ * @return whether this vertex is fully expanded in its current direction
+ */
+ public boolean isExpanded() {
+ FcgDirection direction = level.getDirection();
+ if (direction.isSource()) {
+ return isIncomingExpanded() && isOutgoingExpanded();
+ }
+ if (direction.isIn()) {
+ return isIncomingExpanded();
+ }
+ return isOutgoingExpanded();
+ }
+
+ /**
+ * Sets whether this vertex has too many incoming references, where too many is subjectively
+ * defined by this class. Too many nodes in the display would ruin rendering and general
+ * usability.
+ *
+ * @param tooMany if there are too many references
+ */
+ public void setTooManyIncomingReferences(boolean tooMany) {
+ this.tooManyIncomingReferences = tooMany;
+ toggleInsButton.setIcon(NOT_ALLOWED_ICON);
+ toggleInsButton.setToolTipText("Too many incoming references to show");
+ buildUi();
+ }
+
+ /**
+ * Sets whether this vertex has too many outgoing references, where too many is subjectively
+ * defined by this class. Too many nodes in the display would ruin rendering and general
+ * usability.
+ *
+ * @param tooMany if there are too many references
+ */
+ public void setTooManyOutgoingReferences(boolean tooMany) {
+ this.tooManyOutgoingReferences = tooMany;
+ toggleOutsButton.setIcon(NOT_ALLOWED_ICON);
+ toggleOutsButton.setToolTipText("Too many outgoing references to show");
+ buildUi();
+ }
+
+ /**
+ * Returns whether this vertex has too many incoming references, where too many is subjectively
+ * defined by this class. Too many nodes in the display would ruin rendering and general
+ * usability.
+ *
+ * @return true if there are too many references
+ */
+ public boolean hasTooManyIncomingReferences() {
+ return tooManyIncomingReferences;
+ }
+
+ /**
+ * Returns whether this vertex has too many outgoing references, where too many is subjectively
+ * defined by this class. Too many nodes in the display would ruin rendering and general
+ * usability.
+ *
+ * @return true if there are too many references
+ */
+ public boolean hasTooManyOutgoingReferences() {
+ return tooManyOutgoingReferences;
+ }
+
+ /**
+ * Returns true if this vertex can expand itself in its current direction, or in either
+ * direction if this is a source vertex
+ *
+ * @return true if this vertex can be expanded
+ */
+ public boolean canExpand() {
+ FcgDirection direction = level.getDirection();
+ if (direction.isSource()) {
+ return canExpandIncomingReferences() || canExpandOutgoingReferences();
+ }
+
+ if (direction.isIn()) {
+ return canExpandIncomingReferences();
+ }
+
+ return canExpandOutgoingReferences();
+ }
+
+
+ public boolean canExpandIncomingReferences() {
+ return hasIncomingReferences && !tooManyIncomingReferences && !incomingExpanded;
+ }
+
+ public boolean canExpandOutgoingReferences() {
+ return hasOutgoingReferences && !tooManyOutgoingReferences && !outgoingExpanded;
+ }
+
+ /**
+ * Sets whether this vertex has any incoming references
+ *
+ * @param hasIncoming true if this vertex has any incoming references
+ */
+ public void setHasIncomingReferences(boolean hasIncoming) {
+ this.hasIncomingReferences = hasIncoming;
+ }
+
+ /**
+ * Sets whether this vertex has any outgoing references
+ *
+ * @param hasOutgoing true if this vertex has any outgoing references
+ */
+
+ public void setHasOutgoingReferences(boolean hasOutgoing) {
+ this.hasOutgoingReferences = hasOutgoing;
+ }
+
+ @Override
+ public void setHovered(boolean hovered) {
+ super.setHovered(hovered);
+
+ setTogglesVisible(hovered);
+ }
+
+ private void setTogglesVisible(boolean visible) {
+
+ boolean isIn = isInDirection();
+ boolean turnOn = isIn && hasIncomingReferences && visible;
+ toggleInsButton.setVisible(turnOn);
+
+ boolean isOut = isOutDirection();
+ turnOn = isOut && hasOutgoingReferences && visible;
+ toggleOutsButton.setVisible(turnOn);
+ }
+
+
+ @Override
+ public JComponent getComponent() {
+ return layeredPane;
+ }
+
+ @Override
+ public Shape getCompactShape() {
+ return compactShape;
+ }
+
+ @Override
+ public Shape getFullShape() {
+ return fullShape;
+ }
+
+ @Override
+ public String toString() {
+ return getName();// + " @ " + level; // + " (" + System.identityHashCode(this) + ')';
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(namespace);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+
+ if(getClass() != obj.getClass()) {
+ return false;
+ }
+
+ GraphCutVertex other = (GraphCutVertex) obj;
+ return Objects.equals(namespace, other.namespace);
+ }
+
+ @Override
+ public void dispose() {
+ // nothing to do
+ }
+
+ public long getID() {
+ return getNamespace().getID();
+ }
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/codecut-gui/src/main/java/graphcut/GraphCutVertexPaintTransformer.java b/codecut-gui/src/main/java/graphcut/GraphCutVertexPaintTransformer.java
new file mode 100644
index 0000000..45b1c93
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/GraphCutVertexPaintTransformer.java
@@ -0,0 +1,56 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+/*
+ * Heavily Borrows from Ghidra file /Features Graph FunctionCalls/src/main/java/functioncalls/graph/renderer/FcgVertexPaintTransformer.java
+ */
+
+
+package graphcut;
+
+import com.google.common.base.Function;
+import java.awt.Color;
+import java.awt.Paint;
+
+import ghidra.util.ColorUtils;
+/**
+ * A class that takes a GraphCutVertex and determines which color to paint it with
+ */
+public class GraphCutVertexPaintTransformer implements Function {
+ private Color color;
+
+ public GraphCutVertexPaintTransformer(Color color) {
+ this.color = color;
+ }
+
+ @Override
+ public Paint apply(GraphCutVertex v) {
+ return color;
+ }
+}
diff --git a/codecut-gui/src/main/java/graphcut/GraphCutView.java b/codecut-gui/src/main/java/graphcut/GraphCutView.java
new file mode 100644
index 0000000..b5d57c4
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/GraphCutView.java
@@ -0,0 +1,63 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+/*
+ * Heavily Borrows from Ghidra file /Features Graph FunctionCalls/src/main/java/functioncalls/graph/view/FcgView.java
+ */
+
+
+package graphcut;
+
+import ghidra.graph.viewer.VisualGraphView;
+import ghidra.graph.viewer.options.VisualGraphOptions;
+
+public class GraphCutView extends VisualGraphView {
+ private VisualGraphOptions options;
+ public GraphCutView(VisualGraphOptions options) {
+ this.options = options;
+ }
+
+ @Override
+ protected void installGraphViewer() {
+
+ GraphCutComponent component = createGraphComponent();
+ component.setGraphOptions(options);
+ setGraphComponent(component);
+ }
+
+ private GraphCutComponent createGraphComponent() {
+ GraphCutComponent component = new GraphCutComponent(getVisualGraph());
+ return component;
+ }
+
+ @Override
+ public GraphCutComponent getGraphComponent() {
+ return (GraphCutComponent) super.getGraphComponent();
+ }
+}
diff --git a/codecut-gui/src/main/java/graphcut/NamespaceEdge.java b/codecut-gui/src/main/java/graphcut/NamespaceEdge.java
new file mode 100644
index 0000000..19ac662
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/NamespaceEdge.java
@@ -0,0 +1,92 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+
+package graphcut;
+
+import java.util.Objects;
+import ghidra.program.model.symbol.Namespace;
+
+
+public class NamespaceEdge {
+
+ private Namespace start;
+ private Namespace end;
+
+ NamespaceEdge(Namespace start, Namespace end){
+ this.start = start;
+ this.end = end;
+ }
+
+ Namespace getStart() {
+ return start;
+ }
+
+ Namespace getEnd() {
+ return end;
+ }
+
+ @Override
+ public String toString() {
+ return "[" + start + ", " + end + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime*result + ((end == null) ? 0 : end.hashCode());
+ result = prime * result + ((start == null) ? 0 : start.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+
+ NamespaceEdge other = (NamespaceEdge) obj;
+ if(!Objects.equals(end, other.end)) {
+ return false;
+ }
+
+ if(!Objects.equals(start, other.start)) {
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/codecut-gui/src/main/java/graphcut/NamespaceEdgeCache.java b/codecut-gui/src/main/java/graphcut/NamespaceEdgeCache.java
new file mode 100644
index 0000000..3f86fe3
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/NamespaceEdgeCache.java
@@ -0,0 +1,65 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+
+package graphcut;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.collections4.map.LazyMap;
+
+import ghidra.program.model.symbol.Namespace;
+
+/**
+ * A class to cache known namespace edges
+ */
+public class NamespaceEdgeCache {
+
+ //contains all known edges, even those not shown in the graph
+ private Map> allEdgesByNamespace =
+ LazyMap.lazyMap(new HashMap<>(), () -> new HashSet<>());
+
+ // track processed namespaces
+ private Set tracked = new HashSet<>();
+
+ public Set get(Namespace ns){
+ return allEdgesByNamespace.get(ns);
+ }
+
+ public boolean isTracked(Namespace ns) {
+ return tracked.contains(ns);
+ }
+
+ public void setTracked(Namespace ns) {
+ tracked.add(ns);
+ }
+}
diff --git a/codecut-gui/src/main/java/graphcut/ValidGraphCutData.java b/codecut-gui/src/main/java/graphcut/ValidGraphCutData.java
new file mode 100644
index 0000000..17f5c4f
--- /dev/null
+++ b/codecut-gui/src/main/java/graphcut/ValidGraphCutData.java
@@ -0,0 +1,101 @@
+/* ###
+ * © 2021 The Johns Hopkins University Applied Physics Laboratory LLC (JHU/APL).
+ * All Rights Reserved.
+ *
+ * This material may be only be used, modified, or reproduced by or for the U.S.
+ * Government pursuant to the license rights granted under the clauses at
+ * DFARS 252.227-7013/7014 or FAR 52.227-14. For any other permission, please
+ * contact the Office of Technology Transfer at JHU/APL.
+ *
+ * NO WARRANTY, NO LIABILITY. THIS MATERIAL IS PROVIDED “AS IS.” JHU/APL MAKES
+ * NO REPRESENTATION OR WARRANTY WITH RESPECT TO THE PERFORMANCE OF THE MATERIALS,
+ * INCLUDING THEIR SAFETY, EFFECTIVENESS, OR COMMERCIAL VIABILITY, AND DISCLAIMS
+ * ALL WARRANTIES IN THE MATERIAL, WHETHER EXPRESS OR IMPLIED, INCLUDING
+ * (BUT NOT LIMITED TO) ANY AND ALL IMPLIED WARRANTIES OF PERFORMANCE,
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT OF
+ * INTELLECTUAL PROPERTY OR OTHER THIRD PARTY RIGHTS. ANY USER OF THE MATERIAL
+ * ASSUMES THE ENTIRE RISK AND LIABILITY FOR USING THE MATERIAL. IN NO EVENT SHALL
+ * JHU/APL BE LIABLE TO ANY USER OF THE MATERIAL FOR ANY ACTUAL, INDIRECT,
+ * CONSEQUENTIAL, SPECIAL OR OTHER DAMAGES ARISING FROM THE USE OF, OR INABILITY TO
+ * USE, THE MATERIAL, INCLUDING, BUT NOT LIMITED TO, ANY DAMAGES FOR LOST PROFITS.
+ *
+ * HAVE A NICE DAY.
+ */
+
+/* This material is based upon work supported by the Defense Advanced Research
+ * Projects Agency (DARPA) and Naval Information Warfare Center Pacific (NIWC Pacific)
+ * under Contract Number N66001-20-C-4024.
+*/
+
+/*
+ * Borrows from /Features Graph FunctionCalls/src/main/java/functioncalls/plugin/ValidFcgData.java
+ */
+
+
+package graphcut;
+
+import java.util.Objects;
+
+import ghidra.graph.viewer.GraphPerspectiveInfo;
+import ghidra.program.model.symbol.Namespace;
+
+public class ValidGraphCutData implements GraphCutData {
+
+ private Namespace namespace;
+ private GraphCutGraph graph;
+ private GraphPerspectiveInfo perspectiveInfo;
+
+ private NamespaceEdgeCache allEdgesByNamespace = new NamespaceEdgeCache();
+
+ ValidGraphCutData(Namespace namespace, GraphCutGraph graph){
+ this.namespace = Objects.requireNonNull(namespace);
+ this.graph = Objects.requireNonNull(graph);
+ }
+
+ @Override
+ public Namespace getNamespace() {
+ return namespace;
+ }
+
+ @Override
+ public boolean isNamespace(Namespace ns) {
+ return namespace.equals(ns);
+ }
+
+ @Override
+ public GraphCutGraph getGraph() {
+ return graph;
+ }
+
+ @Override
+ public NamespaceEdgeCache getNamespaceEdgeCache() {
+ return allEdgesByNamespace;
+ }
+
+ @Override
+ public boolean hasResults() {
+ return true;
+ }
+
+ @Override
+ public boolean isInitialized() {
+ return !graph.isEmpty();
+ }
+
+ @Override
+ public void dispose() {
+ graph.dispose();
+ }
+
+ @Override
+ public GraphPerspectiveInfo getGraphPerspective(){
+ return perspectiveInfo;
+ }
+
+ @Override
+ public void setGraphPerspective( GraphPerspectiveInfo info) {
+ this.perspectiveInfo = info;
+ }
+
+
+}