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 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 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 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 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; + } + + +}