functions = new HashSet<>();
+ if (selection == null || selection.isEmpty()) {
+ functions.add(getFunctionForLocation());
+ }
+ else {
+ FunctionManager functionManager = program.getFunctionManager();
+ FunctionIterator functionIter = functionManager.getFunctions(selection, true);
+ for (Function selectedFunction : functionIter) {
+ functions.add(selectedFunction);
+ }
+ }
+ return functions;
+ }
+
+ private Function getFunctionForLocation() {
+ if (!(location instanceof FunctionLocation functionLocation)) {
+ return null;
+ }
+ Address functionAddress = functionLocation.getFunctionAddress();
+ return program.getFunctionManager().getFunctionAt(functionAddress);
+ }
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparison.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparison.java
deleted file mode 100644
index dfb377a62b..0000000000
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparison.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/* ###
- * IP: GHIDRA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package ghidra.app.plugin.core.functioncompare;
-
-import java.util.*;
-
-import ghidra.app.services.FunctionComparisonModel;
-import ghidra.program.model.listing.Function;
-
-/**
- * Defines the structure of a function comparison. The relationship is strictly
- * one-to-many; a single source function may be associated with one
- * or more target functions.
- *
- * This is the basic unit for the
- * {@link FunctionComparisonModel function comparison data model}
- */
-public class FunctionComparison implements Comparable {
-
- private Function source;
-
- /** Use a tree so functions are always kept in sorted order */
- private FunctionComparator functionComparator = new FunctionComparator();
- private Set targets = new TreeSet<>(functionComparator);
-
- /**
- * Returns the source function
- *
- * @return the source function
- */
- public Function getSource() {
- return source;
- }
-
- /**
- * Returns the set of targets, in sorted order by function name
- *
- * @return the set of targets
- */
- public Set getTargets() {
- return targets;
- }
-
- /**
- * Sets a given function as the comparison source
- *
- * @param function the source function
- */
- public void setSource(Function function) {
- source = function;
- }
-
- /**
- * Adds a target function to the comparison
- *
- * @param function the function to add to the target list
- */
- public void addTarget(Function function) {
- targets.add(function);
- }
-
- /**
- * Adds a set of functions to the target list
- *
- * @param functions the functions to add
- */
- public void addTargets(Set functions) {
- targets.addAll(functions);
- }
-
- /**
- * Removes the given function from the target list.
- *
- * Note that the target list is a {@link Set}, so there will only ever
- * be at most one entry that matches the given function
- *
- * @param function the function to remove
- */
- public void removeTarget(Function function) {
- targets.remove(function);
- }
-
- /**
- * Removes all targets from the comparison
- */
- public void clearTargets() {
- targets.clear();
- }
-
- /**
- * Ensures that FunctionComparison objects are always ordered according
- * to the source program path, name and address
- */
- @Override
- public int compareTo(FunctionComparison o) {
- return functionComparator.compare(source, o.source);
- }
-
- /**
- * Forces an ordering on {@link Function} objects by program path, name and
- * address. This is to ensure that the list of targets is kept in sorted
- * order at all times.
- */
- class FunctionComparator implements Comparator {
-
- @Override
- public int compare(Function o1, Function o2) {
- if (o2 == null) {
- return 1;
- }
-
- String o1Path = o1.getProgram().getDomainFile().getPathname();
- String o2Path = o2.getProgram().getDomainFile().getPathname();
-
- String o1Name = o1.getName();
- String o2Name = o2.getName();
-
- if (o1Path.equals(o2Path)) {
- if (o1Name.equals(o2Name)) {
- return o1.getEntryPoint().compareTo(o2.getEntryPoint());
- }
- return o1Name.compareTo(o2Name);
- }
-
- return o1Path.compareTo(o2Path);
- }
- }
-}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonModelListener.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonModelListener.java
index 40bdea601b..6accb19dc9 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonModelListener.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonModelListener.java
@@ -15,9 +15,9 @@
*/
package ghidra.app.plugin.core.functioncompare;
-import java.util.List;
-
import ghidra.app.services.FunctionComparisonModel;
+import ghidra.program.model.listing.Function;
+import ghidra.util.datastruct.Duo.Side;
/**
* Allows subscribers to register for {@link FunctionComparisonModel function
@@ -26,9 +26,15 @@ import ghidra.app.services.FunctionComparisonModel;
public interface FunctionComparisonModelListener {
/**
- * Invoked when the comparison model has changed
- *
- * @param model the current state of the model
+ * Notification that the selected function changed on one side or the other.
+ * @param side the side whose selected function changed
+ * @param function the new selected function for the given side
*/
- public void modelChanged(List model);
+ public void activeFunctionChanged(Side side, Function function);
+
+ /**
+ * Notification that the set of functions on at least one side changed. The selected functions
+ * on either side may have also changed.
+ */
+ public void modelDataChanged();
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPanel.java
index f97615bc02..8011e84c75 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPanel.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPanel.java
@@ -37,7 +37,6 @@ import generic.theme.GIcon;
import ghidra.app.util.viewer.listingpanel.ListingCodeComparisonPanel;
import ghidra.app.util.viewer.util.*;
import ghidra.framework.options.SaveState;
-import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSetView;
@@ -77,28 +76,19 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
private JTabbedPane tabbedPane;
private Map tabNameToComponentMap;
- protected PluginTool tool;
- protected ComponentProviderAdapter provider;
private List codeComparisonPanels;
private ToggleScrollLockAction toggleScrollLockAction;
private boolean syncScrolling = false;
private Duo comparisonData = new Duo();
- /**
- * Constructor
- *
- * @param provider the GUI provider that includes this panel
- * @param tool the tool containing this panel
- */
- public FunctionComparisonPanel(ComponentProviderAdapter provider, PluginTool tool) {
- this.provider = provider;
- this.tool = tool;
+ public FunctionComparisonPanel(PluginTool tool, String owner) {
this.comparisonData = new Duo<>(EMPTY, EMPTY);
- this.codeComparisonPanels = getCodeComparisonPanels();
+
+ codeComparisonPanels = getCodeComparisonPanels(tool, owner);
tabNameToComponentMap = new HashMap<>();
createMainPanel();
- createActions();
+ createActions(owner);
setScrollingSyncState(true);
help.registerHelp(this, new HelpLocation(HELP_TOPIC, "Function Comparison"));
}
@@ -223,13 +213,6 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
tabChanged();
}
- /**
- * Refreshes the contents of the panel
- */
- public void reload() {
- // do nothing by default; override in subs if necessary
- }
-
/**
* Set the current tabbed panel to be the component with the given name
*
@@ -273,7 +256,6 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
* Remove all views in the tabbed pane
*/
public void dispose() {
- tool.removeComponentProvider(provider);
tabbedPane.removeAll();
setVisible(false);
@@ -514,16 +496,16 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
/**
* Creates the actions available for this panel
*/
- private void createActions() {
- toggleScrollLockAction = new ToggleScrollLockAction();
+ private void createActions(String owner) {
+ toggleScrollLockAction = new ToggleScrollLockAction(owner);
}
/**
* Action that sets the scrolling state of the comparison panels
*/
private class ToggleScrollLockAction extends ToggleDockingAction {
- ToggleScrollLockAction() {
- super("Synchronize Scrolling of Dual View", provider.getName());
+ ToggleScrollLockAction(String owner) {
+ super("Synchronize Scrolling of Dual View", owner);
setDescription("Lock/Unlock Synchronized Scrolling of Dual View");
setToolBarData(new ToolBarData(UNSYNC_SCROLLING_ICON, SCROLLING_GROUP));
setEnabled(true);
@@ -550,25 +532,27 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
*
* @return the CodeComparisonPanels which are extension points
*/
- private List getCodeComparisonPanels() {
+ private List getCodeComparisonPanels(PluginTool tool, String owner) {
if (codeComparisonPanels == null) {
- codeComparisonPanels = createAllPossibleCodeComparisonPanels();
+ codeComparisonPanels = createAllPossibleCodeComparisonPanels(tool, owner);
codeComparisonPanels.sort((p1, p2) -> p1.getName().compareTo(p2.getName()));
}
return codeComparisonPanels;
}
- @SuppressWarnings({ "rawtypes", "unchecked" })
- private ArrayList createAllPossibleCodeComparisonPanels() {
- ArrayList instances =
- new ArrayList<>();
+ private List createAllPossibleCodeComparisonPanels(PluginTool tool,
+ String owner) {
+
+ List instances = new ArrayList<>();
+
List> classes =
ClassSearcher.getClasses(CodeComparisonPanel.class);
+
for (Class extends CodeComparisonPanel> panelClass : classes) {
try {
Constructor extends CodeComparisonPanel> constructor =
panelClass.getConstructor(String.class, PluginTool.class);
- CodeComparisonPanel panel = constructor.newInstance(provider.getName(), tool);
+ CodeComparisonPanel panel = constructor.newInstance(owner, tool);
instances.add(panel);
}
catch (NoSuchMethodException | SecurityException | InstantiationException
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPlugin.java
index 15df0fc755..87d794f1d2 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPlugin.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPlugin.java
@@ -15,16 +15,16 @@
*/
package ghidra.app.plugin.core.functioncompare;
-import java.util.Set;
-import java.util.function.Supplier;
+import java.util.*;
+import java.util.function.Consumer;
+import docking.action.builder.ActionBuilder;
import ghidra.app.CorePluginPackage;
+import ghidra.app.context.FunctionSupplierContext;
import ghidra.app.events.*;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin;
-import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsAction;
-import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsFromListingAction;
-import ghidra.app.services.FunctionComparisonService;
+import ghidra.app.services.*;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginInfo;
import ghidra.framework.plugintool.PluginTool;
@@ -33,7 +33,9 @@ import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramChangeRecord;
import ghidra.program.util.ProgramEvent;
+import ghidra.util.HelpLocation;
import ghidra.util.Swing;
+import utility.function.Callback;
/**
* Allows users to create function comparisons that are displayed
@@ -58,31 +60,18 @@ import ghidra.util.Swing;
public class FunctionComparisonPlugin extends ProgramPlugin
implements DomainObjectListener, FunctionComparisonService {
- static final String MENU_PULLRIGHT = "CompareFunctions";
- static final String POPUP_MENU_GROUP = "CompareFunction";
+ // Keep a stack of recently added providers so that the "add to comparison" service methods
+ // can easily add to the last created provider.
+ private Deque providers = new ArrayDeque<>();
- private FunctionComparisonProviderManager functionComparisonManager;
-
- /**
- * Constructor
- *
- * @param tool the tool that owns this plugin
- */
public FunctionComparisonPlugin(PluginTool tool) {
super(tool);
- functionComparisonManager = new FunctionComparisonProviderManager(this);
- }
-
- @Override
- protected void init() {
- CompareFunctionsAction compareFunctionsAction =
- new CompareFunctionsFromListingAction(tool, getName());
- tool.addAction(compareFunctionsAction);
+ createActions();
}
@Override
public void dispose() {
- functionComparisonManager.dispose();
+ foreEachProvider(p -> p.closeComponent());
}
@Override
@@ -92,8 +81,8 @@ public class FunctionComparisonPlugin extends ProgramPlugin
@Override
protected void programClosed(Program program) {
- functionComparisonManager.closeProviders(program);
program.removeListener(this);
+ foreEachProvider(p -> p.programClosed(program));
}
/**
@@ -111,7 +100,7 @@ public class FunctionComparisonPlugin extends ProgramPlugin
EventType eventType = doRecord.getEventType();
if (eventType == DomainObjectEvent.RESTORED) {
- functionComparisonManager.domainObjectRestored((Program) ev.getSource());
+ domainObjectRestored((Program) ev.getSource());
}
else if (eventType == ProgramEvent.FUNCTION_REMOVED) {
ProgramChangeRecord rec = (ProgramChangeRecord) ev.getChangeRecord(i);
@@ -123,71 +112,113 @@ public class FunctionComparisonPlugin extends ProgramPlugin
}
}
- private void runOnSwingNonBlocking(Runnable r) {
- Swing.runIfSwingOrRunLater(r);
- }
-
- private FunctionComparisonProvider getFromSwingBlocking(
- Supplier comparer) {
-
- if (Swing.isSwingThread()) {
- return comparer.get();
- }
-
- return Swing.runNow(comparer);
- }
-
void providerClosed(FunctionComparisonProvider provider) {
- functionComparisonManager.providerClosed(provider);
+ providers.remove(provider);
}
+
+ void removeFunction(Function function) {
+ Swing.runIfSwingOrRunLater(() -> doRemoveFunction(function));
+ }
+
+ private void foreEachProvider(Consumer c) {
+ // copy needed because this may cause callbacks to remove a provider from our list
+ List localCopy = new ArrayList<>(providers);
+ localCopy.forEach(c);
+
+ }
+
+ private void domainObjectRestored(Program program) {
+ foreEachProvider(p -> p.programRestored(program));
+ }
+
+ private void createActions() {
+ new ActionBuilder("Compare Functions", getName())
+ .description("Create Function Comparison")
+ .popupMenuPath("Compare Function(s)")
+ .helpLocation(new HelpLocation("FunctionComparison", "Function_Comparison"))
+ .popupMenuGroup("Functions", "Z1")
+ .withContext(FunctionSupplierContext.class)
+ .enabledWhen(c -> c.hasFunctions())
+ .onAction(c -> createComparison(c.getFunctions()))
+ .buildAndInstall(tool);
+
+ new ActionBuilder("Add To Last Function Comparison", getName())
+ .description("Add the selected function(s) to the last Function Comparison window")
+ .popupMenuPath("Add To Last Comparison")
+ .helpLocation(new HelpLocation("FunctionComparison", "Function_Comparison_Add_To"))
+ .popupMenuGroup("Functions", "Z2")
+ .withContext(FunctionSupplierContext.class)
+ .enabledWhen(c -> c.hasFunctions())
+ .onAction(c -> addToComparison(c.getFunctions()))
+ .buildAndInstall(tool);
+ }
+
+ private void doRemoveFunction(Function function) {
+ foreEachProvider(p -> p.getModel().removeFunction(function));
+ }
+
+ private FunctionComparisonProvider createProvider(FunctionComparisonModel model) {
+ return createProvider(model, null);
+ }
+
+ private FunctionComparisonProvider createProvider(FunctionComparisonModel model,
+ Callback closeListener) {
+ FunctionComparisonProvider provider =
+ new FunctionComparisonProvider(this, model, closeListener);
+
+ // insert at the top so the last created provider is first when searching for a provider
+ providers.addFirst(provider);
+ return provider;
+ }
+
+ private FunctionComparisonProvider findLastDefaultProviderModel() {
+ for (FunctionComparisonProvider provider : providers) {
+ if (provider.getModel() instanceof DefaultFunctionComparisonModel) {
+ return provider;
+ }
+ }
+ return null;
+ }
+
//==================================================================================================
// Service Methods
//==================================================================================================
-
@Override
- public void removeFunction(Function function) {
- runOnSwingNonBlocking(() -> functionComparisonManager.removeFunction(function));
+ public void createComparison(Collection functions) {
+ if (functions.isEmpty()) {
+ return;
+ }
+ DefaultFunctionComparisonModel model = new DefaultFunctionComparisonModel(functions);
+ Swing.runLater(() -> createProvider(model));
}
@Override
- public void removeFunction(Function function, FunctionComparisonProvider provider) {
- runOnSwingNonBlocking(() -> functionComparisonManager.removeFunction(function, provider));
+ public void createComparison(Function left, Function right) {
+ DefaultFunctionComparisonModel model = new DefaultFunctionComparisonModel(left, right);
+ Swing.runLater(() -> createProvider(model));
}
@Override
- public FunctionComparisonProvider createFunctionComparisonProvider() {
- return getFromSwingBlocking(() -> functionComparisonManager.createProvider());
+ public void addToComparison(Collection functions) {
+ FunctionComparisonProvider lastProvider = findLastDefaultProviderModel();
+ if (lastProvider == null) {
+ createComparison(functions);
+ }
+ else {
+ DefaultFunctionComparisonModel model =
+ (DefaultFunctionComparisonModel) lastProvider.getModel();
+ Swing.runLater(() -> model.addFunctions(functions));
+ }
}
@Override
- public FunctionComparisonProvider compareFunctions(Function source, Function target) {
- return getFromSwingBlocking(
- () -> functionComparisonManager.compareFunctions(source, target));
+ public void addToComparison(Function function) {
+ addToComparison(Arrays.asList(function));
}
@Override
- public FunctionComparisonProvider compareFunctions(Set functions) {
- return getFromSwingBlocking(() -> functionComparisonManager.compareFunctions(functions));
- }
-
- @Override
- public FunctionComparisonProvider compareFunctions(Set sourceFunctions,
- Set destinationFunctions) {
- return getFromSwingBlocking(() -> functionComparisonManager
- .compareFunctions(sourceFunctions, destinationFunctions));
- }
-
- @Override
- public void compareFunctions(Set functions, FunctionComparisonProvider provider) {
- runOnSwingNonBlocking(
- () -> functionComparisonManager.compareFunctions(functions, provider));
- }
-
- @Override
- public void compareFunctions(Function source, Function target,
- FunctionComparisonProvider provider) {
- runOnSwingNonBlocking(
- () -> functionComparisonManager.compareFunctions(source, target, provider));
+ public void createCustomComparison(FunctionComparisonModel model, Callback closeListener) {
+ Swing.runLater(() -> createProvider(model, closeListener));
}
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProvider.java
index 11c5087db0..b6c01350c6 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProvider.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProvider.java
@@ -19,14 +19,21 @@ import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.event.MouseEvent;
import java.util.*;
+import java.util.stream.Collectors;
+
+import javax.swing.Icon;
import docking.ActionContext;
import docking.Tool;
-import docking.action.DockingAction;
-import docking.action.DockingActionIf;
+import docking.action.*;
+import docking.action.builder.ActionBuilder;
+import docking.action.builder.ToggleActionBuilder;
import docking.actions.PopupActionProvider;
-import ghidra.app.services.FunctionComparisonModel;
-import ghidra.app.services.FunctionComparisonService;
+import docking.widgets.dialogs.TableSelectionDialog;
+import generic.theme.GIcon;
+import ghidra.app.plugin.core.functionwindow.FunctionRowObject;
+import ghidra.app.plugin.core.functionwindow.FunctionTableModel;
+import ghidra.app.services.*;
import ghidra.app.util.viewer.listingpanel.ListingCodeComparisonPanel;
import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.app.util.viewer.util.CodeComparisonPanel;
@@ -34,7 +41,10 @@ import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
-import ghidra.util.HelpLocation;
+import ghidra.util.*;
+import ghidra.util.datastruct.Duo.Side;
+import resources.Icons;
+import util.CollectionUtils;
import utility.function.Callback;
/**
@@ -44,62 +54,55 @@ import utility.function.Callback;
*/
public class FunctionComparisonProvider extends ComponentProviderAdapter
implements PopupActionProvider, FunctionComparisonModelListener {
+ private static final String ADD_COMPARISON_GROUP = "A9_AddToComparison";
+ private static final String NAV_GROUP = "A9 FunctionNavigate";
+ private static final String REMOVE_FUNCTIONS_GROUP = "A9_RemoveFunctions";
- protected static final String HELP_TOPIC = "FunctionComparison";
- protected FunctionComparisonPanel functionComparisonPanel;
- protected FunctionComparisonPlugin plugin;
+ private static final Icon ADD_TO_COMPARISON_ICON =
+ new GIcon("icon.plugin.functioncompare.open.function.table");
+ private static final Icon NAV_FUNCTION_ICON = Icons.NAVIGATE_ON_INCOMING_EVENT_ICON;
+ private static final Icon NEXT_FUNCTION_ICON =
+ new GIcon("icon.plugin.functioncompare.function.next");
+ private static final Icon PREVIOUS_FUNCTION_ICON =
+ new GIcon("icon.plugin.functioncompare.function.previous");
+ private static final Icon REMOVE_FUNCTION_ICON =
+ new GIcon("icon.plugin.functioncompare.function.remove");
+
+ private static final String HELP_TOPIC = "FunctionComparison";
+
+ private FunctionComparisonPlugin plugin;
+ private FunctionComparisonModel model;
+ private MultiFunctionComparisonPanel functionComparisonPanel;
- /** Contains all the comparison data to be displayed by this provider */
- protected FunctionComparisonModel model;
private Callback closeListener = Callback.dummy();
+ private ToggleDockingAction navigateToAction;
- /**
- * Constructor
- *
- * @param plugin the active plugin
- * @param name the providers name; used to group similar providers into a tab within
- * the same window
- * @param owner the provider owner, usually a plugin name
- */
- public FunctionComparisonProvider(FunctionComparisonPlugin plugin, String name, String owner) {
- this(plugin, name, owner, null);
- }
-
- /**
- * Constructor
- *
- * @param plugin the active plugin
- * @param name the providers name; used to group similar providers into a tab within
- * the same window
- * @param owner the provider owner, usually a plugin name
- * @param contextType the type of context supported by this provider; may be null
- */
- public FunctionComparisonProvider(FunctionComparisonPlugin plugin, String name, String owner,
- Class> contextType) {
- super(plugin.getTool(), name, owner, contextType);
+ public FunctionComparisonProvider(FunctionComparisonPlugin plugin,
+ FunctionComparisonModel model, Callback closeListener) {
+ super(plugin.getTool(), "Function Comparison Provider", plugin.getName());
this.plugin = plugin;
- setTransient();
- model = new FunctionComparisonModel();
+ this.model = model;
+ this.closeListener = Callback.dummyIfNull(closeListener);
+
+ functionComparisonPanel = new MultiFunctionComparisonPanel(this, tool, model);
model.addFunctionComparisonModelListener(this);
- functionComparisonPanel = getComponent();
- initFunctionComparisonPanel();
+
+ setTabText(functionComparisonPanel.getDescription());
+ tool.addPopupActionProvider(this);
+ setHelpLocation(new HelpLocation(HELP_TOPIC, "Function Comparison"));
+
+ createActions();
+ addSpecificCodeComparisonActions();
+ setTransient();
+ addToTool();
+ setVisible(true);
}
@Override
public FunctionComparisonPanel getComponent() {
- if (functionComparisonPanel == null) {
- functionComparisonPanel = new FunctionComparisonPanel(this, tool);
- }
return functionComparisonPanel;
}
- @Override
- public void closeComponent() {
- super.closeComponent();
- closeListener.call();
- closeListener = Callback.dummy();
- }
-
@Override
public String toString() {
StringBuffer buff = new StringBuffer();
@@ -121,18 +124,28 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
}
@Override
- public void removeFromTool() {
- tool.removePopupActionProvider(this);
- super.removeFromTool();
- plugin.providerClosed(this);
+ public void modelDataChanged() {
+ updateTabAndTitle();
+ tool.contextChanged(this);
+
+ // The component will be disposed if all functions are gone. Do this later to prevent
+ // concurrent modification exception since we are in a listener callback.
+ Swing.runLater(this::closeIfEmpty);
}
@Override
- public void modelChanged(List data) {
- this.model.setComparisons(data);
- functionComparisonPanel.reload();
- setTabText(functionComparisonPanel.getDescription());
- closeIfEmpty();
+ public void activeFunctionChanged(Side side, Function function) {
+ updateTabAndTitle();
+ tool.contextChanged(this);
+ if (navigateToAction.isSelected()) {
+ goToFunction(function);
+ }
+ }
+
+ @Override
+ public void contextChanged() {
+ super.contextChanged();
+ maybeGoToActiveFunction();
}
@Override
@@ -157,15 +170,6 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
return model;
}
- /**
- * Replaces the comparison model with the one provided
- *
- * @param model the comparison model
- */
- public void setModel(FunctionComparisonModel model) {
- this.model = model;
- }
-
/**
* Removes any functions being displayed by this provider that are from
* the given program. If there are no functions left to display, the
@@ -232,30 +236,131 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
functionComparisonPanel.writeConfigState(getName(), saveState);
}
- /**
- * Perform initialization for this provider and its panel
- */
- protected void initFunctionComparisonPanel() {
- setTabText(functionComparisonPanel.getDescription());
- addSpecificCodeComparisonActions();
- tool.addPopupActionProvider(this);
- setHelpLocation(new HelpLocation(HELP_TOPIC, "Function Comparison"));
+ @Override
+ public void removeFromTool() {
+ tool.removePopupActionProvider(this);
+ super.removeFromTool();
+ dispose();
}
- /**
- * Returns true if the comparison panel is empty
- *
- * @return true if the panel is empty
- */
- boolean isEmpty() {
- return functionComparisonPanel.isEmpty();
+ private void updateTabAndTitle() {
+ String description = functionComparisonPanel.getDescription();
+ setTabText(description);
+ setTitle(description);
+
+ }
+
+ private void createActions() {
+ new ActionBuilder("Compare Next Function", plugin.getName())
+ .description("Compare the next function for the side with focus.")
+ .helpLocation(new HelpLocation(HELP_TOPIC, "Navigate Next"))
+ .keyBinding("control shift N")
+ .popupMenuPath("Compare Next Function")
+ .popupMenuGroup(NAV_GROUP)
+ .toolBarIcon(NEXT_FUNCTION_ICON)
+ .toolBarGroup(NAV_GROUP)
+ .enabledWhen(c -> functionComparisonPanel.canCompareNextFunction())
+ .onAction(c -> functionComparisonPanel.compareNextFunction())
+ .buildAndInstallLocal(this);
+
+ new ActionBuilder("Compare Previous Function", plugin.getName())
+ .description("Compare the previous function for the side with focus.")
+ .helpLocation(new HelpLocation(HELP_TOPIC, "Navigate Previous"))
+ .keyBinding("control shift P")
+ .popupMenuPath("Compare Previous Function")
+ .popupMenuGroup(NAV_GROUP)
+ .toolBarIcon(PREVIOUS_FUNCTION_ICON)
+ .toolBarGroup(NAV_GROUP)
+ .enabledWhen(c -> functionComparisonPanel.canComparePreviousFunction())
+ .onAction(c -> functionComparisonPanel.comparePreviousFunction())
+ .buildAndInstallLocal(this);
+
+ new ActionBuilder("Remove Function", plugin.getName())
+ .description("Removes the active function from the comparison")
+ .helpLocation(new HelpLocation(HELP_TOPIC, "Remove_From_Comparison"))
+ .keyBinding("control shift R")
+ .popupMenuPath("Remove Function")
+ .popupMenuGroup(REMOVE_FUNCTIONS_GROUP)
+ .toolBarIcon(REMOVE_FUNCTION_ICON)
+ .toolBarGroup(REMOVE_FUNCTIONS_GROUP)
+ .enabledWhen(c -> functionComparisonPanel.canRemoveActiveFunction())
+ .onAction(c -> functionComparisonPanel.removeActiveFunction())
+ .buildAndInstallLocal(this);
+
+ navigateToAction = new ToggleActionBuilder("Navigate to Selected Function",
+ plugin.getName())
+ .description(HTMLUtilities.toHTML("Toggle On means to navigate to " +
+ "whatever function is selected in the comparison panel, when focus changes" +
+ " or a new function is selected."))
+ .helpLocation(new HelpLocation(HELP_TOPIC, "Navigate_To_Function"))
+ .toolBarIcon(NAV_FUNCTION_ICON)
+ .onAction(c -> maybeGoToActiveFunction())
+ .buildAndInstallLocal(this);
+
+ if (model instanceof DefaultFunctionComparisonModel) {
+ createDefaultModelActions();
+ }
+ }
+
+ // Only the default model supports adding to the current comparison
+ private void createDefaultModelActions() {
+ new ActionBuilder("Add Functions To Comparison", plugin.getName())
+ .description("Add functions to this comparison")
+ .helpLocation(new HelpLocation(HELP_TOPIC, "Add_To_Comparison"))
+ .popupMenuPath("Add Functions")
+ .popupMenuGroup(ADD_COMPARISON_GROUP)
+ .toolBarIcon(ADD_TO_COMPARISON_ICON)
+ .toolBarGroup(ADD_COMPARISON_GROUP)
+ .enabledWhen(c -> model instanceof DefaultFunctionComparisonModel)
+ .onAction(c -> addFunctions())
+ .buildAndInstallLocal(this);
+
+ }
+
+ private void addFunctions() {
+ ProgramManager service = tool.getService(ProgramManager.class);
+ Program currentProgram = service.getCurrentProgram();
+ FunctionTableModel functionTableModel = new FunctionTableModel(tool, currentProgram);
+
+ TableSelectionDialog diag = new TableSelectionDialog<>(
+ "Select Functions: " + currentProgram.getName(), functionTableModel, true);
+ tool.showDialog(diag);
+ List rows = diag.getSelectionItems();
+ if (CollectionUtils.isBlank(rows)) {
+ return; // the table chooser can return null if the operation was cancelled
+ }
+
+ List functions =
+ rows.stream().map(row -> row.getFunction()).collect(Collectors.toList());
+
+ if (model instanceof DefaultFunctionComparisonModel defaultModel) {
+ defaultModel.addFunctions(functions);
+ }
+
+ }
+
+ private void maybeGoToActiveFunction() {
+ if (navigateToAction.isSelected()) {
+ Side activeSide = functionComparisonPanel.getActiveSide();
+ Function function = model.getActiveFunction(activeSide);
+ goToFunction(function);
+ }
+ }
+
+ private void goToFunction(Function function) {
+ GoToService goToService = tool.getService(GoToService.class);
+ if (goToService == null) {
+ Msg.warn(this, "Can't navigate to selected function because GoToService is missing!");
+ return;
+ }
+ goToService.goTo(function.getEntryPoint(), function.getProgram());
}
/**
* Closes this provider if there are no comparisons to view
*/
- void closeIfEmpty() {
- if (isEmpty()) {
+ private void closeIfEmpty() {
+ if (model.isEmpty()) {
closeComponent();
}
}
@@ -271,21 +376,14 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
}
}
- public void removeAddFunctionsAction() {
- //TODO merge multi and this into one
-
- }
-
- public void setCloseListener(Callback closeListener) {
- this.closeListener = Callback.dummyIfNull(closeListener);
- }
-
public CodeComparisonPanel getCodeComparisonPanelByName(String name) {
return functionComparisonPanel.getCodeComparisonPanelByName(name);
}
- public void dispose() {
+ private void dispose() {
+ plugin.providerClosed(this);
+ closeListener.call();
+ closeListener = Callback.dummy();
functionComparisonPanel.dispose();
}
-
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderManager.java
deleted file mode 100644
index 8b233fc7f4..0000000000
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderManager.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/* ###
- * IP: GHIDRA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package ghidra.app.plugin.core.functioncompare;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArraySet;
-
-import docking.ComponentProviderActivationListener;
-import ghidra.program.model.listing.Function;
-import ghidra.program.model.listing.Program;
-
-/**
- * Provides access to all open {@link FunctionComparisonProvider comparison providers}
- * and allows users to do the following:
- * create new providers
- * add comparisons to existing providers
- * remove comparisons
- * notify subscribers when providers are opened/closed
- */
-public class FunctionComparisonProviderManager implements FunctionComparisonProviderListener {
-
- private Set providers = new CopyOnWriteArraySet<>();
- private Set listeners = new HashSet<>();
- private FunctionComparisonPlugin plugin;
-
- /**
- * Constructor
- *
- * @param plugin the parent plugin
- */
- public FunctionComparisonProviderManager(FunctionComparisonPlugin plugin) {
- this.plugin = plugin;
- }
-
- @Override
- public void providerClosed(FunctionComparisonProvider provider) {
- providers.remove(provider);
- provider.dispose();
- listeners.stream().forEach(l -> l.componentProviderDeactivated(provider));
- }
-
- @Override
- public void providerOpened(FunctionComparisonProvider provider) {
- listeners.stream().forEach(l -> l.componentProviderActivated(provider));
- }
-
- public FunctionComparisonProvider createProvider() {
- FunctionComparisonProvider provider = new MultiFunctionComparisonProvider(plugin);
- provider.addToTool();
- providers.add(provider);
- provider.setVisible(true);
- return provider;
- }
-
- /**
- * Creates a new comparison between the given set of functions
- *
- * @param functions the functions to compare
- * @return the new comparison provider
- */
- public FunctionComparisonProvider compareFunctions(Set functions) {
- if (functions.isEmpty()) {
- return null;
- }
- FunctionComparisonProvider provider = createProvider();
- provider.getModel().compareFunctions(functions);
- return provider;
- }
-
- /**
- * Create a new comparison between two given sets of functions
- *
- * @param sourceFunctions
- * @param destinationFunctions
- * @return the new comparison provider
- */
- public FunctionComparisonProvider compareFunctions(Set sourceFunctions,
- Set destinationFunctions) {
- if (sourceFunctions.isEmpty() || destinationFunctions.isEmpty()) {
- return null;
- }
- FunctionComparisonProvider provider = createProvider();
- provider.getModel().compareFunctions(sourceFunctions, destinationFunctions);
- return provider;
- }
-
- /**
- * Creates a new comparison comparison between two functions
- *
- * @param source the source function
- * @param target the target function
- * @return the new comparison provider
- */
- public FunctionComparisonProvider compareFunctions(Function source, Function target) {
- FunctionComparisonProvider provider = new MultiFunctionComparisonProvider(plugin);
- provider.addToTool();
- provider.getModel().compareFunctions(source, target);
- providers.add(provider);
- provider.setVisible(true);
- return provider;
- }
-
- /**
- * Adds a set of functions to an existing comparison provider
- *
- * @param functions the functions to compare
- * @param provider the provider to add the functions to
- */
- public void compareFunctions(Set functions, FunctionComparisonProvider provider) {
- if (functions.isEmpty() || provider == null) {
- return;
- }
-
- providers.add(provider);
- provider.setVisible(true);
- provider.getModel().compareFunctions(functions);
- }
-
- /**
- * Adds the given functions to an existing comparison provider
- *
- * @param source the source function
- * @param target the target function
- * @param provider the provider to add the functions to
- */
- public void compareFunctions(Function source, Function target,
- FunctionComparisonProvider provider) {
- if (provider == null) {
- return;
- }
-
- providers.add(provider);
- provider.setVisible(true);
- provider.getModel().compareFunctions(source, target);
- }
-
- /**
- * Removes a given function from all comparisons across all providers
- *
- * @param function the function to remove
- */
- public void removeFunction(Function function) {
- providers.stream().forEach(p -> p.getModel().removeFunction(function));
- }
-
- /**
- * Removes a given function from a specified provider
- *
- * @param function the function to remove
- * @param provider the provider to remove the function from
- */
- public void removeFunction(Function function, FunctionComparisonProvider provider) {
- if (provider == null) {
- return;
- }
- provider.getModel().removeFunction(function);
- }
-
- /**
- * Registers subscribers who wish to know of provider activation status
- *
- * @param listener the subscriber to register
- */
- public void addProviderListener(ComponentProviderActivationListener listener) {
- listeners.add(listener);
- }
-
- /**
- * Removes a subscriber who no longer wishes to receive provider activation
- * events
- *
- * @param listener the subscriber to remove
- */
- public void removeProviderListener(ComponentProviderActivationListener listener) {
- listeners.remove(listener);
- }
-
- /**
- * Closes all the comparison providers that contain a function from
- * the given program
- *
- * @param program the program whose function providers need to close
- */
- public void closeProviders(Program program) {
- providers.stream().forEach(p -> p.programClosed(program));
- }
-
- /**
- * Removes any comparisons that contain a function from the given program
- *
- * @param program the program whose functions require removal
- */
- public void removeFunctions(Program program) {
- providers.stream().forEach(p -> p.removeFunctions(program));
- }
-
- /**
- * Cleans up all providers, setting them invisible and removing any
- * associated ui components (eg: tabs)
- */
- public void dispose() {
- for (FunctionComparisonProvider provider : providers) {
- provider.dispose();
- }
- providers.clear();
- }
-
- /**
- * Called when there is an Undo/Redo. If a program is being restored, this
- * will notify all the function comparison providers. This allows them to
- * refresh if they are showing a function from the program
- *
- * @param program the program that was restored
- */
- public void domainObjectRestored(Program program) {
- providers.stream().forEach(p -> p.programRestored(program));
- }
-
-}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonPanel.java
index 22b7f3c0e9..84dca8805d 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonPanel.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonPanel.java
@@ -20,8 +20,6 @@ import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
-import java.util.Iterator;
-import java.util.Set;
import javax.swing.*;
@@ -29,295 +27,180 @@ import ghidra.app.services.FunctionComparisonModel;
import ghidra.app.util.viewer.util.CodeComparisonPanel;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Function;
+import ghidra.util.datastruct.Duo;
import ghidra.util.datastruct.Duo.Side;
-import help.Help;
-import help.HelpService;
/**
* Extends the basic {@link FunctionComparisonPanel one-to-one comparison panel}
* to allow a many-to-many relationship. The panel provides a pair of combo
* boxes above the function display area that allows users to select which
* functions are to be compared.
- *
- * Throughout this class the terms source and target
- * are used when referencing functions. This is because the model that backs
- * this panel maintains a relationship between the functions being compared
- * such that each source function can only be compared to a specific set
- * of target functions. For all practical purposes, the source functions
- * appear in the left-side panel and targets appear on the right.
- *
+ *
+ * This behavior of this class is driven by the given {@link FunctionComparisonModel}. The default
+ * model displays the same set of functions on both sides. But the model interface allows for
+ * other behaviors such as having different sets of function on each side and even changing the
+ * set of functions on one side base on what is selected on the other side.
*/
-public class MultiFunctionComparisonPanel extends FunctionComparisonPanel {
+public class MultiFunctionComparisonPanel extends FunctionComparisonPanel
+ implements FunctionComparisonModelListener {
- /** Functions that will show up on the left side of the panel */
- private JComboBox sourceFunctionsCB;
-
- /** Functions that will show up on the right side of the panel */
- private JComboBox targetFunctionsCB;
-
- /** Data models backing the source and target combo boxes */
- private DefaultComboBoxModel sourceFunctionsCBModel;
- private DefaultComboBoxModel targetFunctionsCBModel;
-
- protected static final HelpService help = Help.getHelpService();
public static final String HELP_TOPIC = "FunctionComparison";
+ private FunctionComparisonModel model;
+ private Duo> comboBoxes;
+ private Duo comboListeners;
+
/**
* Constructor
*
* @param provider the comparison provider associated with this panel
* @param tool the active plugin tool
+ * @param model the comparison data model
*/
- public MultiFunctionComparisonPanel(MultiFunctionComparisonProvider provider, PluginTool tool) {
- super(provider, tool);
+ public MultiFunctionComparisonPanel(FunctionComparisonProvider provider, PluginTool tool,
+ FunctionComparisonModel model) {
+ super(tool, provider.getName());
+ this.model = model;
+ model.addFunctionComparisonModelListener(this);
- JPanel choicePanel = new JPanel(new GridLayout(1, 2));
- choicePanel.add(createSourcePanel());
- choicePanel.add(createTargetPanel());
- add(choicePanel, BorderLayout.NORTH);
+ buildComboPanels();
- // For the multi-panels we don't need to show the title of each
- // comparison panel because the name of the function/data being shown
- // is already visible in the combo box
getComparisonPanels().forEach(p -> p.setShowDataTitles(false));
setPreferredSize(new Dimension(1200, 600));
+ modelDataChanged();
}
- /**
- * Clears out the source and targets lists and reloads them to
- * ensure that they reflect the current state of the data model. Any
- * currently-selected list items will be restored after the lists
- * are reloaded.
- */
@Override
- public void reload() {
-
- reloadSourceList();
- Function selectedSource = (Function) sourceFunctionsCBModel.getSelectedItem();
- reloadTargetList(selectedSource);
- loadFunctions(selectedSource, (Function) targetFunctionsCBModel.getSelectedItem());
- updateTabText();
-
- // Fire a notification to update the UI state; without this the
- // actions would not be properly enabled/disabled
- tool.contextChanged(provider);
+ public void activeFunctionChanged(Side side, Function function) {
+ updateComboBoxSelectIfNeeded(side, function);
+ loadFunctions(model.getActiveFunction(LEFT), model.getActiveFunction(RIGHT));
}
- /**
- * Returns the combo box (source or target) which has focus
- *
- * @return the focused component
- */
- public JComboBox getFocusedComponent() {
- CodeComparisonPanel currentComponent = getCurrentComponent();
- Side side = currentComponent.getActiveSide();
- return side == LEFT ? sourceFunctionsCB : targetFunctionsCB;
+ @Override
+ public void modelDataChanged() {
+ intializeComboBox(LEFT);
+ intializeComboBox(RIGHT);
+ loadFunctions(model.getActiveFunction(LEFT), model.getActiveFunction(RIGHT));
}
- public Side getFocusedSide() {
+ @Override
+ public void dispose() {
+ model.removeFunctionComparisonModelListener(this);
+ super.dispose();
+ }
+
+ Side getActiveSide() {
CodeComparisonPanel currentComponent = getCurrentComponent();
return currentComponent.getActiveSide();
}
- /**
- * Returns the source combo box
- *
- * @return the source combo box
- */
- public JComboBox getSourceComponent() {
- return sourceFunctionsCB;
+ boolean canCompareNextFunction() {
+ Side activeSide = getActiveSide();
+ JComboBox combo = comboBoxes.get(activeSide);
+ int index = combo.getSelectedIndex();
+ return index < combo.getModel().getSize() - 1;
}
- /**
- * Returns the target combo box
- *
- * @return the target combo box
- */
- public JComboBox getTargetComponent() {
- return targetFunctionsCB;
+ boolean canComparePreviousFunction() {
+ Side activeSide = getActiveSide();
+ JComboBox combo = comboBoxes.get(activeSide);
+ int index = combo.getSelectedIndex();
+ return index > 0;
}
- /**
- * Clears out and reloads the source function list. Any selection currently
- * made on the list will be reestablished.
- */
- private void reloadSourceList() {
+ void compareNextFunction() {
+ Side activeSide = getActiveSide();
+ JComboBox combo = comboBoxes.get(activeSide);
+ int index = combo.getSelectedIndex();
+ combo.setSelectedIndex(index + 1);
+ }
- // Save off any selected item so we can restore if it later
- Function selection = (Function) sourceFunctionsCBModel.getSelectedItem();
+ void comparePreviousFunction() {
+ Side activeSide = getActiveSide();
+ JComboBox combo = comboBoxes.get(activeSide);
+ int index = combo.getSelectedIndex();
+ combo.setSelectedIndex(index - 1);
+ }
- // Remove all functions
- sourceFunctionsCBModel.removeAllElements();
+ boolean canRemoveActiveFunction() {
+ Side activeSide = getActiveSide();
+ return model.getActiveFunction(activeSide) != null;
+ }
- // Reload the functions
- FunctionComparisonModel model = ((FunctionComparisonProvider) provider).getModel();
- Iterator compIter = model.getComparisons().iterator();
- while (compIter.hasNext()) {
- FunctionComparison fc = compIter.next();
- sourceFunctionsCBModel.addElement(fc.getSource());
+ void removeActiveFunction() {
+ Side activeSide = getActiveSide();
+ model.removeFunction(model.getActiveFunction(activeSide));
+ }
+
+ private void buildComboPanels() {
+ JPanel choicePanel = new JPanel(new GridLayout(1, 2));
+ createComboBoxes();
+ choicePanel.add(createPanel(LEFT));
+ choicePanel.add(createPanel(RIGHT));
+ add(choicePanel, BorderLayout.NORTH);
+ }
+
+ private void intializeComboBox(Side side) {
+ JComboBox comboBox = comboBoxes.get(side);
+ comboBox.removeItemListener(comboListeners.get(side));
+
+ DefaultComboBoxModel comboModel =
+ (DefaultComboBoxModel) comboBox.getModel();
+ comboModel.removeAllElements();
+ comboModel.addAll(model.getFunctions(side));
+
+ Function activeFunction = model.getActiveFunction(side);
+ if (activeFunction != null) {
+ comboBox.setSelectedItem(activeFunction);
}
- restoreSelection(sourceFunctionsCB, selection);
+ comboBox.addItemListener(comboListeners.get(side));
}
- /**
- * Clears out and reloads the target function list with functions
- * associated with the given source function. Any selection currently made
- * on the list will be reestablished.
- *
- * @param source the selected source function
- */
- private void reloadTargetList(Function source) {
+ private void createComboBoxes() {
+ createComboBoxListeners();
+ JComboBox leftComboBox = buildComboBox(LEFT);
+ JComboBox rightComboBox = buildComboBox(RIGHT);
+ comboBoxes = new Duo<>(leftComboBox, rightComboBox);
+ }
- // Save off any selected item so we can restore if it later
- Function selection = (Function) targetFunctionsCBModel.getSelectedItem();
+ private void createComboBoxListeners() {
+ ItemListener leftListener = e -> comboChanged(e, LEFT);
+ ItemListener rightListener = e -> comboChanged(e, RIGHT);
+ comboListeners = new Duo<>(leftListener, rightListener);
+ }
- // Remove all functions
- targetFunctionsCBModel.removeAllElements();
-
- // Find all target functions associated with the given source function
- // and add them to the combo box model
- FunctionComparisonModel model = ((FunctionComparisonProvider) provider).getModel();
- Iterator compIter = model.getComparisons().iterator();
- while (compIter.hasNext()) {
- FunctionComparison fc = compIter.next();
- if (fc.getSource().equals(source)) {
- Set targets = fc.getTargets();
- targetFunctionsCBModel.addAll(targets);
- }
+ private void comboChanged(ItemEvent e, Side side) {
+ if (e.getStateChange() == ItemEvent.DESELECTED) {
+ return; // only care when a function is selected
}
-
- restoreSelection(targetFunctionsCB, selection);
-
- // we don't want the initial target to match the source as that is a pointless comparison
- fixupTargetSelectionToNotMatchSource(source);
+ model.setActiveFunction(side, (Function) e.getItem());
}
- private void fixupTargetSelectionToNotMatchSource(Function source) {
- if (targetFunctionsCB.getSelectedItem() != source) {
+ private JComboBox buildComboBox(Side side) {
+ DefaultComboBoxModel leftModel = new DefaultComboBoxModel<>();
+ JComboBox comboBox = new JComboBox<>(leftModel);
+ comboBox.setName(side + "FunctionComboBox");
+ comboBox.setRenderer(new FunctionListCellRenderer());
+ comboBox.addItemListener(comboListeners.get(side));
+ return comboBox;
+ }
+
+ private JPanel createPanel(Side side) {
+ JPanel panel = new JPanel(new BorderLayout());
+ JComboBox comboBox = comboBoxes.get(side);
+ panel.add(comboBox, BorderLayout.CENTER);
+ return panel;
+ }
+
+ private void updateComboBoxSelectIfNeeded(Side side, Function function) {
+ JComboBox combo = comboBoxes.get(side);
+ if (combo.getSelectedItem() == function) {
return;
}
- for (int i = 0; i < targetFunctionsCB.getItemCount(); i++) {
- if (targetFunctionsCB.getItemAt(i) != source) {
- targetFunctionsCB.setSelectedIndex(i);
- return;
- }
- }
- }
-
- /**
- * Sets the text on the current tab to match whatever is displayed in the
- * comparison panels
- */
- private void updateTabText() {
- String tabText = getDescription();
- provider.setTabText(tabText);
- provider.setTitle(tabText);
- }
-
- /**
- * Sets a given function to be the selected item in a given combo
- * box. If the function isn't found, the first item in the box is
- * set.
- *
- * @param cb the combo box
- * @param selection the function to set
- */
- private void restoreSelection(JComboBox cb, Function selection) {
- ComboBoxModel model = cb.getModel();
-
- boolean found = false;
- for (int i = 0; i < model.getSize(); i++) {
- Function f = model.getElementAt(i);
- if (f.equals(selection)) {
- model.setSelectedItem(f);
- found = true;
- break;
- }
- }
-
- if (!found && model.getSize() > 0) {
- cb.setSelectedIndex(0);
- }
- }
-
- /**
- * Creates the panel displaying the source combo box
- *
- * Note: The custom renderer is used so the name of the program associated
- * with each function can be displayed in the combo box; this is necessary
- * since a combo box may show functions from any number of programs, and
- * the default is to simply show the function name
- * eg: "init (notepad)"
- *
- * @return the source panel
- */
- private JPanel createSourcePanel() {
- JPanel panel = new JPanel(new BorderLayout());
- sourceFunctionsCB = new JComboBox<>();
- sourceFunctionsCBModel = new DefaultComboBoxModel<>();
- sourceFunctionsCB.setModel(sourceFunctionsCBModel);
- sourceFunctionsCB.setRenderer(new FunctionListCellRenderer());
- sourceFunctionsCB.addItemListener(new ItemListener() {
- @Override
- public void itemStateChanged(ItemEvent e) {
- if (e.getStateChange() != ItemEvent.SELECTED) {
- return;
- }
-
- // Each time a source function is selected we need
- // to load the targets associated with it
- reloadTargetList((Function) sourceFunctionsCBModel.getSelectedItem());
-
- updateTabText();
-
- // Fire a notification to update the UI state; without this the
- // actions would not be properly enabled/disabled
- tool.contextChanged(provider);
- }
- });
-
- panel.add(sourceFunctionsCB, BorderLayout.CENTER);
- return panel;
- }
-
- /**
- * Creates the panel for the target functions selection components
- *
- * Note: The custom renderer is used so the name of the program associated
- * with each function can be displayed in the combo box; this is necessary
- * since a combo box may show functions from any number of programs, and
- * the default is to simply show the function name
- * eg: "init (notepad)"
- *
- * @return the target panel
- */
- private JPanel createTargetPanel() {
- JPanel panel = new JPanel(new BorderLayout());
- targetFunctionsCB = new JComboBox<>();
- targetFunctionsCBModel = new DefaultComboBoxModel<>();
- targetFunctionsCB.setModel(targetFunctionsCBModel);
- targetFunctionsCB.setRenderer(new FunctionListCellRenderer());
- targetFunctionsCB.addItemListener(new ItemListener() {
- @Override
- public void itemStateChanged(ItemEvent e) {
- if (e.getStateChange() != ItemEvent.SELECTED) {
- return;
- }
-
- Function selected = (Function) targetFunctionsCBModel.getSelectedItem();
- loadFunctions((Function) sourceFunctionsCBModel.getSelectedItem(), selected);
-
- updateTabText();
-
- // Fire a notification to update the UI state; without this the
- // actions would not be properly enabled/disabled
- tool.contextChanged(provider);
- }
- });
-
- panel.add(targetFunctionsCB, BorderLayout.CENTER);
- return panel;
+ combo.removeItemListener(comboListeners.get(side));
+ combo.setSelectedItem(function);
+ combo.addItemListener(comboListeners.get(side));
}
/**
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonProvider.java
deleted file mode 100644
index acf1c20f05..0000000000
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonProvider.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/* ###
- * IP: GHIDRA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package ghidra.app.plugin.core.functioncompare;
-
-import docking.action.DockingAction;
-import ghidra.app.plugin.core.functioncompare.actions.*;
-import ghidra.framework.plugintool.Plugin;
-
-/**
- * Provider for a {@link MultiFunctionComparisonPanel}. This differs from the
- * base comparison provider in that it has additional actions that are
- * appropriate for managing multiple comparisons (add, remove, etc...).
- */
-public class MultiFunctionComparisonProvider extends FunctionComparisonProvider {
-
- private DockingAction openFunctionTableAction;
-
- /**
- * Constructor
- *
- * @param plugin the parent plugin
- */
- protected MultiFunctionComparisonProvider(FunctionComparisonPlugin plugin) {
- super(plugin, "Functions Comparison Provider", plugin.getName());
- }
-
- @Override
- public FunctionComparisonPanel getComponent() {
- if (functionComparisonPanel == null) {
- functionComparisonPanel = new MultiFunctionComparisonPanel(this, tool);
- }
- return functionComparisonPanel;
- }
-
- @Override
- boolean isEmpty() {
- return model.getSourceFunctions().isEmpty();
- }
-
- @Override
- protected void initFunctionComparisonPanel() {
- super.initFunctionComparisonPanel();
-
- DockingAction nextFunctionAction = new NextFunctionAction(this);
- DockingAction previousFunctionAction = new PreviousFunctionAction(this);
- DockingAction removeFunctionsAction = new RemoveFunctionsAction(this);
- openFunctionTableAction = getOpenFunctionTableAction();
- DockingAction navigateToAction = new NavigateToFunctionAction(this);
-
- addLocalAction(nextFunctionAction);
- addLocalAction(previousFunctionAction);
- addLocalAction(removeFunctionsAction);
- addLocalAction(openFunctionTableAction);
- addLocalAction(navigateToAction);
- }
-
- /**
- * Returns an action that opens a table from which users may select
- * functions for comparison. By default this returns an action that will
- * open a standard function table, but may be overridden as-needed.
- *
- * @return the docking action
- */
- protected DockingAction getOpenFunctionTableAction() {
- return new OpenFunctionTableAction(tool, this);
- }
-
- @Override
- public void removeAddFunctionsAction() {
- removeLocalAction(openFunctionTableAction);
- }
-}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsAction.java
deleted file mode 100644
index 79063dce92..0000000000
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsAction.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/* ###
- * IP: GHIDRA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package ghidra.app.plugin.core.functioncompare.actions;
-
-import java.awt.event.InputEvent;
-import java.util.Set;
-
-import javax.swing.Icon;
-
-import docking.ActionContext;
-import docking.action.*;
-import generic.theme.GIcon;
-import ghidra.app.services.FunctionComparisonService;
-import ghidra.framework.plugintool.PluginTool;
-import ghidra.program.model.listing.Function;
-import ghidra.util.HelpLocation;
-
-/**
- * Creates a new comparison between a set of functions, launching a new
- * comparison provider in the process
- *
- * This class is abstract to force implementors to supply the source of the
- * functions (may be the listing, a table, etc...)
- *
- * @see #getSelectedFunctions(ActionContext)
- */
-public abstract class CompareFunctionsAction extends DockingAction {
-
- protected FunctionComparisonService comparisonService;
-
- private static final Icon COMPARISON_ICON = new GIcon("icon.plugin.functioncompare.new");
- private static final String CREATE_COMPARISON_GROUP = "A9_CreateComparison";
- static final String POPUP_MENU_NAME = "Compare Selected Functions";
-
- /**
- * Constructor
- *
- * @param tool the plugin tool
- * @param owner the action owner (usually the plugin name)
- */
- public CompareFunctionsAction(PluginTool tool, String owner) {
- super("Compare Functions", owner, KeyBindingType.SHARED);
- this.comparisonService = tool.getService(FunctionComparisonService.class);
- setActionAttributes();
- }
-
- @Override
- public void actionPerformed(ActionContext context) {
- Set functions = getSelectedFunctions(context);
- comparisonService.compareFunctions(functions);
- }
-
- @Override
- public boolean isEnabledForContext(ActionContext actionContext) {
- Set functions = getSelectedFunctions(actionContext);
- return !functions.isEmpty();
- }
-
- /**
- * Returns the icon to use for the action
- *
- * @return the icon
- */
- protected Icon getToolBarIcon() {
- return COMPARISON_ICON;
- }
-
- /**
- * Returns the set of functions that will be sent to the comparison service
- *
- * @param actionContext the current action context
- * @return set of functions to be compared
- */
- protected abstract Set getSelectedFunctions(ActionContext actionContext);
-
- private void setActionAttributes() {
- setDescription("Create Function Comparison");
- setPopupMenuData(new MenuData(new String[] { "Compare Selected Functions" },
- getToolBarIcon(), CREATE_COMPARISON_GROUP));
-
- ToolBarData newToolBarData = new ToolBarData(getToolBarIcon(), CREATE_COMPARISON_GROUP);
- setToolBarData(newToolBarData);
-
- setHelpLocation(new HelpLocation("FunctionComparison", "Function_Comparison"));
-
- KeyBindingData data = new KeyBindingData('C', InputEvent.SHIFT_DOWN_MASK);
- setKeyBindingData(data);
- }
-}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsFromFunctionTableAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsFromFunctionTableAction.java
deleted file mode 100644
index d9c86dd9df..0000000000
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsFromFunctionTableAction.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/* ###
- * IP: GHIDRA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package ghidra.app.plugin.core.functioncompare.actions;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import docking.ActionContext;
-import ghidra.app.plugin.core.functionwindow.FunctionRowObject;
-import ghidra.app.plugin.core.functionwindow.FunctionTableModel;
-import ghidra.framework.plugintool.PluginTool;
-import ghidra.program.model.listing.Function;
-import ghidra.util.table.GhidraTable;
-
-/**
- * Creates a comparison between a set of functions extracted from selections in
- * a ghidra table. By default this table is assumed to be constructed using a
- * {@link FunctionTableModel}. If the {@link ActionContext context} for
- * this action does NOT meet those parameters this action will not even be
- * enabled.
- *
- * If this action is to be used with a different type of table, simply
- * extend this class and override {@link #getSelectedFunctions(ActionContext) getSelectedFunctions}
- * and {@link #isModelSupported(ActionContext) isModelSupported} as-needed.
- */
-public class CompareFunctionsFromFunctionTableAction extends CompareFunctionsAction {
-
- /**
- * Constructor
- *
- * @param tool the plugin tool
- * @param owner the action owner
- */
- public CompareFunctionsFromFunctionTableAction(PluginTool tool, String owner) {
- super(tool, owner);
- }
-
- @Override
- public boolean isAddToPopup(ActionContext context) {
- return isModelSupported(context);
- }
-
- @Override
- public boolean isValidContext(ActionContext context) {
- return isModelSupported(context);
- }
-
- @Override
- public boolean isEnabledForContext(ActionContext actionContext) {
- GhidraTable table = (GhidraTable) actionContext.getContextObject();
- int[] selectedRows = table.getSelectedRows();
- return selectedRows.length > 1;
- }
-
- @Override
- protected Set getSelectedFunctions(ActionContext actionContext) {
- Set functions = new HashSet<>();
-
- GhidraTable table = (GhidraTable) actionContext.getContextObject();
- int[] selectedRows = table.getSelectedRows();
- if (selectedRows.length == 0) {
- return Collections.emptySet();
- }
- FunctionTableModel model = (FunctionTableModel) table.getModel();
- List functionRowObjects = model.getRowObjects(selectedRows);
- for (FunctionRowObject functionRowObject : functionRowObjects) {
- Function rowFunction = functionRowObject.getFunction();
- functions.add(rowFunction);
- }
- return functions;
- }
-
- /**
- * Helper method to determine if the current context is one that this
- * action supports (eg: is this action being applied to a table that
- * contains function information?).
- *
- * By default this method verifies that the table in question is a
- * {@link FunctionTableModel}. If another table is being used, override this
- * method.
- *
- * @param context the action context
- * @return true if the context is a function table model
- */
- protected boolean isModelSupported(ActionContext context) {
- if (!(context.getContextObject() instanceof GhidraTable)) {
- return false;
- }
- GhidraTable table = (GhidraTable) context.getContextObject();
- return table.getModel() instanceof FunctionTableModel;
- }
-}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsFromListingAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsFromListingAction.java
deleted file mode 100644
index d0161bf7ff..0000000000
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/CompareFunctionsFromListingAction.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/* ###
- * IP: GHIDRA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package ghidra.app.plugin.core.functioncompare.actions;
-
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.swing.Icon;
-
-import docking.ActionContext;
-import docking.action.MenuData;
-import ghidra.app.context.ListingActionContext;
-import ghidra.framework.plugintool.PluginTool;
-import ghidra.program.model.listing.*;
-import ghidra.program.util.ProgramSelection;
-
-/**
- * Creates a comparison between a set of functions extracted from selections
- * in the listing
- */
-public class CompareFunctionsFromListingAction extends CompareFunctionsAction {
-
- private final static String FUNCTION_MENU_SUBGROUP = "Function";
-
- /**
- * Constructor
- *
- * @param tool the plugin tool
- * @param owner the action owner
- */
- public CompareFunctionsFromListingAction(PluginTool tool, String owner) {
- super(tool, owner);
-
- // this action is used as a global action--do not add it to the toolbar
- setToolBarData(null);
-
- //
- // Guilty knowledge of other function-related menu items.
- // See the FunctionPlugin for this value
- //
- String menuSubGroup = "Z_End";
- Icon icon = null; // we don't use icons in the Listing popup menu
- setPopupMenuData(new MenuData(new String[] { POPUP_MENU_NAME }, icon,
- FUNCTION_MENU_SUBGROUP, MenuData.NO_MNEMONIC,
- menuSubGroup));
- }
-
- @Override
- public boolean isAddToPopup(ActionContext actionContext) {
- return actionContext instanceof ListingActionContext && isEnabledForContext(actionContext);
- }
-
- @Override
- public boolean isValidContext(ActionContext context) {
- return context instanceof ListingActionContext;
- }
-
- @Override
- protected Set getSelectedFunctions(ActionContext actionContext) {
- ListingActionContext listingContext = (ListingActionContext) actionContext;
- ProgramSelection selection = listingContext.getSelection();
- Program program = listingContext.getProgram();
- FunctionManager functionManager = program.getFunctionManager();
- Set functions = new HashSet<>();
- FunctionIterator functionIter = functionManager.getFunctions(selection, true);
- for (Function selectedFunction : functionIter) {
- functions.add(selectedFunction);
- }
- return functions;
- }
-}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/NavigateToFunctionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/NavigateToFunctionAction.java
deleted file mode 100644
index 0efece36a5..0000000000
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/NavigateToFunctionAction.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/* ###
- * IP: GHIDRA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package ghidra.app.plugin.core.functioncompare.actions;
-
-import static ghidra.util.datastruct.Duo.Side.*;
-
-import java.awt.event.*;
-import java.util.List;
-
-import javax.swing.Icon;
-import javax.swing.JComboBox;
-
-import docking.ActionContext;
-import docking.action.ToggleDockingAction;
-import docking.action.ToolBarData;
-import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonPanel;
-import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonProvider;
-import ghidra.app.services.GoToService;
-import ghidra.app.util.viewer.util.CodeComparisonPanel;
-import ghidra.program.model.address.AddressSetView;
-import ghidra.program.model.listing.Function;
-import ghidra.program.model.listing.Program;
-import ghidra.util.HTMLUtilities;
-import ghidra.util.HelpLocation;
-import ghidra.util.datastruct.Duo.Side;
-import resources.Icons;
-
-/**
- * Toggle Action designed to be used with a {@link MultiFunctionComparisonProvider}.
- * When toggled on, a GoTo event will be issued for the function displayed in
- * the comparison panel after the following events:
- *
- * - focus is gained on either the left or right panels
- * - the function displayed in a comparison panel changes
- *
- * Note that the GoTo will only operate on the comparison panel that
- * has focus. eg: If the left panel has focus but the user changes the
- * function being viewed in the right panel, no GoTo will be issued.
- */
-public class NavigateToFunctionAction extends ToggleDockingAction {
-
- private GoToService goToService;
-
- private static final Icon NAV_FUNCTION_ICON = Icons.NAVIGATE_ON_INCOMING_EVENT_ICON;
-
- private MultiFunctionComparisonPanel comparisonPanel;
-
- /**
- * Constructor
- *
- * @param provider the function comparison provider containing this action
- */
- public NavigateToFunctionAction(MultiFunctionComparisonProvider provider) {
- super("Navigate To Selected Function", provider.getName());
- comparisonPanel = (MultiFunctionComparisonPanel) provider.getComponent();
-
- goToService = provider.getTool().getService(GoToService.class);
-
- setEnabled(true);
- setSelected(false);
- ToolBarData newToolBarData = new ToolBarData(NAV_FUNCTION_ICON);
- setToolBarData(newToolBarData);
- setDescription(HTMLUtilities.toHTML("Toggle On means to navigate to whatever " +
- "function is selected in the comparison panel, when focus changes or" +
- "a new function is selected."));
- setHelpLocation(
- new HelpLocation(MultiFunctionComparisonPanel.HELP_TOPIC, "Navigate_To_Function"));
-
- addFocusListeners();
- addChangeListeners();
- }
-
- @Override
- public void actionPerformed(ActionContext context) {
- JComboBox combo = comparisonPanel.getFocusedComponent();
- Function f = (Function) combo.getSelectedItem();
- goToService.goTo(f.getEntryPoint(), f.getProgram());
- }
-
- /**
- * Adds a listener to each of the function selection widgets in the
- * comparison provider. When a new function is selected, a GoTo event
- * is generated for the entry point of the function.
- *
- */
- private void addChangeListeners() {
- JComboBox sourceCombo = comparisonPanel.getSourceComponent();
- JComboBox targetCombo = comparisonPanel.getTargetComponent();
- sourceCombo.addItemListener(new PanelItemListener(LEFT));
- targetCombo.addItemListener(new PanelItemListener(RIGHT));
-
- }
-
- /**
- * Adds a listener to each panel in the function comparison provider,
- * triggered when focus has been changed. If focused is gained in a panel,
- * a GoTo event is issued containing the function start address.
- */
- private void addFocusListeners() {
- List panels = comparisonPanel.getComparisonPanels();
-
- for (CodeComparisonPanel panel : panels) {
- panel.getComparisonComponent(LEFT)
- .addFocusListener(new PanelFocusListener(panel, Side.LEFT));
- panel.getComparisonComponent(RIGHT)
- .addFocusListener(new PanelFocusListener(panel, Side.RIGHT));
- }
- }
-
- private class PanelItemListener implements ItemListener {
- private Side side;
-
- PanelItemListener(Side side) {
- this.side = side;
- }
-
- @Override
- public void itemStateChanged(ItemEvent e) {
- if (e.getStateChange() != ItemEvent.SELECTED) {
- return;
- }
- if (comparisonPanel.getFocusedSide() != side) {
- return;
- }
-
- if (isSelected()) {
- JComboBox> combo = (JComboBox>) e.getSource();
- Function f = (Function) combo.getSelectedItem();
- goToService.goTo(f.getEntryPoint(), f.getProgram());
- }
- }
-
- }
-
- private class PanelFocusListener extends FocusAdapter {
- private CodeComparisonPanel panel;
- private Side side;
-
- PanelFocusListener(CodeComparisonPanel panel, Side side) {
- this.panel = panel;
- this.side = side;
- }
-
- @Override
- public void focusGained(FocusEvent e) {
- if (!isSelected()) {
- return;
- }
- Program program = panel.getProgram(side);
- AddressSetView addresses = panel.getAddresses(side);
- if (program != null && addresses != null && !addresses.isEmpty()) {
- goToService.goTo(addresses.getMinAddress(), program);
- }
- }
- }
-}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/NextFunctionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/NextFunctionAction.java
deleted file mode 100644
index 915724a1ec..0000000000
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/NextFunctionAction.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/* ###
- * IP: GHIDRA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package ghidra.app.plugin.core.functioncompare.actions;
-
-import java.awt.Component;
-import java.awt.event.InputEvent;
-
-import javax.swing.Icon;
-import javax.swing.JComboBox;
-
-import docking.ActionContext;
-import docking.ComponentProvider;
-import docking.action.*;
-import generic.theme.GIcon;
-import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonPanel;
-import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonProvider;
-import ghidra.program.model.listing.Function;
-import ghidra.util.HelpLocation;
-
-/**
- * Displays the next available function in the function comparison panel. If
- * already at the end of the list, the action will not be enabled.
- */
-public class NextFunctionAction extends DockingAction {
-
- private static final String FUNCTION_NAVIGATE_GROUP = "A9_FunctionNavigate";
- private static final Icon NEXT_FUNCTION_ICON =
- new GIcon("icon.plugin.functioncompare.function.next");
-
- /**
- * Constructor
- *
- * @param provider the comparison provider for this action
- */
- public NextFunctionAction(MultiFunctionComparisonProvider provider) {
- super("Compare Next Function", provider.getOwner());
-
- setKeyBindingData(
- new KeyBindingData('N', InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
- setDescription("Compare the next function for the side with focus.");
- setPopupMenuData(
- new MenuData(new String[] { "Compare The Next Function" }, NEXT_FUNCTION_ICON,
- FUNCTION_NAVIGATE_GROUP));
-
- ToolBarData newToolBarData =
- new ToolBarData(NEXT_FUNCTION_ICON, FUNCTION_NAVIGATE_GROUP);
- setToolBarData(newToolBarData);
-
- HelpLocation helpLocation =
- new HelpLocation(MultiFunctionComparisonPanel.HELP_TOPIC, "Navigate_Next");
- setHelpLocation(helpLocation);
- }
-
- @Override
- public boolean isEnabledForContext(ActionContext context) {
- if (!(context.getComponentProvider() instanceof MultiFunctionComparisonProvider)) {
- return false;
- }
- MultiFunctionComparisonProvider provider =
- (MultiFunctionComparisonProvider) context.getComponentProvider();
-
- Component comp = provider.getComponent();
- if (!(comp instanceof MultiFunctionComparisonPanel)) {
- return false;
- }
-
- MultiFunctionComparisonPanel panel = (MultiFunctionComparisonPanel) comp;
- JComboBox focusedComponent = panel.getFocusedComponent();
- return focusedComponent.getSelectedIndex() < (focusedComponent.getModel().getSize() - 1);
- }
-
- @Override
- public void actionPerformed(ActionContext context) {
- ComponentProvider provider = context.getComponentProvider();
- MultiFunctionComparisonPanel panel = (MultiFunctionComparisonPanel) provider.getComponent();
- JComboBox focusedComponent = panel.getFocusedComponent();
- focusedComponent.setSelectedIndex(focusedComponent.getSelectedIndex() + 1);
- }
-}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/OpenFunctionTableAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/OpenFunctionTableAction.java
deleted file mode 100644
index 77f42d7063..0000000000
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/OpenFunctionTableAction.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/* ###
- * IP: GHIDRA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package ghidra.app.plugin.core.functioncompare.actions;
-
-import java.awt.event.InputEvent;
-import java.util.*;
-import java.util.stream.Collectors;
-
-import javax.swing.Icon;
-
-import docking.ActionContext;
-import docking.action.*;
-import docking.widgets.dialogs.TableSelectionDialog;
-import generic.theme.GIcon;
-import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider;
-import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonPanel;
-import ghidra.app.plugin.core.functionwindow.FunctionRowObject;
-import ghidra.app.plugin.core.functionwindow.FunctionTableModel;
-import ghidra.app.services.FunctionComparisonService;
-import ghidra.app.services.ProgramManager;
-import ghidra.framework.plugintool.PluginTool;
-import ghidra.program.model.listing.Function;
-import ghidra.program.model.listing.Program;
-import ghidra.util.HelpLocation;
-import util.CollectionUtils;
-
-/**
- * Opens a table chooser allowing the user to select functions from the current
- * program. The table displayed uses a {@link FunctionTableModel}.
- *
- * @see FunctionComparisonService
- */
-public class OpenFunctionTableAction extends DockingAction {
-
- private static final String ADD_COMPARISON_GROUP = "A9_AddToComparison";
- private static final Icon ADD_TO_COMPARISON_ICON =
- new GIcon("icon.plugin.functioncompare.open.function.table");
-
- protected PluginTool tool;
- protected ProgramManager programManagerService;
- protected FunctionComparisonService comparisonService;
-
- /**
- * Constructor
- *
- * @param tool the plugin tool
- * @param provider the function comparison provider
- */
- public OpenFunctionTableAction(PluginTool tool, FunctionComparisonProvider provider) {
- super("Add Functions To Comparison", provider.getOwner());
-
- this.tool = tool;
- this.programManagerService = tool.getService(ProgramManager.class);
- this.comparisonService = tool.getService(FunctionComparisonService.class);
-
- setDescription("Add functions to comparison");
- setPopupMenuData(new MenuData(new String[] { "Add functions" }, ADD_TO_COMPARISON_ICON,
- ADD_COMPARISON_GROUP));
-
- ToolBarData newToolBarData = new ToolBarData(ADD_TO_COMPARISON_ICON, ADD_COMPARISON_GROUP);
- setToolBarData(newToolBarData);
-
- HelpLocation helpLocation =
- new HelpLocation(MultiFunctionComparisonPanel.HELP_TOPIC, "Add_To_Comparison");
- setHelpLocation(helpLocation);
-
- KeyBindingData data = new KeyBindingData('A', InputEvent.SHIFT_DOWN_MASK);
- setKeyBindingData(data);
- }
-
- @Override
- public boolean isEnabledForContext(ActionContext context) {
- return context.getComponentProvider() instanceof FunctionComparisonProvider;
- }
-
- @Override
- public void actionPerformed(ActionContext context) {
- FunctionComparisonProvider provider =
- (FunctionComparisonProvider) context.getComponentProvider();
- Program currentProgram = programManagerService.getCurrentProgram();
- FunctionTableModel model = new FunctionTableModel(tool, currentProgram);
- model.reload(programManagerService.getCurrentProgram());
-
- TableSelectionDialog diag = new TableSelectionDialog<>(
- "Select Functions: " + currentProgram.getName(), model, true);
- tool.showDialog(diag);
- List rows = diag.getSelectionItems();
- if (CollectionUtils.isBlank(rows)) {
- return; // the table chooser can return null if the operation was cancelled
- }
-
- Set functions =
- rows.stream().map(row -> row.getFunction()).collect(Collectors.toSet());
- comparisonService.compareFunctions(new HashSet<>(functions), provider);
- }
-}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/PreviousFunctionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/PreviousFunctionAction.java
deleted file mode 100644
index ece250013a..0000000000
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/PreviousFunctionAction.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/* ###
- * IP: GHIDRA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package ghidra.app.plugin.core.functioncompare.actions;
-
-import java.awt.Component;
-import java.awt.event.InputEvent;
-
-import javax.swing.Icon;
-import javax.swing.JComboBox;
-
-import docking.ActionContext;
-import docking.ComponentProvider;
-import docking.action.*;
-import generic.theme.GIcon;
-import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonPanel;
-import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonProvider;
-import ghidra.program.model.listing.Function;
-import ghidra.util.HelpLocation;
-
-/**
- * Displays the previous function in the function comparison panel. If
- * already at the beginning of the list, the action will not be enabled.
- */
-public class PreviousFunctionAction extends DockingAction {
-
- private static final String FUNCTION_NAVIGATE_GROUP = "A9_FunctionNavigate";
- private static final Icon PREVIOUS_FUNCTION_ICON =
- new GIcon("icon.plugin.functioncompare.function.previous");
-
- /**
- * Constructor
- *
- * @param provider the function comparison provider
- */
- public PreviousFunctionAction(MultiFunctionComparisonProvider provider) {
- super("Compare Previous Function", provider.getOwner());
-
- setKeyBindingData(
- new KeyBindingData('P', InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
- setDescription("Compare the previous function for the side with focus.");
- setPopupMenuData(new MenuData(new String[] { "Compare The Previous Function" },
- PREVIOUS_FUNCTION_ICON, FUNCTION_NAVIGATE_GROUP));
-
- ToolBarData newToolBarData =
- new ToolBarData(PREVIOUS_FUNCTION_ICON, FUNCTION_NAVIGATE_GROUP);
- setToolBarData(newToolBarData);
-
- HelpLocation helpLocation =
- new HelpLocation(MultiFunctionComparisonPanel.HELP_TOPIC,
- "Navigate Previous");
- setHelpLocation(helpLocation);
- }
-
- @Override
- public boolean isEnabledForContext(ActionContext context) {
- if (!(context.getComponentProvider() instanceof MultiFunctionComparisonProvider)) {
- return false;
- }
- MultiFunctionComparisonProvider provider =
- (MultiFunctionComparisonProvider) context.getComponentProvider();
-
- Component comp = provider.getComponent();
- if (!(comp instanceof MultiFunctionComparisonPanel)) {
- return false;
- }
-
- MultiFunctionComparisonPanel panel = (MultiFunctionComparisonPanel) comp;
- JComboBox focusedComponent = panel.getFocusedComponent();
- return focusedComponent.getSelectedIndex() > 0;
- }
-
- @Override
- public void actionPerformed(ActionContext context) {
- ComponentProvider provider = context.getComponentProvider();
- MultiFunctionComparisonPanel panel = (MultiFunctionComparisonPanel) provider.getComponent();
- JComboBox focusedComponent = panel.getFocusedComponent();
- focusedComponent.setSelectedIndex(focusedComponent.getSelectedIndex() - 1);
- }
-}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/RemoveFunctionsAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/RemoveFunctionsAction.java
deleted file mode 100644
index cf390a5fef..0000000000
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/RemoveFunctionsAction.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/* ###
- * IP: GHIDRA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package ghidra.app.plugin.core.functioncompare.actions;
-
-import java.awt.Component;
-import java.awt.event.InputEvent;
-import java.util.Arrays;
-import java.util.HashSet;
-
-import javax.swing.Icon;
-import javax.swing.JComboBox;
-
-import docking.ActionContext;
-import docking.action.*;
-import generic.theme.GIcon;
-import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonPanel;
-import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonProvider;
-import ghidra.program.model.listing.Function;
-import ghidra.util.HelpLocation;
-
-/**
- * Removes the currently-selected function from the comparison panel. If no
- * functions are enabled, the action will be disabled.
- */
-public class RemoveFunctionsAction extends DockingAction {
-
- private static final String REMOVE_FUNCTION_GROUP = "A9_RemoveFunctions";
- private static final Icon REMOVE_FUNCTION_ICON =
- new GIcon("icon.plugin.functioncompare.function.remove");
-
- /**
- * Constructor
- *
- * @param provider the function comparison provider
- */
- public RemoveFunctionsAction(MultiFunctionComparisonProvider provider) {
- super("Remove Functions", provider.getOwner());
-
- setKeyBindingData(
- new KeyBindingData('R', InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
- setDescription("Removes function in the focused comparison panel");
- setPopupMenuData(new MenuData(new String[] { "Remove Function" },
- REMOVE_FUNCTION_ICON, REMOVE_FUNCTION_GROUP));
-
- ToolBarData newToolBarData =
- new ToolBarData(REMOVE_FUNCTION_ICON, REMOVE_FUNCTION_GROUP);
- setToolBarData(newToolBarData);
-
- HelpLocation helpLocation =
- new HelpLocation(MultiFunctionComparisonPanel.HELP_TOPIC, "Remove_From_Comparison");
- setHelpLocation(helpLocation);
- }
-
- @Override
- public boolean isEnabledForContext(ActionContext context) {
- if (!(context.getComponentProvider() instanceof MultiFunctionComparisonProvider)) {
- return false;
- }
- MultiFunctionComparisonProvider provider =
- (MultiFunctionComparisonProvider) context.getComponentProvider();
-
- Component comp = provider.getComponent();
- if (!(comp instanceof MultiFunctionComparisonPanel)) {
- return false;
- }
- MultiFunctionComparisonPanel panel = (MultiFunctionComparisonPanel) comp;
- JComboBox focusedComponent = panel.getFocusedComponent();
-
- return focusedComponent.getSelectedIndex() != -1;
- }
-
- @Override
- public void actionPerformed(ActionContext context) {
- MultiFunctionComparisonProvider provider =
- (MultiFunctionComparisonProvider) context.getComponentProvider();
- JComboBox focusedComponent =
- ((MultiFunctionComparisonPanel) provider.getComponent()).getFocusedComponent();
- Function selectedFunction = (Function) focusedComponent.getSelectedItem();
- provider.removeFunctions(new HashSet<>(Arrays.asList(selectedFunction)));
- provider.contextChanged();
- }
-}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionTableModel.java
index e82faa8372..71ccbf1dbc 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionTableModel.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionTableModel.java
@@ -41,6 +41,8 @@ public class FunctionTableModel extends AddressBasedTableModel interfaceClass, Object service) {
if (interfaceClass == FunctionComparisonService.class) {
- compareFunctionsAction = new CompareFunctionsFromFunctionTableAction(tool, getName());
- tool.addLocalAction(provider, compareFunctionsAction);
- tool.contextChanged(provider);
+ provider.createCompareAction();
}
}
@Override
public void serviceRemoved(Class> interfaceClass, Object service) {
if (interfaceClass == FunctionComparisonService.class) {
- tool.removeLocalAction(provider, compareFunctionsAction);
- compareFunctionsAction = null;
+ provider.removeCompareAction();
}
}
@@ -175,17 +165,6 @@ public class FunctionWindowPlugin extends ProgramPlugin {
return currentProgram;
}
- private void createActions() {
- DockingAction action = new SelectionNavigationAction(this, provider.getTable());
- tool.addLocalAction(provider, action);
-
- selectAction = new MakeProgramSelectionAction(this, provider.getTable());
- tool.addLocalAction(provider, selectAction);
-
- // note that the compare functions action is only added when the compare functions service
- // is added to the tool
- }
-
void showFunctions() {
provider.showFunctions();
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowProvider.java
index 27d9244b33..85f8a35f51 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowProvider.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functionwindow/FunctionWindowProvider.java
@@ -18,20 +18,24 @@ package ghidra.app.plugin.core.functionwindow;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.MouseEvent;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.*;
import javax.swing.*;
import javax.swing.table.*;
import docking.ActionContext;
import docking.DefaultActionContext;
+import docking.action.DockingAction;
+import docking.action.builder.ActionBuilder;
import generic.theme.GIcon;
+import ghidra.app.context.FunctionSupplierContext;
+import ghidra.app.services.FunctionComparisonService;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.util.HelpLocation;
import ghidra.util.table.*;
+import ghidra.util.table.actions.MakeProgramSelectionAction;
/**
* Provider that displays all functions in the selected program
@@ -39,6 +43,7 @@ import ghidra.util.table.*;
public class FunctionWindowProvider extends ComponentProviderAdapter {
public static final Icon ICON = new GIcon("icon.plugin.functionwindow.provider");
+ private static final Icon COMPARISON_ICON = new GIcon("icon.plugin.functioncompare.new");
private FunctionWindowPlugin plugin;
private GhidraTable functionTable;
@@ -48,6 +53,8 @@ public class FunctionWindowProvider extends ComponentProviderAdapter {
private GhidraTableFilterPanel tableFilterPanel;
private GhidraThreadedTablePanel threadedTablePanel;
+ private DockingAction compareAction;
+
/**
* Constructor
*
@@ -62,6 +69,41 @@ public class FunctionWindowProvider extends ComponentProviderAdapter {
tool = plugin.getTool();
mainPanel = createWorkPanel();
tool.addComponentProvider(this, false);
+ createActions();
+ }
+
+ private void createActions() {
+ addLocalAction(new SelectionNavigationAction(plugin.getName(), getTable()));
+ addLocalAction(new MakeProgramSelectionAction(plugin, getTable()));
+ }
+
+ void createCompareAction() {
+ compareAction = new ActionBuilder("Compare Functions", plugin.getName())
+ .description("Create Function Comparison")
+ .helpLocation(new HelpLocation("FunctionComparison", "Function_Comparison"))
+ .toolBarIcon(COMPARISON_ICON)
+ .toolBarGroup("Comparison")
+ .enabledWhen(c -> functionTable.getSelectedRowCount() > 1)
+ .onAction(c -> compareSelectedFunctions())
+ .buildAndInstallLocal(this);
+ }
+
+ void removeCompareAction() {
+ tool.removeLocalAction(this, compareAction);
+ }
+
+ private void compareSelectedFunctions() {
+ Set functions = new HashSet<>();
+ int[] selectedRows = functionTable.getSelectedRows();
+
+ List functionRowObjects = functionModel.getRowObjects(selectedRows);
+ for (FunctionRowObject functionRowObject : functionRowObjects) {
+ Function rowFunction = functionRowObject.getFunction();
+ functions.add(rowFunction);
+ }
+
+ FunctionComparisonService service = getTool().getService(FunctionComparisonService.class);
+ service.createComparison(functions);
}
@Override
@@ -76,7 +118,7 @@ public class FunctionWindowProvider extends ComponentProviderAdapter {
@Override
public ActionContext getActionContext(MouseEvent event) {
- return new DefaultActionContext(this, functionTable);
+ return new FunctionWindowActionContext();
}
@Override
@@ -231,4 +273,32 @@ public class FunctionWindowProvider extends ComponentProviderAdapter {
public boolean isTransient() {
return false;
}
+
+ private class FunctionWindowActionContext extends DefaultActionContext
+ implements FunctionSupplierContext {
+
+ FunctionWindowActionContext() {
+ super(FunctionWindowProvider.this, functionTable);
+ }
+
+ @Override
+ public boolean hasFunctions() {
+ return functionTable.getSelectedRowCount() > 0;
+ }
+
+ @Override
+ public Set getFunctions() {
+ Set functions = new HashSet<>();
+ int[] selectedRows = functionTable.getSelectedRows();
+ if (selectedRows.length == 0) {
+ return Collections.emptySet();
+ }
+ List functionRowObjects = functionModel.getRowObjects(selectedRows);
+ for (FunctionRowObject functionRowObject : functionRowObjects) {
+ Function rowFunction = functionRowObject.getFunction();
+ functions.add(rowFunction);
+ }
+ return functions;
+ }
+ }
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/AbstractFunctionComparisonModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/AbstractFunctionComparisonModel.java
new file mode 100644
index 0000000000..0adbff1a18
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/AbstractFunctionComparisonModel.java
@@ -0,0 +1,95 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.services;
+
+import java.util.*;
+
+import ghidra.app.plugin.core.functioncompare.FunctionComparisonModelListener;
+import ghidra.program.model.listing.Function;
+import ghidra.util.datastruct.Duo;
+import ghidra.util.datastruct.Duo.Side;
+
+/**
+ * Base class for implementers of the FunctionComparisonModel. Provides listener support and
+ * tracking for the selected function for each side.
+ */
+public abstract class AbstractFunctionComparisonModel implements FunctionComparisonModel {
+ public static Comparator FUNCTION_COMPARATOR = new FunctionComparator();
+ private List listeners = new ArrayList<>();
+ protected Duo activeFunctions = new Duo<>();
+
+ @Override
+ public void addFunctionComparisonModelListener(FunctionComparisonModelListener listener) {
+ listeners.add(listener);
+ }
+
+ @Override
+ public void removeFunctionComparisonModelListener(FunctionComparisonModelListener listener) {
+ listeners.remove(listener);
+ }
+
+ @Override
+ public boolean setActiveFunction(Side side, Function function) {
+ if (activeFunctions.get(side) == function) {
+ return false;
+ }
+ if (!containsFunction(side, function)) {
+ return false;
+ }
+ activeFunctions = activeFunctions.with(side, function);
+ fireActiveFunctionChanged(side, function);
+ return true;
+ }
+
+ @Override
+ public Function getActiveFunction(Side side) {
+ return activeFunctions.get(side);
+ }
+
+ private void fireActiveFunctionChanged(Side side, Function function) {
+ listeners.forEach(l -> l.activeFunctionChanged(side, function));
+ }
+
+ protected void fireModelDataChanged() {
+ listeners.forEach(l -> l.modelDataChanged());
+ }
+
+ protected abstract boolean containsFunction(Side side, Function function);
+
+ /**
+ * Orders functions by program path and then name and then address
+ */
+ private static class FunctionComparator implements Comparator {
+
+ @Override
+ public int compare(Function o1, Function o2) {
+ String o1Path = o1.getProgram().getDomainFile().getPathname();
+ String o2Path = o2.getProgram().getDomainFile().getPathname();
+
+ String o1Name = o1.getName();
+ String o2Name = o2.getName();
+
+ if (o1Path.equals(o2Path)) {
+ if (o1Name.equals(o2Name)) {
+ return o1.getEntryPoint().compareTo(o2.getEntryPoint());
+ }
+ return o1Name.compareTo(o2Name);
+ }
+
+ return o1Path.compareTo(o2Path);
+ }
+ }
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/DefaultFunctionComparisonModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/DefaultFunctionComparisonModel.java
new file mode 100644
index 0000000000..ef065d1497
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/DefaultFunctionComparisonModel.java
@@ -0,0 +1,131 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.services;
+
+import static ghidra.util.datastruct.Duo.Side.*;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+import ghidra.program.model.listing.Function;
+import ghidra.program.model.listing.Program;
+import ghidra.util.datastruct.Duo;
+import ghidra.util.datastruct.Duo.Side;
+
+/**
+ * Basic FunctionComparisonModel where a set of functions can be compared with each other
+ */
+public class DefaultFunctionComparisonModel extends AbstractFunctionComparisonModel {
+ private Set functions = new HashSet<>();
+
+ public DefaultFunctionComparisonModel(Collection functions) {
+ this.functions.addAll(functions);
+ List orderedFunctions = getOrderedFunctions();
+ if (orderedFunctions.size() == 1) {
+ setActiveFunction(LEFT, orderedFunctions.get(0));
+ setActiveFunction(RIGHT, orderedFunctions.get(0));
+ }
+ else if (orderedFunctions.size() > 1) {
+ setActiveFunction(LEFT, orderedFunctions.get(0));
+ setActiveFunction(RIGHT, orderedFunctions.get(1));
+ }
+ }
+
+ public DefaultFunctionComparisonModel(Function... functions) {
+ this(Arrays.asList(functions));
+ }
+
+ @Override
+ public List getFunctions(Side side) {
+ return getOrderedFunctions();
+ }
+
+ @Override
+ public void removeFunction(Function function) {
+ removeFunctions(Set.of(function));
+ }
+
+ @Override
+ public void removeFunctions(Collection functionsToRemove) {
+ int beforeSize = functions.size();
+ functions.removeAll(functionsToRemove);
+ int afterSize = functions.size();
+ if (beforeSize != afterSize) {
+ fixupActiveFunctions();
+ fireModelDataChanged();
+ }
+ }
+
+ @Override
+ public void removeFunctions(Program program) {
+ Set functionsToRemove = functions.stream()
+ .filter(f -> f.getProgram().equals(program))
+ .collect(Collectors.toSet());
+
+ removeFunctions(functionsToRemove);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return functions.isEmpty();
+ }
+
+ public void addFunctions(Collection additionalFunctions) {
+ if (additionalFunctions.isEmpty()) {
+ return;
+ }
+ functions.addAll(additionalFunctions);
+ fireModelDataChanged();
+ setActiveFunction(RIGHT, additionalFunctions.iterator().next());
+ }
+
+ public void addFunction(Function function) {
+ addFunctions(List.of(function));
+ }
+
+ @Override
+ protected boolean containsFunction(Side side, Function function) {
+ return functions.contains(function);
+ }
+
+ private List getOrderedFunctions() {
+ List functionsList = new ArrayList<>(functions);
+ Collections.sort(functionsList, FUNCTION_COMPARATOR);
+ return functionsList;
+ }
+
+ private void fixupActiveFunctions() {
+ Function left = getActiveFunction(LEFT);
+ Function right = getActiveFunction(RIGHT);
+ boolean containsLeft = functions.contains(left);
+ boolean containsRight = functions.contains(right);
+ if (containsLeft && containsRight) {
+ return;
+ }
+
+ Function firstFunction = functions.isEmpty() ? null : getOrderedFunctions().get(0);
+
+ if (!containsLeft) {
+ left = firstFunction;
+ }
+ if (!containsRight) {
+ right = firstFunction;
+ }
+
+ activeFunctions = new Duo<>(left, right);
+ }
+
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonModel.java
index 1e750187f0..addc8f6ef9 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonModel.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonModel.java
@@ -18,24 +18,14 @@
*/
package ghidra.app.services;
-import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-import org.apache.commons.collections4.CollectionUtils;
-
-import ghidra.app.plugin.core.functioncompare.FunctionComparison;
import ghidra.app.plugin.core.functioncompare.FunctionComparisonModelListener;
import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
-import ghidra.util.Msg;
-import ghidra.util.task.TaskLauncher;
+import ghidra.util.datastruct.Duo.Side;
/**
* A collection of {@link FunctionComparison function comparison}
@@ -49,348 +39,70 @@ import ghidra.util.task.TaskLauncher;
* Note: Subscribers may register to be informed of changes to this model via the
* {@link FunctionComparisonModelListener comparison model listener} interface.
*/
-public class FunctionComparisonModel {
-
- private List comparisons = new ArrayList<>();
- private List listeners = new ArrayList<>();
+public interface FunctionComparisonModel {
/**
- * Adds the given subscriber to the list of those to be notified of model
- * changes
+ * Adds the given listener to the list of those to be notified of model changes.
*
- * @param listener the model change subscriber
+ * @param listener the listener to add
*/
- public void addFunctionComparisonModelListener(FunctionComparisonModelListener listener) {
- listeners.add(listener);
- }
+ public void addFunctionComparisonModelListener(FunctionComparisonModelListener listener);
/**
- * Returns a list of all comparisons in the model, in sorted order by
- * source function name
+ * Removes the given listener from the list of those to be notified of model changes.
*
- * @return a list of all comparisons in the model
+ * @param listener the listener to remove
*/
- public List getComparisons() {
- List toReturn = new ArrayList<>();
- toReturn.addAll(comparisons);
- Collections.sort(toReturn);
- return toReturn;
- }
+ public void removeFunctionComparisonModelListener(FunctionComparisonModelListener listener);
/**
- * Replaces the current model with the comparisons provided
- *
- * @param comparisons the new comparison model
+ * Sets the function for the given side. The function must be one of the functions from that
+ * side's set of functions
+ * @param side the side to set the function for
+ * @param function the function so set for the given side
+ * @return true if the function was made active or false if the function does not exist for the
+ * given side
*/
- public void setComparisons(List comparisons) {
- this.comparisons = comparisons;
- }
+ public boolean setActiveFunction(Side side, Function function);
/**
- * Adds a single comparison to the model
- *
- * @param comparison the comparison to add
+ * Returns the active (selected) function for the given side.
+ * @param side the side to get the active function for
+ * @return the active function for the given side
*/
- public void addComparison(FunctionComparison comparison) {
- comparisons.add(comparison);
- }
+ public Function getActiveFunction(Side side);
/**
- * Returns a list of all targets in the model (across all comparisons) for
- * a given source function
- *
- * @param source the source function
- * @return list of associated target functions
+ * Returns the list of all functions on the given side that could be made active.
+ * @param side the side to get functions for
+ * @return the list of all functions on the given side that could be made active
*/
- public Set getTargets(Function source) {
- Set targets = new HashSet<>();
- for (FunctionComparison fc : comparisons) {
- if (fc.getSource().equals(source)) {
- targets.addAll(fc.getTargets());
- }
- }
-
- return targets;
- }
+ public List getFunctions(Side side);
/**
- * Updates the model with a set of functions to compare. This will add the
- * functions to any existing {@link FunctionComparison comparisons} in the
- * model and create new comparisons for functions not represented.
- *
- * Note: It is assumed that when using this method, all functions can be
- * compared with all other functions; meaning each function will be added as
- * both a source AND a target. To specify a specific source/target
- * relationship, use {@link #compareFunctions(Function, Function)}.
- *
- * @param functions the set of functions to compare
- */
- public void compareFunctions(Set functions) {
- if (CollectionUtils.isEmpty(functions)) {
- return; // not an error, just return
- }
-
- addToExistingComparisons(functions);
- createNewComparisons(functions);
-
- fireModelChanged();
- }
-
- /**
- * Updates the model with two sets of functions to compare. This will add the
- * functions to any existing {@link FunctionComparison comparisons} in the
- * model and create new comparisons for functions not represented.
- *
- * Note: It is assumed that when using this method, all source functions can be
- * compared to all destination functions; meaning all functions in the source function set will
- * be added as sources, and all functions in the destination function set will be added as targets.
- *
- * @param sourceFunctions
- * @param destinationFunctions
- */
- public void compareFunctions(Set sourceFunctions,
- Set destinationFunctions) {
- if (CollectionUtils.isEmpty(sourceFunctions) ||
- CollectionUtils.isEmpty(destinationFunctions)) {
- return; // not an error, just return
- }
-
- for (Function f : sourceFunctions) {
- FunctionComparison comparison = new FunctionComparison();
-
- comparison.setSource(f);
- comparison.addTargets(destinationFunctions);
- comparisons.add(comparison);
- }
- fireModelChanged();
- }
-
- /**
- * Compares two functions. If a comparison already exists in the model for
- * the given source, the target will simply be added to it; otherwise a
- * new comparison will be created.
- *
- * @param source the source function
- * @param target the target function
- */
- public void compareFunctions(Function source, Function target) {
- FunctionComparison fc = getOrCreateComparison(source);
- fc.addTarget(target);
-
- fireModelChanged();
- }
-
- /**
- * Removes the given function from all comparisons in the model, whether
- * stored as a source or target
+ * Removes the given function from both sides of the comparison.
*
* @param function the function to remove
*/
- public void removeFunction(Function function) {
- doRemoveFunction(function);
- fireModelChanged();
- }
+ public void removeFunction(Function function);
/**
- * Removes all the given functions from all comparisons in the model
+ * Removes all the given functions from both sides of the comparison.
+ *
* @param functions the functions to remove
*/
- public void removeFunctions(Collection functions) {
- for (Function function : functions) {
- doRemoveFunction(function);
- }
- fireModelChanged();
- }
-
- private void doRemoveFunction(Function function) {
- List comparisonsToRemove = new ArrayList<>();
-
- Iterator iter = comparisons.iterator();
- while (iter.hasNext()) {
-
- // First remove any comparisons that have the function as its source
- FunctionComparison fc = iter.next();
- if (fc.getSource().equals(function)) {
- comparisonsToRemove.add(fc);
- continue;
- }
-
- // Now remove the function from the target list (if it's there)
- fc.getTargets().remove(function);
- }
-
- comparisons.removeAll(comparisonsToRemove);
- }
+ public void removeFunctions(Collection functions);
/**
- * Removes all functions in the model that come from the given
- * program
- *
- * @param program the program to remove functions from
+ * Removes all functions from the given program from both sides of the comparison
+ * @param program that program whose functions should be removed from this model
*/
- public void removeFunctions(Program program) {
- Set allFunctions = getSourceFunctions();
- allFunctions.addAll(getTargetFunctions());
-
- Set functionsToRemove = allFunctions.stream()
- .filter(f -> f.getProgram().equals(program))
- .collect(Collectors.toSet());
-
- removeFunctions(functionsToRemove);
- }
+ public void removeFunctions(Program program);
/**
- * Returns all source functions in the model
- *
- * @return a set of all source functions
+ * Returns true if the model has no function to compare.
+ * @return true if the model has no functions to compare
*/
- public Set getSourceFunctions() {
- Set items = new HashSet<>();
- for (FunctionComparison fc : comparisons) {
- items.add(fc.getSource());
- }
- return items;
- }
+ public boolean isEmpty();
- /**
- * Returns all target functions in the model
- *
- * @return a set of all target functions
- */
- public Set getTargetFunctions() {
- Set items = new HashSet<>();
- Iterator iter = comparisons.iterator();
- while (iter.hasNext()) {
- FunctionComparison fc = iter.next();
- items.addAll(fc.getTargets());
- }
-
- return items;
- }
-
- /**
- * Returns a set of all target functions for a given source
- *
- * @param source the source function to search for
- * @return the set of associated target functions
- */
- public Set getTargetFunctions(Function source) {
- Set items = new HashSet<>();
- Iterator iter = comparisons.iterator();
- while (iter.hasNext()) {
- FunctionComparison fc = iter.next();
- if (!fc.getSource().equals(source)) {
- continue;
- }
- items.addAll(fc.getTargets());
- }
-
- return items;
- }
-
- /**
- * Creates a {@link FunctionComparison comparison} for each function
- * given, such that each comparison will have every other function as its
- * targets. For example, given three functions, f1, f2, and f3, this is what the
- * model will look like after this call:
- * comparison 1:
- *
- * - source: f1
- * - targets: f2, f3
- *
- * comparison 2:
- *
- * - source: f2
- * - targets: f1, f3
- *
- * comparison 3:
- *
- * - source: f3
- * - targets: f1, f2
- *
- *
- * If this model already contains a comparison for a given function
- * (meaning the model contains a comparison with the function as the
- * source) then that function is skipped.
- *
- * Note that this could be a long-running process if many (thousands)
- * functions are chosen to compare, hence the monitored task. In practice
- * this should never be the case, as users will likely not be
- * comparing more than a handful of functions at any given time.
- *
- * @param functions the set of functions to create comparisons for
- */
- private void createNewComparisons(Set functions) {
-
- TaskLauncher.launchModal("Creating Comparisons", (monitor) -> {
-
- // Remove any functions that already have an comparison in the
- // model; these will be ignored
- functions.removeIf(f -> comparisons.stream().anyMatch(fc -> f.equals(fc.getSource())));
-
- monitor.setIndeterminate(false);
- monitor.setMessage("Creating new comparisons");
- monitor.initialize(functions.size());
-
- // Save off all the existing targets in the model; these have to be
- // added to any new comparisons
- Set existingTargets = getTargetFunctions();
-
- // Now loop over the given functions and create new comparisons
- for (Function f : functions) {
- if (monitor.isCancelled()) {
- Msg.info(this, "Function comparison operation cancelled");
- return;
- }
-
- FunctionComparison fc = new FunctionComparison();
- fc.setSource(f);
- fc.addTargets(functions);
- fc.addTargets(existingTargets);
- comparisons.add(fc);
- monitor.incrementProgress(1);
- }
- });
-
- }
-
- /**
- * Searches the model for a comparison that has the given function as its
- * source; if not found, a new comparison is created
- *
- * @param source the source function to search for
- * @return a function comparison object for the given source
- */
- private FunctionComparison getOrCreateComparison(Function source) {
- for (FunctionComparison fc : comparisons) {
- if (fc.getSource().equals(source)) {
- return fc;
- }
- }
-
- FunctionComparison fc = new FunctionComparison();
- fc.setSource(source);
- comparisons.add(fc);
- return fc;
- }
-
- /**
- * Adds a given set of functions to every target list in every
- * comparison in the model
- *
- * @param functions the functions to add
- */
- private void addToExistingComparisons(Set functions) {
- for (FunctionComparison fc : comparisons) {
- fc.getTargets().addAll(functions);
- }
- }
-
- /**
- * Sends model-change notifications to all subscribers. The updated model
- * is sent in the callback.
- */
- private void fireModelChanged() {
- listeners.forEach(l -> l.modelChanged(comparisons));
- }
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonService.java
index c2926ba56d..05d2f769a5 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonService.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonService.java
@@ -15,125 +15,77 @@
*/
package ghidra.app.services;
-import java.util.Set;
+import java.util.Collection;
import ghidra.app.plugin.core.functioncompare.FunctionComparisonPlugin;
import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider;
import ghidra.framework.plugintool.ServiceInfo;
import ghidra.program.model.listing.Function;
+import utility.function.Callback;
/**
- * Allows users to create comparisons between functions which will be displayed
+ * Service interface to create comparisons between functions which will be displayed
* side-by-side in a {@link FunctionComparisonProvider}. Each side in the
* display will allow the user to select one or more functions
*
- * Concurrent usage: All work performed by this service will be done on the Swing thread.
- * Further, all calls that do not return a value will be run immediately if the caller is on
- * the Swing thread; otherwise, the work will be done on the Swing thread at a later time.
- * Contrastingly, any method on this interface that returns a value will be run immediately,
- * regardless of whether the call is on the Swing thread. Thus, the methods that return a value
- * will always be blocking calls; methods that do not return a value may or may not block,
- * depending on the client's thread.
+ *
Concurrent usage: All work performed by this service will be done asynchronously on the
+ * Swing thread.
*/
@ServiceInfo(defaultProvider = FunctionComparisonPlugin.class)
public interface FunctionComparisonService {
/**
- * Creates a comparison provider that allows comparisons between a functions.
- *
- * @return the new comparison provider
- */
- public FunctionComparisonProvider createFunctionComparisonProvider();
-
- /**
- * Creates a comparison between a set of functions, where each function
- * in the list can be compared against any other.
- *
- * eg: Given a set of 3 functions (f1, f2, f3), the comparison dialog will
- * allow the user to display either f1, f2 or f3 on EITHER side of the
- * comparison.
- *
- * Note that this method will always create a new provider; if you want to
- * add functions to an existing comparison, use
- * {@link #compareFunctions(Set, FunctionComparisonProvider) this}
- * variant that takes a provider.
- *
+ * Creates a function comparison window where each side can display any of the given functions.
* @param functions the functions to compare
- * @return the new comparison provider
*/
- public FunctionComparisonProvider compareFunctions(Set functions);
+ public void createComparison(Collection functions);
/**
- * Creates a comparison between two sets of functions, where all the functions in source list can
- * be compared against all functions in the destination list.
- *
- * Note that this method will always create a new provider.
- *
- * @param sourceFunctions
- * @param destinationFunctions
- * @return the new comparison provider
+ * Creates a function comparison window for the two given functions. Each side can select
+ * either function, but initially the left function will be shown in the left panel and the
+ * right function will be shown in the right panel.
+ * @param left the function to initially show in the left panel
+ * @param right the function to initially show in the right panel
*/
- public FunctionComparisonProvider compareFunctions(Set sourceFunctions,
- Set destinationFunctions);
+ public void createComparison(Function left, Function right);
/**
- * Creates a comparison between two functions, where the source function
- * will be shown on the left side of the comparison dialog and the target
- * on the right.
- *
- * Note that this will always create a new provider; if you want to add
- * functions to an existing comparison, use
- * {@link #compareFunctions(Function, Function, FunctionComparisonProvider) this}
- * variant that takes a provider.
- *
- * @param source a function in the comparison
- * @param target a function in the comparison
- * @return the new comparison provider
+ * Adds the given function to each side the last created comparison window or creates
+ * a new comparison if none exists. The right panel will be changed to show the new function.
+ * Note that this method will not add to any provider created via the
+ * {@link #createCustomComparison(FunctionComparisonModel, Callback)}. Those providers
+ * are private to the client that created them. They take in a model, so if the client wants
+ * to add to those providers, it must retain a handle to the model and add functions directly
+ * to the model.
+ * @param function the function to be added to the last function comparison window
*/
- public FunctionComparisonProvider compareFunctions(Function source, Function target);
+ public void addToComparison(Function function);
/**
- * Creates a comparison between a set of functions, adding them to the
- * given comparison provider. Each function in the given set will be added
- * to both sides of the comparison, allowing users to compare any functions
- * in the existing provider with the new set.
- *
- * @see #compareFunctions(Set)
- * @param functions the functions to compare
- * @param provider the provider to add the comparisons to
+ * Adds the given functions to each side the last created comparison window or creates
+ * a new comparison if none exists. The right panel will be change to show a random function
+ * from the new functions. Note that this method will not add to any comparison windows created
+ * with a custom comparison model.
+ * @param functions the functions to be added to the last function comparison window
*/
- public void compareFunctions(Set functions,
- FunctionComparisonProvider provider);
+ public void addToComparison(Collection functions);
/**
- * Creates a comparison between two functions and adds it to a given
- * comparison provider. The existing comparisons in the provider will not
- * be affected, unless the provider already contains a comparison with
- * the same source function; in this case the given target will be added
- * to that comparisons' list of targets.
- *
- * @see #compareFunctions(Function, Function)
- * @param source a function in the comparison
- * @param target a function in the comparison
- * @param provider the provider to add the comparison to
+ * Creates a custom function comparison window. The default model shows all functions on both
+ * sides. This method allows the client to provide a custom comparison model which can have
+ * more control over what functions can be selected on each side. One such custom model
+ * is the {@link MatchedFunctionComparisonModel} which gives a unique set of functions on the
+ * right side, depending on what is selected on the left side.
+ *
+ * Note that function comparison windows created with this method are considered private for the
+ * client and are not available to be chosen for either of the above "add to" service methods.
+ * Instead, the client that uses this model can retain a handle to the model and add or remove
+ * functions directly on the model.
+ *
+ * @param model the custom function comparison model
+ * @param closeListener an optional callback if the client wants to be notified when the
+ * associated function comparison windows is closed.
*/
- public void compareFunctions(Function source, Function target,
- FunctionComparisonProvider provider);
-
- /**
- * Removes a given function from all comparisons across all comparison
- * providers
- *
- * @param function the function to remove
- */
- public void removeFunction(Function function);
-
- /**
- * Removes a given function from all comparisons in the given comparison
- * provider only
- *
- * @param function the function to remove
- * @param provider the comparison provider to remove functions from
- */
- public void removeFunction(Function function, FunctionComparisonProvider provider);
+ public void createCustomComparison(FunctionComparisonModel model,
+ Callback closeListener);
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/MatchedFunctionComparisonModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/MatchedFunctionComparisonModel.java
new file mode 100644
index 0000000000..1aa8b6c216
--- /dev/null
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/MatchedFunctionComparisonModel.java
@@ -0,0 +1,217 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ *
+ */
+package ghidra.app.services;
+
+import static ghidra.util.datastruct.Duo.Side.*;
+
+import java.util.*;
+import java.util.Map.Entry;
+
+import ghidra.program.model.listing.Function;
+import ghidra.program.model.listing.Program;
+import ghidra.util.datastruct.Duo;
+import ghidra.util.datastruct.Duo.Side;
+
+/**
+ * A FunctionComparisonModel comprised of matched pairs of source and target functions. Each
+ * source function has its own set of target functions that it can be compared with.
+ */
+public class MatchedFunctionComparisonModel extends AbstractFunctionComparisonModel {
+
+ private Map> sourceToTargetsMap = new HashMap<>();
+
+ /**
+ * Removes the given function from all comparisons in the model, whether
+ * stored as a source or target
+ *
+ * @param function the function to remove
+ */
+ @Override
+ public void removeFunction(Function function) {
+ if (doRemoveFunction(function)) {
+ fixupActiveFunctions();
+ fireModelDataChanged();
+ }
+ }
+
+ /**
+ * Removes all the given functions from all comparisons in the model
+ * @param functions the functions to remove
+ */
+ @Override
+ public void removeFunctions(Collection functions) {
+ boolean didRemove = false;
+ for (Function function : functions) {
+ didRemove |= doRemoveFunction(function);
+ }
+ if (didRemove) {
+ fixupActiveFunctions();
+ fireModelDataChanged();
+ }
+ }
+
+ private boolean doRemoveFunction(Function function) {
+ return removeFunctionFromTargets(function) || removeFunctionFromSources(function);
+ }
+
+ private void fixupActiveFunctions() {
+ if (sourceToTargetsMap.isEmpty()) {
+ activeFunctions = new Duo<>();
+ return;
+ }
+
+ if (!containsFunction(LEFT, activeFunctions.get(LEFT))) {
+ Function newLeft = getFunctions(LEFT).get(0);
+ activeFunctions = activeFunctions.with(LEFT, newLeft);
+ }
+ if (!containsFunction(RIGHT, activeFunctions.get(RIGHT))) {
+ Function newRight = getFunctions(RIGHT).get(0);
+ activeFunctions = activeFunctions.with(RIGHT, newRight);
+ }
+ }
+
+ private boolean removeFunctionFromTargets(Function function) {
+ boolean didRemove = false;
+ Iterator it = sourceToTargetsMap.keySet().iterator();
+
+ while (it.hasNext()) {
+ Function source = it.next();
+ Set set = sourceToTargetsMap.get(source);
+ didRemove |= set.remove(function);
+ if (set.isEmpty()) {
+ it.remove();
+ }
+ }
+ return didRemove;
+ }
+
+ private boolean removeFunctionFromSources(Function function) {
+ return sourceToTargetsMap.remove(function) != null;
+ }
+
+ /**
+ * Removes all functions in the model that come from the given
+ * program
+ *
+ * @param program the program to remove functions from
+ */
+ @Override
+ public void removeFunctions(Program program) {
+ Set functionsToRemove = findFunctions(program);
+ removeFunctions(functionsToRemove);
+ }
+
+ private Set findFunctions(Program program) {
+ Set functions = new HashSet<>();
+ for (Entry> entry : sourceToTargetsMap.entrySet()) {
+ Function source = entry.getKey();
+ Set targets = entry.getValue();
+
+ if (source.getProgram() == program) {
+ functions.add(source);
+ }
+ for (Function function : targets) {
+ if (function.getProgram() == program) {
+ functions.add(function);
+ }
+ }
+ }
+ return functions;
+ }
+
+ @Override
+ public List getFunctions(Side side) {
+ if (side == LEFT) {
+ return getSourceFunctions();
+ }
+ return getTargetFunctions();
+ }
+
+ @Override
+ public boolean setActiveFunction(Side side, Function function) {
+ // If the right side changes, nothing special happens so let the super handle it.
+ // If the left side changes, the entire set of functions on the right will change, so
+ // we need special handling for that case
+ if (side == RIGHT) {
+ return super.setActiveFunction(side, function);
+ }
+
+ if (function == activeFunctions.get(LEFT)) {
+ return false; // function is already selected
+ }
+
+ if (!containsFunction(side, function)) {
+ return false;
+ }
+
+ activeFunctions = activeFunctions.with(side, function);
+ Function newRightSideFunction = getFunctions(RIGHT).get(0);
+ activeFunctions = activeFunctions.with(RIGHT, newRightSideFunction);
+
+ fireModelDataChanged();
+ return true;
+ }
+
+ private List getTargetFunctions() {
+ List targets = new ArrayList<>();
+
+ Function source = getActiveFunction(LEFT);
+ if (source != null) {
+ targets.addAll(sourceToTargetsMap.get(source));
+ }
+ Collections.sort(targets, FUNCTION_COMPARATOR);
+ return targets;
+ }
+
+ public List getSourceFunctions() {
+ List sourceFunctions = new ArrayList<>(sourceToTargetsMap.keySet());
+ Collections.sort(sourceFunctions, FUNCTION_COMPARATOR);
+ return sourceFunctions;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return sourceToTargetsMap.isEmpty();
+ }
+
+ /**
+ * Adds a new comparison to the model. If the sourceFunction already exists on the left side,
+ * then the target function will be added to that specific function's right side functions.
+ * Otherwise, the source function will be added to the left side the given target as its only
+ * right side function.
+ * @param sourceFunction the left side function to add
+ * @param targetFunction the right side function to add for that source function
+ */
+ public void addMatch(Function sourceFunction, Function targetFunction) {
+ Set targets =
+ sourceToTargetsMap.computeIfAbsent(sourceFunction, k -> new HashSet<>());
+ targets.add(targetFunction);
+ activeFunctions = new Duo<>(sourceFunction, targetFunction);
+ fireModelDataChanged();
+ }
+
+ @Override
+ protected boolean containsFunction(Side side, Function function) {
+ if (side == LEFT) {
+ return sourceToTargetsMap.containsKey(function);
+ }
+ return sourceToTargetsMap.get(activeFunctions.get(LEFT)).contains(function);
+ }
+
+}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/task/ProgramOpener.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/task/ProgramOpener.java
index b885f7cd19..b11636dbe7 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/task/ProgramOpener.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/task/ProgramOpener.java
@@ -101,8 +101,9 @@ public class ProgramOpener {
}
catch (IOException e) {
Msg.showError(this, null, "Program Open Failed",
- "Failed to open Ghidra URL: " + locator.getURL(), e);
+ "Failed to open Ghidra URL: " + locator.getURL());
}
+ return null;
}
return openProgram(locator, locator.getDomainFile(), monitor);
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonPanel.java
index 546ffb4904..3431c2021c 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonPanel.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonPanel.java
@@ -36,6 +36,7 @@ import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.util.HTMLUtilities;
+import ghidra.util.HelpLocation;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.classfinder.ExtensionPoint;
import ghidra.util.datastruct.Duo;
@@ -385,6 +386,8 @@ public abstract class CodeComparisonPanel extends JPanel
super(name + " Toggle Orientation", "FunctionComparison");
setDescription(
"Toggle the layout to be either side by side or one above the other");
+ setHelpLocation(
+ new HelpLocation("FunctionComparison", "Dual_" + name + "_Toggle_Orientation"));
setEnabled(true);
MenuData menuData =
new MenuData(new String[] { "Show " + name + " Side-by-Side" }, "Orientation");
diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsSlowTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsProviderTest.java
similarity index 53%
rename from Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsSlowTest.java
rename to Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsProviderTest.java
index a0e62a627a..cbf6985bd1 100644
--- a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsSlowTest.java
+++ b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsProviderTest.java
@@ -19,9 +19,10 @@ import static ghidra.util.datastruct.Duo.Side.*;
import static org.junit.Assert.*;
import java.awt.Window;
-import java.util.Date;
-import java.util.Set;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.swing.JComboBox;
import javax.swing.JPanel;
import org.junit.*;
@@ -32,8 +33,8 @@ import docking.widgets.dialogs.TableSelectionDialog;
import docking.widgets.table.GFilterTable;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.function.FunctionPlugin;
-import ghidra.app.plugin.core.functionwindow.FunctionRowObject;
-import ghidra.app.plugin.core.functionwindow.FunctionTableModel;
+import ghidra.app.services.FunctionComparisonModel;
+import ghidra.app.services.MatchedFunctionComparisonModel;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.ByteDataType;
@@ -42,12 +43,13 @@ import ghidra.program.model.listing.*;
import ghidra.program.util.ProgramLocation;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
+import ghidra.util.datastruct.Duo.Side;
/**
* Tests for the {@link FunctionComparisonPlugin function comparison plugin}
* that involve the GUI
*/
-public class CompareFunctionsSlowTest extends AbstractGhidraHeadedIntegrationTest {
+public class CompareFunctionsProviderTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env;
private Program program1;
@@ -79,75 +81,63 @@ public class CompareFunctionsSlowTest extends AbstractGhidraHeadedIntegrationTes
@Test
public void testRemoveLastItem() throws Exception {
- Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo);
- provider = compareFunctions(functions);
- runSwing(() -> plugin.removeFunction(foo, provider));
+ provider = compareFunctions(Set.of(foo));
+ assertTrue(provider.isVisible());
+ plugin.removeFunction(foo);
+ waitForSwing();
assertFalse(provider.isVisible());
}
@Test
public void testCloseProgram() throws Exception {
- Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar);
+ Set functions = Set.of(foo, bar);
provider = compareFunctions(functions);
- CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar);
+ checkFunctions(LEFT, foo, foo, bar);
+ checkFunctions(RIGHT, bar, foo, bar);
runSwing(() -> plugin.programClosed(program1));
+ waitForSwing();
- CompareFunctionsTestUtility.checkSourceFunctions(provider, bar);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, bar);
+ checkFunctions(LEFT, bar, bar);
+ checkFunctions(RIGHT, bar, bar);
runSwing(() -> plugin.programClosed(program2));
-
- CompareFunctionsTestUtility.checkSourceFunctions(provider);
+ waitForSwing();
+ assertFalse(provider.isVisible());
+ assertFalse(provider.isInTool());
}
@Test
public void testNextPreviousAction() {
- Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar);
+ Set functions = Set.of(foo, bar);
provider = compareFunctions(functions);
- // Must do this or there will be no "active" provider in the actions
- // initiated below
- clickComponentProvider(provider);
-
DockingActionIf nextAction = getAction(plugin, "Compare Next Function");
- DockingActionIf prevAction = getAction(plugin, "Compare Previous Function");
+ DockingActionIf previousAction = getAction(plugin, "Compare Previous Function");
ActionContext context = provider.getActionContext(null);
- assertTrue(nextAction.isEnabledForContext(context));
- assertFalse(prevAction.isEnabledForContext(context));
+ assertEnabled(nextAction, context);
+ assertNotEnabled(previousAction, context);
performAction(nextAction);
context = provider.getActionContext(null);
- assertFalse(nextAction.isEnabledForContext(context));
- assertTrue(prevAction.isEnabledForContext(context));
+ assertNotEnabled(nextAction, context);
+ assertEnabled(previousAction, context);
}
@Test
public void testNextPreviousActionSwitchPanelFocus() {
- Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar);
+ Set functions = Set.of(foo, bar);
provider = compareFunctions(functions);
-
- // Must do this or there will be no "active" provider in the actions
- // initiated below
- clickComponentProvider(provider);
-
DockingActionIf nextAction = getAction(plugin, "Compare Next Function");
- DockingActionIf prevAction = getAction(plugin, "Compare Previous Function");
+ DockingActionIf previousAction = getAction(plugin, "Compare Previous Function");
+ // left panel has focus, so nextAction should be enabled and previous should be disabled
ActionContext context = provider.getActionContext(null);
- assertTrue(nextAction.isEnabledForContext(context));
- assertFalse(prevAction.isEnabledForContext(context));
-
- performAction(nextAction);
-
- context = provider.getActionContext(null);
- assertFalse(nextAction.isEnabledForContext(context));
- assertTrue(prevAction.isEnabledForContext(context));
+ assertEnabled(nextAction, context);
+ assertNotEnabled(previousAction, context);
JPanel rightPanel =
provider.getComponent().getDualListingPanel().getListingPanel(RIGHT).getFieldPanel();
@@ -155,20 +145,17 @@ public class CompareFunctionsSlowTest extends AbstractGhidraHeadedIntegrationTes
waitForSwing();
provider.getComponent().updateActionEnablement();
+ // right panel has focus, so nextAction should be disabled and previous should be enabled
context = provider.getActionContext(null);
- assertTrue(nextAction.isEnabledForContext(context));
- assertFalse(prevAction.isEnabledForContext(context));
+ assertNotEnabled(nextAction, context);
+ assertEnabled(previousAction, context);
}
@Test
public void testOpenFunctionTableActionForAdd() {
- Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar);
+ Set functions = Set.of(foo, bar);
provider = compareFunctions(functions);
- // Must do this or the context for the action initiated below will be
- // for the listing, not the comparison provider
- clickComponentProvider(provider);
-
DockingActionIf openTableAction = getAction(plugin, "Add Functions To Comparison");
performAction(openTableAction, provider, false);
@@ -179,33 +166,28 @@ public class CompareFunctionsSlowTest extends AbstractGhidraHeadedIntegrationTes
@Test
public void testAddFunctionToExistingCompare() {
- Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo);
+ Set functions = Set.of(foo);
provider = compareFunctions(functions);
- // Must do this or there will be no "active" provider in the actions initiated below
- clickComponentProvider(provider);
-
- assertEquals(provider.getModel().getSourceFunctions().size(), 1);
- assertTrue(provider.getModel().getSourceFunctions().contains(foo));
+ assertEquals(provider.getModel().getFunctions(LEFT).size(), 1);
+ assertTrue(provider.getModel().getFunctions(LEFT).contains(foo));
DockingActionIf openTableAction = getAction(plugin, "Add Functions To Comparison");
performAction(openTableAction, provider, false);
- @SuppressWarnings("unchecked")
- TableSelectionDialog chooser =
+ TableSelectionDialog> chooser =
waitForDialogComponent(TableSelectionDialog.class);
- @SuppressWarnings("unchecked")
- GFilterTable table =
- (GFilterTable) getInstanceField("gFilterTable", chooser);
+
+ GFilterTable> table = (GFilterTable>) getInstanceField("gFilterTable", chooser);
waitForCondition(() -> table.getModel().getRowCount() == 2);
clickTableCell(table.getTable(), 1, 0, 1);
pressButtonByText(chooser, "OK");
waitForSwing();
- assertEquals(provider.getModel().getSourceFunctions().size(), 2);
- assertTrue(provider.getModel().getSourceFunctions().contains(foo));
- assertTrue(provider.getModel().getSourceFunctions().contains(bat));
+ assertEquals(provider.getModel().getFunctions(LEFT).size(), 2);
+ assertTrue(provider.getModel().getFunctions(LEFT).contains(foo));
+ assertTrue(provider.getModel().getFunctions(LEFT).contains(bat));
}
/**
@@ -215,29 +197,99 @@ public class CompareFunctionsSlowTest extends AbstractGhidraHeadedIntegrationTes
*/
@Test
public void testDeleteFunctionFromListing() {
- Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar);
+ Set functions = Set.of(foo, bar);
provider = compareFunctions(functions);
- assertEquals(provider.getModel().getSourceFunctions().size(), 2);
- assertTrue(provider.getModel().getSourceFunctions().contains(foo));
- assertTrue(provider.getModel().getSourceFunctions().contains(bar));
+ assertEquals(provider.getModel().getFunctions(LEFT).size(), 2);
+ assertTrue(provider.getModel().getFunctions(LEFT).contains(foo));
+ assertTrue(provider.getModel().getFunctions(LEFT).contains(bar));
- Address addr = program1.getAddressFactory().getAddress("10018cf");
- ProgramLocation loc = new ProgramLocation(program1, addr);
+ Address address = program1.getAddressFactory().getAddress("10018cf");
+ ProgramLocation loc = new ProgramLocation(program1, address);
cbPlugin.goTo(loc);
DockingActionIf deleteAction = getAction(functionPlugin, "Delete Function");
performAction(deleteAction, cbPlugin.getProvider().getActionContext(null), true);
waitForSwing();
- assertEquals(provider.getModel().getSourceFunctions().size(), 1);
- assertTrue(provider.getModel().getSourceFunctions().contains(bar));
+ assertEquals(provider.getModel().getFunctions(LEFT).size(), 1);
+ assertTrue(provider.getModel().getFunctions(LEFT).contains(bar));
+ }
+
+ @Test
+ public void testCustomComparison() {
+ MatchedFunctionComparisonModel model = new MatchedFunctionComparisonModel();
+ model.addMatch(foo, bar);
+ model.addMatch(bar, bat);
+ plugin.createCustomComparison(model, null);
+ waitForSwing();
+ provider = waitForComponentProvider(FunctionComparisonProvider.class);
+ assertEquals(model, provider.getModel());
+
+ setLeftFunction(foo);
+
+ assertEquals(model.getFunctions(LEFT).size(), 2);
+ assertTrue(model.getFunctions(LEFT).contains(foo));
+ assertTrue(model.getFunctions(LEFT).contains(bar));
+ assertEquals(model.getFunctions(RIGHT).size(), 1);
+ assertTrue(model.getFunctions(RIGHT).contains(bar));
+
+ setLeftFunction(bar);
+
+ assertEquals(model.getFunctions(RIGHT).size(), 1);
+ assertTrue(model.getFunctions(RIGHT).contains(bat));
+ }
+
+ private void setLeftFunction(Function function) {
+ FunctionComparisonPanel component = provider.getComponent();
+ JComboBox> combo = (JComboBox>) findComponentByName(component, "LEFTFunctionComboBox");
+ runSwing(() -> combo.setSelectedItem(function));
+ }
+
+ @Test
+ public void testCustomComparitorCloseCallack() {
+ final AtomicBoolean closed = new AtomicBoolean(false);
+ MatchedFunctionComparisonModel model = new MatchedFunctionComparisonModel();
+ model.addMatch(foo, bar);
+ model.addMatch(bar, bat);
+ plugin.createCustomComparison(model, () -> closed.set(true));
+ waitForSwing();
+ provider = waitForComponentProvider(FunctionComparisonProvider.class);
+ assertEquals(model, provider.getModel());
+
+ assertFalse(closed.get());
+ runSwing(() -> provider.closeComponent());
+ waitForSwing();
+ assertTrue(closed.get());
+
+ }
+
+ @Test
+ public void testAddToComparison() {
+ Set functions = Set.of(foo, bar);
+ provider = compareFunctions(functions);
+
+ checkFunctions(LEFT, foo, foo, bar);
+ checkFunctions(RIGHT, bar, foo, bar);
+
+ runSwing(() -> plugin.addToComparison(bat));
+ waitForSwing();
+
+ checkFunctions(LEFT, foo, foo, bar, bat);
+ checkFunctions(RIGHT, bat, foo, bar, bat);
+ }
+
+ private void assertEnabled(DockingActionIf action, ActionContext context) {
+ assertTrue(runSwing(() -> action.isEnabledForContext(context)));
+ }
+
+ private void assertNotEnabled(DockingActionIf action, ActionContext context) {
+ assertFalse(runSwing(() -> action.isEnabledForContext(context)));
}
private FunctionComparisonProvider compareFunctions(Set functions) {
- provider = runSwing(() -> plugin.compareFunctions(functions));
- provider.setVisible(true);
+ runSwing(() -> plugin.createComparison(functions));
waitForSwing();
- return provider;
+ return waitForComponentProvider(FunctionComparisonProvider.class);
}
/**
@@ -258,6 +310,17 @@ public class CompareFunctionsSlowTest extends AbstractGhidraHeadedIntegrationTes
return builder;
}
+ private void checkFunctions(Side side, Function activeFunction, Function... functions) {
+ Set funcs = Set.of(functions);
+
+ FunctionComparisonModel model = provider.getModel();
+ assertEquals(activeFunction, model.getActiveFunction(side));
+
+ List fcs = model.getFunctions(side);
+ assertEquals(fcs.size(), funcs.size());
+ assertTrue(fcs.containsAll(funcs));
+ }
+
/**
* Builds a program with 1 function
*/
diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsTest.java
deleted file mode 100644
index 549fe13448..0000000000
--- a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsTest.java
+++ /dev/null
@@ -1,412 +0,0 @@
-/* ###
- * IP: GHIDRA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package ghidra.app.plugin.core.functioncompare;
-
-import static org.junit.Assert.*;
-
-import java.util.Date;
-import java.util.Set;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import generic.test.AbstractGenericTest;
-import ghidra.app.services.FunctionComparisonModel;
-import ghidra.app.services.FunctionComparisonService;
-import ghidra.framework.plugintool.DummyPluginTool;
-import ghidra.program.database.ProgramBuilder;
-import ghidra.program.model.data.ByteDataType;
-import ghidra.program.model.data.DataType;
-import ghidra.program.model.listing.*;
-import ghidra.test.AbstractGhidraHeadedIntegrationTest;
-
-/**
- * Tests the function comparison API and data model. Each test verifies that
- * the underlying data model looks correct following a particular API method
- * call. There are a few tests that also exercise various features of the data
- * model directly.
- * The API methods being tested: {@link FunctionComparisonService}
- * The model being used for verification: {@link FunctionComparison}
- */
-public class CompareFunctionsTest extends AbstractGhidraHeadedIntegrationTest {
-
- private Program program1;
- private Program program2;
- private Function foo;
- private Function bar;
- private Function junk;
- private Function stuff;
- private Function one;
- private Function two;
- private Function three;
- private Function four;
- private Function five;
- private FunctionComparisonPlugin plugin;
- private FunctionComparisonProvider provider;
- private FunctionComparisonProvider provider2;
- private FunctionComparisonModel model;
-
- @Before
- public void setUp() throws Exception {
- DummyPluginTool tool = new DummyPluginTool();
- plugin = new FunctionComparisonPlugin(tool);
- buildTestProgram1();
- buildTestProgram2();
-
- model = createTestModel();
- }
-
- @Test
- public void testSetNoFunctions() throws Exception {
- Set functions = CompareFunctionsTestUtility.getFunctionsAsSet();
- provider = compare(functions);
- assertNull(provider);
- }
-
- @Test
- public void testSetOneFunction() throws Exception {
- Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo);
- provider = compare(functions);
- CompareFunctionsTestUtility.checkSourceFunctions(provider, foo);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo);
- }
-
- @Test
- public void testSetDuplicateFunctionDifferentProviders() throws Exception {
- Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo);
- provider = compare(functions);
- CompareFunctionsTestUtility.checkSourceFunctions(provider, foo);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo);
-
- provider2 = compare(functions);
- CompareFunctionsTestUtility.checkSourceFunctions(provider2, foo);
- CompareFunctionsTestUtility.checkTargetFunctions(provider2, foo, foo);
- }
-
- @Test
- public void testSetDuplicateFunctionSameProvider() throws Exception {
- Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo);
- provider = compare(functions);
- CompareFunctionsTestUtility.checkSourceFunctions(provider, foo);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo);
-
- compare(functions, provider);
- CompareFunctionsTestUtility.checkSourceFunctions(provider, foo);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo);
- }
-
- @Test
- public void testSetMultipleFunctions() throws Exception {
- Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, junk, stuff);
- provider = compare(functions);
- CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, junk, stuff);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, junk, stuff);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, junk, foo, junk, stuff);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, stuff, foo, junk, stuff);
- }
-
- @Test
- public void testSetMultipleFunctionsMultipleSets() throws Exception {
- Set functions1 = CompareFunctionsTestUtility.getFunctionsAsSet(one, two);
- Set functions2 = CompareFunctionsTestUtility.getFunctionsAsSet(three, four, five);
-
- provider = compare(functions1);
- provider2 = compare(functions2);
-
- CompareFunctionsTestUtility.checkSourceFunctions(provider, one, two);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, one, one, two);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, two, one, two);
- CompareFunctionsTestUtility.checkSourceFunctions(provider2, three, four, five);
- CompareFunctionsTestUtility.checkTargetFunctions(provider2, three, three, four, five);
- CompareFunctionsTestUtility.checkTargetFunctions(provider2, four, three, four, five);
- CompareFunctionsTestUtility.checkTargetFunctions(provider2, five, three, four, five);
- }
-
- @Test
- public void testSetCombineTwoSets() throws Exception {
- Set functions1 = CompareFunctionsTestUtility.getFunctionsAsSet(foo, two);
- Set functions2 = CompareFunctionsTestUtility.getFunctionsAsSet(bar, three, four);
-
- provider = compare(functions1);
- compare(functions2, provider);
-
- CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, two, bar, three, four);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, two, bar, three, four);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, two, foo, two, bar, three, four);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, two, bar, three, four);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, three, foo, two, bar, three,
- four);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, four, foo, two, bar, three,
- four);
- }
-
- @Test
- public void testSetAddToSpecificProvider() throws Exception {
- Set functions1 = CompareFunctionsTestUtility.getFunctionsAsSet(foo, two);
- Set functions2 = CompareFunctionsTestUtility.getFunctionsAsSet(bar, three);
- Set functions3 = CompareFunctionsTestUtility.getFunctionsAsSet(four);
- provider = compare(functions1);
- provider2 = compare(functions2);
-
- compare(functions3, provider2);
-
- CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, two);
- CompareFunctionsTestUtility.checkSourceFunctions(provider2, bar, three, four);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, two);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, two, foo, two);
- CompareFunctionsTestUtility.checkTargetFunctions(provider2, bar, bar, three, four);
- CompareFunctionsTestUtility.checkTargetFunctions(provider2, three, bar, three, four);
- CompareFunctionsTestUtility.checkTargetFunctions(provider2, four, bar, three, four);
- }
-
- @Test
- public void testRemoveFunction() throws Exception {
- Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar);
- provider = compare(functions);
-
- CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar);
-
- remove(foo);
-
- CompareFunctionsTestUtility.checkSourceFunctions(provider, bar);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, bar);
- }
-
- @Test
- public void testRemoveFunctionTargetOnly() throws Exception {
- Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar);
- provider = compare(functions);
-
- // add a target to foo, which is not also a source
- runSwing(() -> plugin.compareFunctions(foo, two, provider));
-
- // Verify the structure with the new target
- CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar, two);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar);
-
- remove(two);
-
- // Verify the new target is gone
- CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar);
- }
-
- @Test
- public void testRemoveFunctionMultipleProviders() throws Exception {
- Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar);
- provider = compare(functions);
- provider2 = compare(functions);
-
- CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar);
- CompareFunctionsTestUtility.checkSourceFunctions(provider2, foo, bar);
-
- remove(foo);
-
- CompareFunctionsTestUtility.checkSourceFunctions(provider, bar);
- CompareFunctionsTestUtility.checkSourceFunctions(provider2, bar);
- }
-
- @Test
- public void testRemoveNonexistentFunction() throws Exception {
- Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar);
- provider = compare(functions);
-
- CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar);
-
- remove(two); // nothing should happen
-
- CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar);
- }
-
- @Test
- public void testRemoveFunctionFromSpecificProvider() throws Exception {
- Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar);
- provider = compare(functions);
- provider2 = compare(functions);
-
- CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar);
- CompareFunctionsTestUtility.checkSourceFunctions(provider2, foo, bar);
- CompareFunctionsTestUtility.checkTargetFunctions(provider2, foo, foo, bar);
- CompareFunctionsTestUtility.checkTargetFunctions(provider2, bar, foo, bar);
-
- remove(foo, provider);
-
- CompareFunctionsTestUtility.checkSourceFunctions(provider, bar);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, bar);
- CompareFunctionsTestUtility.checkSourceFunctions(provider2, foo, bar);
- CompareFunctionsTestUtility.checkTargetFunctions(provider2, foo, foo, bar);
- CompareFunctionsTestUtility.checkTargetFunctions(provider2, bar, foo, bar);
- }
-
- @Test
- public void testDualCompare() {
- provider = compare(foo, bar);
- CompareFunctionsTestUtility.checkSourceFunctions(provider, foo);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, bar);
- }
-
- @Test
- public void testDualCompareAddToExisting() {
- provider = compare(foo, bar);
- runSwing(() -> plugin.compareFunctions(foo, two, provider));
-
- CompareFunctionsTestUtility.checkSourceFunctions(provider, foo);
- CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, bar, two);
- }
-
-//==================================================================================================
-// Data Model tests
-//==================================================================================================
-
- @Test
- public void testGetTargets() {
- Set targets = model.getTargetFunctions();
- assertEquals(6, targets.size());
- assertTrue(targets.contains(bar));
- assertTrue(targets.contains(two));
- assertTrue(targets.contains(three));
- assertTrue(targets.contains(four));
- assertTrue(targets.contains(five));
- assertTrue(targets.contains(stuff));
- }
-
- @Test
- public void testGetTargetsForSource() {
- Set targets = model.getTargetFunctions(bar);
- assertEquals(3, targets.size());
- assertTrue(targets.contains(three));
- assertTrue(targets.contains(four));
- assertTrue(targets.contains(five));
- }
-
- @Test
- public void getSources() {
- Set sources = model.getSourceFunctions();
- assertEquals(3, sources.size());
- assertTrue(sources.contains(foo));
- assertTrue(sources.contains(bar));
- assertTrue(sources.contains(junk));
- }
-
- @Test
- public void testRemoveFunctionFromModel() {
- model.removeFunction(bar);
-
- Set sources = model.getSourceFunctions();
- assertEquals(2, sources.size());
- assertTrue(sources.contains(foo));
- assertTrue(sources.contains(junk));
-
- Set targets = model.getTargetFunctions(foo);
- assertEquals(1, targets.size());
- assertTrue(targets.contains(two));
-
- targets = model.getTargetFunctions(junk);
- assertEquals(1, targets.size());
- assertTrue(targets.contains(stuff));
- }
-
- private void remove(Function f) {
- runSwing(() -> plugin.removeFunction(f));
- }
-
- private void remove(Function f, FunctionComparisonProvider fp) {
- runSwing(() -> plugin.removeFunction(f, fp));
- }
-
- private void compare(Set functions, FunctionComparisonProvider fp) {
- runSwing(() -> plugin.compareFunctions(functions, fp));
- }
-
- private FunctionComparisonProvider compare(Set functions) {
- return plugin.compareFunctions(functions);
- }
-
- private FunctionComparisonProvider compare(Function f1, Function f2) {
- return plugin.compareFunctions(f1, f2);
- }
-
- private ProgramBuilder buildTestProgram1() throws Exception {
- ProgramBuilder builder = new ProgramBuilder("TestPgm1", ProgramBuilder._TOY_BE);
- builder.createMemory(".text", "0x1001000", 0x6600);
- builder.setProperty(Program.DATE_CREATED, new Date(100000000)); // arbitrary, but consistent
-
- // functions
- DataType dt = new ByteDataType();
- Parameter p = new ParameterImpl(null, dt, builder.getProgram());
- foo = builder.createEmptyFunction("Foo", "10018cf", 10, null, p);
- bar = builder.createEmptyFunction("Bar", "100299e", 130, null, p, p, p);
- junk = builder.createEmptyFunction("Junk", "1002cf5", 15, null, p, p, p, p, p);
- stuff = builder.createEmptyFunction("Stuff", "1003100", 20, null, p, p);
-
- program1 = builder.getProgram();
- AbstractGenericTest.setInstanceField("recordChanges", program1, Boolean.TRUE);
- return builder;
- }
-
- private ProgramBuilder buildTestProgram2() throws Exception {
- ProgramBuilder builder = new ProgramBuilder("TestPgm2", ProgramBuilder._TOY64_BE);
- builder.createMemory(".text", "0x1001000", 0x6600);
- builder.setProperty(Program.DATE_CREATED, new Date(100000000)); // arbitrary, but consistent
-
- // functions
- DataType dt = new ByteDataType();
- Parameter p = new ParameterImpl(null, dt, builder.getProgram());
- one = builder.createEmptyFunction("One", "10017c5", 10, null, p);
- two = builder.createEmptyFunction("Two", "1001822", 130, null, p, p, p);
- three = builder.createEmptyFunction("Three", "1001944", 15, null, p, p, p, p, p);
- four = builder.createEmptyFunction("Four", "1002100", 20, null, p, p);
- five = builder.createEmptyFunction("Five", "1002200", 20, null, p, p);
-
- program2 = builder.getProgram();
- AbstractGenericTest.setInstanceField("recordChanges", program2, Boolean.TRUE);
- return builder;
- }
-
- private FunctionComparisonModel createTestModel() {
- FunctionComparisonModel newModel = new FunctionComparisonModel();
-
- FunctionComparison c1 = new FunctionComparison();
- c1.setSource(foo);
- c1.addTarget(bar);
- c1.addTarget(two);
- newModel.addComparison(c1);
-
- FunctionComparison c2 = new FunctionComparison();
- c2.setSource(bar);
- c2.addTarget(three);
- c2.addTarget(four);
- c2.addTarget(five);
- newModel.addComparison(c2);
-
- FunctionComparison c3 = new FunctionComparison();
- c3.setSource(junk);
- c3.addTarget(stuff);
- newModel.addComparison(c3);
-
- return newModel;
- }
-}
diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsTestUtility.java b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsTestUtility.java
deleted file mode 100644
index 7e820e492e..0000000000
--- a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsTestUtility.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/* ###
- * IP: GHIDRA
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package ghidra.app.plugin.core.functioncompare;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.util.*;
-
-import ghidra.program.model.listing.Function;
-
-/**
- * Helper methods for use with function comparison tests
- *
- * @see {@link CompareFunctionsTest}
- * @see {@link CompareFunctionsSlowTest}
- */
-public class CompareFunctionsTestUtility {
-
- /**
- * Asserts that a given list of functions represents all of the source
- * functions in a comparison model
- *
- * @param provider the function comparison provider
- * @param functions the source functions
- */
- public static void checkSourceFunctions(FunctionComparisonProvider provider,
- Function... functions) {
- Set funcs = new HashSet<>(Arrays.asList(functions));
- Set fcs = provider.getModel().getSourceFunctions();
- assertEquals(fcs.size(), funcs.size());
- assertTrue(fcs.containsAll(funcs));
- }
-
- /**
- * Asserts that a given function (source) is mapped to a collection of
- * functions (targets) in a comparison model
- *
- * @param provider the function comparison provider
- * @param source the source function
- * @param targets the target functions
- */
- public static void checkTargetFunctions(FunctionComparisonProvider provider,
- Function source, Function... targets) {
- Set targetsAsList = new HashSet<>(Arrays.asList(targets));
- Set tgts = provider.getModel().getTargetFunctions(source);
- assertEquals(tgts.size(), targetsAsList.size());
- assertTrue(tgts.containsAll(targetsAsList));
- }
-
- /**
- * Returns the given functions as a {@link Set}
- *
- * @param functions the functions to return as a set
- * @return a set of functions
- */
- public static Set getFunctionsAsSet(Function... functions) {
- Set set = new HashSet<>();
- set.addAll(Arrays.asList(functions));
- return set;
- }
-
- /**
- * Returns the given functions as a {@link Map} of a function (source) to
- * a set of functions (targets)
- *
- * @param source the key of the map
- * @param targets the value of the map
- * @return a map of a function to a set of functions
- */
- public static Map> getFunctionsAsMap(Function source,
- Function... targets) {
- Set targetSet = getFunctionsAsSet(targets);
- Map> map = new HashMap<>();
- map.put(source, targetSet);
- return map;
- }
-}
diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/DefaultComparisonModelTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/DefaultComparisonModelTest.java
new file mode 100644
index 0000000000..f3781484af
--- /dev/null
+++ b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/DefaultComparisonModelTest.java
@@ -0,0 +1,303 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.plugin.core.functioncompare;
+
+import static ghidra.util.datastruct.Duo.Side.*;
+import static org.junit.Assert.*;
+
+import java.util.*;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import generic.test.AbstractGenericTest;
+import ghidra.app.services.DefaultFunctionComparisonModel;
+import ghidra.app.services.FunctionComparisonService;
+import ghidra.program.database.ProgramBuilder;
+import ghidra.program.model.data.ByteDataType;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.listing.*;
+import ghidra.test.AbstractGhidraHeadedIntegrationTest;
+import ghidra.util.datastruct.Duo.Side;
+
+/**
+ * Tests the comparison API for using default function comparison model. Each test verifies that
+ * the underlying data model looks correct following a particular API method
+ * call. There are a few tests that also exercise various features of the data
+ * model directly.
+ *
+ * - The API methods being tested: {@link FunctionComparisonService}
+ * - The model being used for verification: {@link DefaultFunctionComparisonModel}
+ *
+ */
+public class DefaultComparisonModelTest extends AbstractGhidraHeadedIntegrationTest {
+
+ private Program program1;
+ private Program program2;
+ private Function a1;
+ private Function a2;
+ private Function a3;
+ private Function b1;
+ private Function b2;
+ private Function b3;
+ private DefaultFunctionComparisonModel model;
+
+ @Before
+ public void setUp() throws Exception {
+ buildTestProgram1();
+ buildTestProgram2();
+
+ model = createTestModel();
+ }
+
+ @Test
+ public void testSetNoFunctions() throws Exception {
+ model = new DefaultFunctionComparisonModel(new HashSet<>());
+ assertTrue(model.isEmpty());
+ assertEquals(0, model.getFunctions(LEFT).size());
+ assertEquals(0, model.getFunctions(RIGHT).size());
+ assertNull(model.getActiveFunction(LEFT));
+ assertNull(model.getActiveFunction(RIGHT));
+ }
+
+ @Test
+ public void testSetOneFunctions() throws Exception {
+ Set set = Set.of(b1);
+ model = new DefaultFunctionComparisonModel(set);
+
+ assertFalse(model.isEmpty());
+ assertEquals(List.of(b1), model.getFunctions(LEFT));
+ assertEquals(List.of(b1), model.getFunctions(RIGHT));
+ assertEquals(b1, model.getActiveFunction(LEFT));
+ assertEquals(b1, model.getActiveFunction(RIGHT));
+ }
+
+ @Test
+ public void testPairOfFunctions() throws Exception {
+ Set set = Set.of(b1, b2);
+ model = new DefaultFunctionComparisonModel(set);
+
+ assertEquals(List.of(b1, b2), model.getFunctions(LEFT));
+ assertEquals(List.of(b1, b2), model.getFunctions(RIGHT));
+ assertEquals(b1, model.getActiveFunction(LEFT));
+ assertEquals(b2, model.getActiveFunction(RIGHT));
+ }
+
+ @Test
+ public void testMultipleFunctions() throws Exception {
+ assertEquals(List.of(a1, a2, b1, b2), model.getFunctions(LEFT));
+ assertEquals(List.of(a1, a2, b1, b2), model.getFunctions(RIGHT));
+ assertEquals(a1, model.getActiveFunction(LEFT));
+ assertEquals(a2, model.getActiveFunction(RIGHT));
+ }
+
+ @Test
+ public void testDeleteFunction() {
+
+ assertEquals(List.of(a1, a2, b1, b2), model.getFunctions(LEFT));
+ assertEquals(List.of(a1, a2, b1, b2), model.getFunctions(RIGHT));
+ assertEquals(a1, model.getActiveFunction(LEFT));
+ assertEquals(a2, model.getActiveFunction(RIGHT));
+
+ model.removeFunction(a1);
+
+ assertEquals(List.of(a2, b1, b2), model.getFunctions(LEFT));
+ assertEquals(List.of(a2, b1, b2), model.getFunctions(RIGHT));
+ assertEquals(a2, model.getActiveFunction(LEFT));
+ assertEquals(a2, model.getActiveFunction(RIGHT));
+ }
+
+ @Test
+ public void testDeleteFunctions() {
+
+ assertEquals(List.of(a1, a2, b1, b2), model.getFunctions(LEFT));
+ assertEquals(List.of(a1, a2, b1, b2), model.getFunctions(RIGHT));
+ assertEquals(a1, model.getActiveFunction(LEFT));
+ assertEquals(a2, model.getActiveFunction(RIGHT));
+
+ model.removeFunctions(Set.of(a1, b1));
+
+ assertEquals(List.of(a2, b2), model.getFunctions(LEFT));
+ assertEquals(List.of(a2, b2), model.getFunctions(RIGHT));
+ assertEquals(a2, model.getActiveFunction(LEFT));
+ assertEquals(a2, model.getActiveFunction(RIGHT));
+ }
+
+ @Test
+ public void testDeleteFunctionsForProgram() {
+
+ assertEquals(List.of(a1, a2, b1, b2), model.getFunctions(LEFT));
+ assertEquals(List.of(a1, a2, b1, b2), model.getFunctions(RIGHT));
+ assertEquals(a1, model.getActiveFunction(LEFT));
+ assertEquals(a2, model.getActiveFunction(RIGHT));
+
+ model.removeFunctions(program2);
+
+ assertEquals(List.of(a1, a2), model.getFunctions(LEFT));
+ assertEquals(List.of(a1, a2), model.getFunctions(RIGHT));
+ assertEquals(a1, model.getActiveFunction(LEFT));
+ assertEquals(a2, model.getActiveFunction(RIGHT));
+ }
+
+ @Test
+ public void testAddFunctions() {
+
+ assertEquals(List.of(a1, a2, b1, b2), model.getFunctions(LEFT));
+ assertEquals(List.of(a1, a2, b1, b2), model.getFunctions(RIGHT));
+ assertEquals(a1, model.getActiveFunction(LEFT));
+ assertEquals(a2, model.getActiveFunction(RIGHT));
+
+ model.addFunctions(Set.of(a3, b3));
+
+ assertEquals(List.of(a1, a2, a3, b1, b2, b3), model.getFunctions(LEFT));
+ assertEquals(List.of(a1, a2, a3, b1, b2, b3), model.getFunctions(RIGHT));
+ assertEquals(a1, model.getActiveFunction(LEFT));
+ // check that one of the new function is now shown on the right -the exact one is random
+ assertTrue(Set.of(a3, b3).contains(model.getActiveFunction(RIGHT)));
+ }
+
+ @Test
+ public void testModelListenerDataChangedWhenFunctionAdded() {
+ TestFunctionComparisonModelListener listener = new TestFunctionComparisonModelListener();
+ model.addFunctionComparisonModelListener(listener);
+
+ assertFalse(listener.modelDataChanged);
+ model.addFunction(a3);
+ assertTrue(listener.modelDataChanged);
+ }
+
+ @Test
+ public void testModelListenerDataChangedWhenFunctionRemoved() {
+ TestFunctionComparisonModelListener listener = new TestFunctionComparisonModelListener();
+ model.addFunctionComparisonModelListener(listener);
+
+ assertFalse(listener.modelDataChanged);
+ model.removeFunction(a1);
+ assertTrue(listener.modelDataChanged);
+ }
+
+ @Test
+ public void testModelListenerDataChangedWhenNonContainingFunctionRemoved() {
+ TestFunctionComparisonModelListener listener = new TestFunctionComparisonModelListener();
+ model.addFunctionComparisonModelListener(listener);
+
+ assertFalse(listener.modelDataChanged);
+ model.removeFunction(a3);
+ assertFalse(listener.modelDataChanged);
+ }
+
+ @Test
+ public void testModelListenerActiveFunctionChanged() {
+ TestFunctionComparisonModelListener listener = new TestFunctionComparisonModelListener();
+ model.addFunctionComparisonModelListener(listener);
+
+ model.setActiveFunction(LEFT, a2);
+ assertEquals(LEFT, listener.changedFunctionSide);
+ assertEquals(a2, listener.changedFunction);
+
+ model.setActiveFunction(RIGHT, b1);
+ assertEquals(RIGHT, listener.changedFunctionSide);
+ assertEquals(b1, listener.changedFunction);
+
+ }
+
+ @Test
+ public void testModelListenerActiveFunctionDidNotChanged() {
+ TestFunctionComparisonModelListener listener = new TestFunctionComparisonModelListener();
+ model.addFunctionComparisonModelListener(listener);
+
+ assertEquals(a1, model.getActiveFunction(LEFT));
+ model.setActiveFunction(LEFT, a1);
+ assertNull(listener.changedFunctionSide);
+ assertNull(listener.changedFunction);
+
+ assertEquals(a2, model.getActiveFunction(RIGHT));
+ model.setActiveFunction(RIGHT, a2);
+ assertNull(listener.changedFunctionSide);
+ assertNull(listener.changedFunction);
+
+ }
+
+ @Test
+ public void testSettingBadFunctionActive() {
+ Set set = Set.of(a1, b1);
+ model = new DefaultFunctionComparisonModel(set);
+
+ assertEquals(a1, model.getActiveFunction(LEFT));
+ model.setActiveFunction(LEFT, a3);
+ assertEquals(a1, model.getActiveFunction(LEFT));
+
+ assertEquals(b1, model.getActiveFunction(RIGHT));
+ model.setActiveFunction(RIGHT, b2);
+ assertEquals(b1, model.getActiveFunction(RIGHT));
+ }
+
+ private ProgramBuilder buildTestProgram1() throws Exception {
+ ProgramBuilder builder = new ProgramBuilder("TestPgm1", ProgramBuilder._TOY_BE);
+ builder.createMemory(".text", "0x1001000", 0x6600);
+ builder.setProperty(Program.DATE_CREATED, new Date(100000000)); // arbitrary, but consistent
+
+ // functions
+ DataType dt = new ByteDataType();
+ Parameter p = new ParameterImpl(null, dt, builder.getProgram());
+ a1 = builder.createEmptyFunction("A1", "10018cf", 10, null, p);
+ a2 = builder.createEmptyFunction("A2", "100299e", 130, null, p, p, p);
+ a3 = builder.createEmptyFunction("A3", "1002cf5", 15, null, p, p, p, p, p);
+
+ program1 = builder.getProgram();
+ AbstractGenericTest.setInstanceField("recordChanges", program1, Boolean.TRUE);
+ return builder;
+ }
+
+ private ProgramBuilder buildTestProgram2() throws Exception {
+ ProgramBuilder builder = new ProgramBuilder("TestPgm2", ProgramBuilder._TOY64_BE);
+ builder.createMemory(".text", "0x1001000", 0x6600);
+ builder.setProperty(Program.DATE_CREATED, new Date(100000000)); // arbitrary, but consistent
+
+ // functions
+ DataType dt = new ByteDataType();
+ Parameter p = new ParameterImpl(null, dt, builder.getProgram());
+ b1 = builder.createEmptyFunction("B1", "10017c5", 10, null, p);
+ b2 = builder.createEmptyFunction("B2", "1001822", 130, null, p, p, p);
+ b3 = builder.createEmptyFunction("B3", "1001944", 15, null, p, p, p, p, p);
+
+ program2 = builder.getProgram();
+ AbstractGenericTest.setInstanceField("recordChanges", program2, Boolean.TRUE);
+ return builder;
+ }
+
+ private DefaultFunctionComparisonModel createTestModel() {
+ Set set = Set.of(b1, b2, a1, a2);
+ return new DefaultFunctionComparisonModel(set);
+ }
+
+ private class TestFunctionComparisonModelListener implements FunctionComparisonModelListener {
+ boolean modelDataChanged = false;
+ Side changedFunctionSide = null;
+ Function changedFunction = null;
+
+ @Override
+ public void activeFunctionChanged(Side side, Function function) {
+ changedFunctionSide = side;
+ changedFunction = function;
+ }
+
+ @Override
+ public void modelDataChanged() {
+ modelDataChanged = true;
+ }
+ }
+}
diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/MatchedFunctionComparisonModelTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/MatchedFunctionComparisonModelTest.java
new file mode 100644
index 0000000000..0d8a72c9b7
--- /dev/null
+++ b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/MatchedFunctionComparisonModelTest.java
@@ -0,0 +1,406 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.app.plugin.core.functioncompare;
+
+import static ghidra.util.datastruct.Duo.Side.*;
+import static org.junit.Assert.*;
+
+import java.util.Date;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import generic.test.AbstractGenericTest;
+import ghidra.app.services.*;
+import ghidra.program.database.ProgramBuilder;
+import ghidra.program.model.data.ByteDataType;
+import ghidra.program.model.data.DataType;
+import ghidra.program.model.listing.*;
+import ghidra.test.AbstractGhidraHeadedIntegrationTest;
+import ghidra.util.datastruct.Duo.Side;
+
+/**
+ * Tests the comparison API for using default function comparison model. Each test verifies that
+ * the underlying data model looks correct following a particular API method
+ * call. There are a few tests that also exercise various features of the data
+ * model directly.
+ * The API methods being tested: {@link FunctionComparisonService}
+ * The model being used for verification: {@link DefaultFunctionComparisonModel}
+ */
+public class MatchedFunctionComparisonModelTest extends AbstractGhidraHeadedIntegrationTest {
+
+ private Program program1;
+ private Program program2;
+ private Function a1;
+ private Function a2;
+ private Function a3;
+ private Function a4;
+ private Function b1;
+ private Function b2;
+ private Function b3;
+ private Function b4;
+ private MatchedFunctionComparisonModel model;
+
+ @Before
+ public void setUp() throws Exception {
+ buildTestProgram1();
+ buildTestProgram2();
+
+ model = createTestModel();
+ }
+
+ @Test
+ public void testSetNoFunctions() throws Exception {
+ model = new MatchedFunctionComparisonModel();
+ assertTrue(model.isEmpty());
+ assertEquals(0, model.getFunctions(LEFT).size());
+ assertEquals(0, model.getFunctions(RIGHT).size());
+ assertNull(model.getActiveFunction(LEFT));
+ assertNull(model.getActiveFunction(RIGHT));
+ }
+
+ @Test
+ public void testPairOfFunctions() throws Exception {
+ model = new MatchedFunctionComparisonModel();
+ model.addMatch(a1, b1);
+
+ assertEquals(List.of(a1), model.getFunctions(LEFT));
+ assertEquals(List.of(b1), model.getFunctions(RIGHT));
+ assertEquals(a1, model.getActiveFunction(LEFT));
+ assertEquals(b1, model.getActiveFunction(RIGHT));
+ }
+
+ @Test
+ public void testMultipleFunctions() throws Exception {
+ assertEquals(List.of(a1, a2, a3), model.getFunctions(LEFT));
+
+ assertEquals(a3, model.getActiveFunction(LEFT));
+ assertEquals(b1, model.getActiveFunction(RIGHT));
+
+ assertEquals(List.of(b1), model.getFunctions(RIGHT));
+
+ model.setActiveFunction(LEFT, a1);
+ assertEquals(List.of(b1, b2), model.getFunctions(RIGHT));
+ assertEquals(b1, model.getActiveFunction(RIGHT));
+
+ model.setActiveFunction(LEFT, a2);
+ assertEquals(List.of(b2, b3), model.getFunctions(RIGHT));
+ assertEquals(b2, model.getActiveFunction(RIGHT));
+
+ model.setActiveFunction(LEFT, a3);
+ assertEquals(List.of(b1), model.getFunctions(RIGHT));
+ assertEquals(List.of(b1), model.getFunctions(RIGHT));
+
+ }
+
+ @Test
+ public void testDeleteSourceFunctionActive() {
+ assertEquals(a3, model.getActiveFunction(LEFT));
+ assertEquals(b1, model.getActiveFunction(RIGHT));
+
+ assertEquals(List.of(a1, a2, a3), model.getFunctions(LEFT));
+ assertEquals(List.of(b1), model.getFunctions(RIGHT));
+
+ model.removeFunction(a3);
+
+ assertEquals(a1, model.getActiveFunction(LEFT));
+ assertEquals(b1, model.getActiveFunction(RIGHT));
+
+ assertEquals(List.of(a1, a2), model.getFunctions(LEFT));
+ assertEquals(List.of(b1, b2), model.getFunctions(RIGHT));
+ }
+
+ @Test
+ public void testDeleteSourceFunctionNonActive() {
+ assertEquals(a3, model.getActiveFunction(LEFT));
+ assertEquals(b1, model.getActiveFunction(RIGHT));
+
+ assertEquals(List.of(a1, a2, a3), model.getFunctions(LEFT));
+ assertEquals(List.of(b1), model.getFunctions(RIGHT));
+
+ model.removeFunction(a1);
+
+ assertEquals(a3, model.getActiveFunction(LEFT));
+ assertEquals(b1, model.getActiveFunction(RIGHT));
+
+ assertEquals(List.of(a2, a3), model.getFunctions(LEFT));
+ assertEquals(List.of(b1), model.getFunctions(RIGHT));
+ }
+
+ @Test
+ public void testDeleteTargetFunctionActive() {
+ model.setActiveFunction(LEFT, a1);
+ model.setActiveFunction(RIGHT, b2);
+
+ assertEquals(a1, model.getActiveFunction(LEFT));
+ assertEquals(b2, model.getActiveFunction(RIGHT));
+
+ assertEquals(List.of(a1, a2, a3), model.getFunctions(LEFT));
+ assertEquals(List.of(b1, b2), model.getFunctions(RIGHT));
+
+ model.removeFunction(b2);
+
+ assertEquals(a1, model.getActiveFunction(LEFT));
+ assertEquals(b1, model.getActiveFunction(RIGHT));
+
+ assertEquals(List.of(a1, a2, a3), model.getFunctions(LEFT));
+ assertEquals(List.of(b1), model.getFunctions(RIGHT));
+ }
+
+ @Test
+ public void testDeleteSingleTargetFromActive() {
+ model.setActiveFunction(LEFT, a3);
+ model.setActiveFunction(RIGHT, b1);
+
+ assertEquals(a3, model.getActiveFunction(LEFT));
+ assertEquals(b1, model.getActiveFunction(RIGHT));
+
+ assertEquals(List.of(a1, a2, a3), model.getFunctions(LEFT));
+ assertEquals(List.of(b1), model.getFunctions(RIGHT));
+
+ model.removeFunction(b1);
+
+ assertEquals(a1, model.getActiveFunction(LEFT));
+ assertEquals(b2, model.getActiveFunction(RIGHT));
+
+ assertEquals(List.of(a1, a2), model.getFunctions(LEFT));
+ assertEquals(List.of(b2), model.getFunctions(RIGHT));
+ }
+
+ @Test
+ public void testDeleteSingleTargetDeletesSourceAsWell() {
+ model.setActiveFunction(LEFT, a1);
+ model.setActiveFunction(RIGHT, b2);
+
+ assertEquals(a1, model.getActiveFunction(LEFT));
+ assertEquals(b2, model.getActiveFunction(RIGHT));
+
+ assertEquals(List.of(a1, a2, a3), model.getFunctions(LEFT));
+ assertEquals(List.of(b1, b2), model.getFunctions(RIGHT));
+
+ model.removeFunction(b1);
+
+ assertEquals(a1, model.getActiveFunction(LEFT));
+ assertEquals(b2, model.getActiveFunction(RIGHT));
+
+ // note a3 was removed because it only had one target, b1, which was deleted
+ assertEquals(List.of(a1, a2), model.getFunctions(LEFT));
+ assertEquals(List.of(b2), model.getFunctions(RIGHT));
+ }
+
+ @Test
+ public void testDeleteFunctionsForDestinationProgram() {
+
+ assertEquals(List.of(a1, a2, a3), model.getFunctions(LEFT));
+ assertEquals(List.of(b1), model.getFunctions(RIGHT));
+ assertEquals(a3, model.getActiveFunction(LEFT));
+ assertEquals(b1, model.getActiveFunction(RIGHT));
+
+ // this will delete everything because all the sources have no targets
+ model.removeFunctions(program2);
+
+ assertEquals(List.of(), model.getFunctions(LEFT));
+ assertEquals(List.of(), model.getFunctions(RIGHT));
+ assertNull(model.getActiveFunction(LEFT));
+ assertNull(model.getActiveFunction(RIGHT));
+ }
+
+ @Test
+ public void testDeleteFunctionsForSourceProgram() {
+
+ assertEquals(List.of(a1, a2, a3), model.getFunctions(LEFT));
+ assertEquals(List.of(b1), model.getFunctions(RIGHT));
+ assertEquals(a3, model.getActiveFunction(LEFT));
+ assertEquals(b1, model.getActiveFunction(RIGHT));
+
+ // this will delete everything because all the sources have no targets
+ model.removeFunctions(program1);
+
+ assertEquals(List.of(), model.getFunctions(LEFT));
+ assertEquals(List.of(), model.getFunctions(RIGHT));
+ assertNull(model.getActiveFunction(LEFT));
+ assertNull(model.getActiveFunction(RIGHT));
+ }
+
+ @Test
+ public void testAddTotallyNewMatch() {
+
+ model.addMatch(a4, b4);
+
+ assertEquals(List.of(a1, a2, a3, a4), model.getFunctions(LEFT));
+ assertEquals(List.of(b4), model.getFunctions(RIGHT));
+ assertEquals(a4, model.getActiveFunction(LEFT));
+ assertEquals(b4, model.getActiveFunction(RIGHT));
+ }
+
+ @Test
+ public void testAddToExistingMatch() {
+
+ model.addMatch(a2, b4);
+
+ assertEquals(List.of(a1, a2, a3), model.getFunctions(LEFT));
+ assertEquals(List.of(b2, b3, b4), model.getFunctions(RIGHT));
+ assertEquals(a2, model.getActiveFunction(LEFT));
+ assertEquals(b4, model.getActiveFunction(RIGHT));
+ }
+
+ @Test
+ public void testModelListenerDataChangedWhenFunctionAdded() {
+ TestFunctionComparisonModelListener listener = new TestFunctionComparisonModelListener();
+ model.addFunctionComparisonModelListener(listener);
+
+ assertFalse(listener.modelDataChanged);
+ model.addMatch(a1, b4);
+ assertTrue(listener.modelDataChanged);
+ }
+
+ @Test
+ public void testModelListenerDataChangedWhenFunctionRemoved() {
+ TestFunctionComparisonModelListener listener = new TestFunctionComparisonModelListener();
+ model.addFunctionComparisonModelListener(listener);
+
+ assertFalse(listener.modelDataChanged);
+ model.removeFunction(a1);
+ assertTrue(listener.modelDataChanged);
+ }
+
+ @Test
+ public void testModelListenerDataChangedWhenNonContainingFunctionRemoved() {
+ TestFunctionComparisonModelListener listener = new TestFunctionComparisonModelListener();
+ model.addFunctionComparisonModelListener(listener);
+
+ assertFalse(listener.modelDataChanged);
+ model.removeFunction(a4);
+ assertFalse(listener.modelDataChanged);
+ }
+
+ @Test
+ public void testRightSideModelListenerActiveFunctionChanged() {
+ model.setActiveFunction(LEFT, a1);
+ model.setActiveFunction(RIGHT, b1);
+ assertEquals(a1, model.getActiveFunction(LEFT));
+ assertEquals(b1, model.getActiveFunction(RIGHT));
+
+ TestFunctionComparisonModelListener listener = new TestFunctionComparisonModelListener();
+ model.addFunctionComparisonModelListener(listener);
+
+ model.setActiveFunction(RIGHT, b2);
+ assertEquals(RIGHT, listener.changedFunctionSide);
+ assertEquals(b2, listener.changedFunction);
+
+ model.setActiveFunction(RIGHT, b1);
+ assertEquals(RIGHT, listener.changedFunctionSide);
+ assertEquals(b1, listener.changedFunction);
+ }
+
+ @Test
+ public void testLeftSideModelListenerActiveFunctionChanged() {
+ model.setActiveFunction(LEFT, a1);
+ model.setActiveFunction(RIGHT, b1);
+ assertEquals(a1, model.getActiveFunction(LEFT));
+ assertEquals(b1, model.getActiveFunction(RIGHT));
+
+ TestFunctionComparisonModelListener listener = new TestFunctionComparisonModelListener();
+ model.addFunctionComparisonModelListener(listener);
+
+ model.setActiveFunction(LEFT, a2);
+ assertTrue(listener.modelDataChanged);
+ }
+
+ @Test
+ public void testModelListenerActiveFunctionDidNotChanged() {
+ model.setActiveFunction(LEFT, a1);
+ model.setActiveFunction(RIGHT, b1);
+
+ TestFunctionComparisonModelListener listener = new TestFunctionComparisonModelListener();
+ model.addFunctionComparisonModelListener(listener);
+
+ assertEquals(a1, model.getActiveFunction(LEFT));
+ model.setActiveFunction(LEFT, a1);
+ assertNull(listener.changedFunctionSide);
+ assertNull(listener.changedFunction);
+
+ assertEquals(b1, model.getActiveFunction(RIGHT));
+ model.setActiveFunction(RIGHT, b1);
+ assertNull(listener.changedFunctionSide);
+ assertNull(listener.changedFunction);
+
+ }
+
+ private ProgramBuilder buildTestProgram1() throws Exception {
+ ProgramBuilder builder = new ProgramBuilder("TestPgm1", ProgramBuilder._TOY_BE);
+ builder.createMemory(".text", "0x1001000", 0x6600);
+ builder.setProperty(Program.DATE_CREATED, new Date(100000000)); // arbitrary, but consistent
+
+ // functions
+ DataType dt = new ByteDataType();
+ Parameter p = new ParameterImpl(null, dt, builder.getProgram());
+ a1 = builder.createEmptyFunction("A1", "10018cf", 10, null, p);
+ a2 = builder.createEmptyFunction("A2", "100299e", 130, null, p, p, p);
+ a3 = builder.createEmptyFunction("A3", "1002cf5", 15, null, p, p, p, p, p);
+ a4 = builder.createEmptyFunction("A4", "1003100", 20, null, p, p);
+
+ program1 = builder.getProgram();
+ AbstractGenericTest.setInstanceField("recordChanges", program1, Boolean.TRUE);
+ return builder;
+ }
+
+ private ProgramBuilder buildTestProgram2() throws Exception {
+ ProgramBuilder builder = new ProgramBuilder("TestPgm2", ProgramBuilder._TOY64_BE);
+ builder.createMemory(".text", "0x1001000", 0x6600);
+ builder.setProperty(Program.DATE_CREATED, new Date(100000000)); // arbitrary, but consistent
+
+ // functions
+ DataType dt = new ByteDataType();
+ Parameter p = new ParameterImpl(null, dt, builder.getProgram());
+ b1 = builder.createEmptyFunction("B1", "10017c5", 10, null, p);
+ b2 = builder.createEmptyFunction("B2", "1001822", 130, null, p, p, p);
+ b3 = builder.createEmptyFunction("B3", "1001944", 15, null, p, p, p, p, p);
+ b4 = builder.createEmptyFunction("B4", "1002100", 20, null, p, p);
+
+ program2 = builder.getProgram();
+ AbstractGenericTest.setInstanceField("recordChanges", program2, Boolean.TRUE);
+ return builder;
+ }
+
+ private MatchedFunctionComparisonModel createTestModel() {
+ MatchedFunctionComparisonModel m = new MatchedFunctionComparisonModel();
+ m.addMatch(a1, b1);
+ m.addMatch(a1, b2);
+ m.addMatch(a2, b2);
+ m.addMatch(a2, b3);
+ m.addMatch(a3, b1);
+ return m;
+ }
+
+ private class TestFunctionComparisonModelListener implements FunctionComparisonModelListener {
+ boolean modelDataChanged = false;
+ Side changedFunctionSide = null;
+ Function changedFunction = null;
+
+ @Override
+ public void activeFunctionChanged(Side side, Function function) {
+ changedFunctionSide = side;
+ changedFunction = function;
+ }
+
+ @Override
+ public void modelDataChanged() {
+ modelDataChanged = true;
+ }
+ }
+}
diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/CompareFuncsFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/CompareFuncsFromMatchedTokensAction.java
index 04acb10b91..4428178ee7 100644
--- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/CompareFuncsFromMatchedTokensAction.java
+++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/CompareFuncsFromMatchedTokensAction.java
@@ -20,7 +20,6 @@ import static ghidra.util.datastruct.Duo.Side.*;
import docking.ActionContext;
import docking.action.MenuData;
import ghidra.app.decompiler.ClangFuncNameToken;
-import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider;
import ghidra.app.services.FunctionComparisonService;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address;
@@ -106,10 +105,7 @@ public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedTokensAc
Msg.error(this, "Function Comparison Service not found!");
return;
}
-
- FunctionComparisonProvider comparisonProvider = service.createFunctionComparisonProvider();
- comparisonProvider.removeAddFunctionsAction();
- comparisonProvider.getModel().compareFunctions(leftFunction, rightFunction);
+ service.createComparison(leftFunction, rightFunction);
}
private Function getFuncFromToken(ClangFuncNameToken funcToken, Program program) {
diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DecompilerCodeComparisonPanel.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DecompilerCodeComparisonPanel.java
index b6621b654d..ba96d5ea2e 100644
--- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DecompilerCodeComparisonPanel.java
+++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DecompilerCodeComparisonPanel.java
@@ -55,7 +55,7 @@ import resources.MultiIcon;
public class DecompilerCodeComparisonPanel
extends CodeComparisonPanel {
- public static final String NAME = "Decompile Diff View";
+ public static final String NAME = "Decompiler View";
private boolean isStale = true;
diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/functionassociation/VTFunctionAssociationProvider.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/functionassociation/VTFunctionAssociationProvider.java
index e1f0b9c040..e8fe334066 100644
--- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/functionassociation/VTFunctionAssociationProvider.java
+++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/functionassociation/VTFunctionAssociationProvider.java
@@ -368,7 +368,7 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
statusPanel.add(statusLabel, BorderLayout.CENTER);
dualTablePanel.add(statusPanel, BorderLayout.SOUTH);
- functionComparisonPanel = new FunctionComparisonPanel(this, tool);
+ functionComparisonPanel = new FunctionComparisonPanel(tool, getName());
addSpecificCodeComparisonActions();
functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.NAME);
functionComparisonPanel.setTitlePrefixes("Source:", "Destination:");
diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/VTMarkupItemsTableProvider.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/VTMarkupItemsTableProvider.java
index 3dcd67a23c..229504b07c 100644
--- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/VTMarkupItemsTableProvider.java
+++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/VTMarkupItemsTableProvider.java
@@ -150,7 +150,7 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
markupItemsTablePanel.add(tablePanel, BorderLayout.CENTER);
markupItemsTablePanel.add(filterAreaPanel, BorderLayout.SOUTH);
- functionComparisonPanel = new FunctionComparisonPanel(this, tool);
+ functionComparisonPanel = new FunctionComparisonPanel(tool, getName());
addSpecificCodeComparisonActions();
functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.NAME);
functionComparisonPanel.setTitlePrefixes("Source:", "Destination:");
diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchTableProvider.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchTableProvider.java
index b4b3e58b99..1459b5ecc3 100644
--- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchTableProvider.java
+++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/matchtable/VTMatchTableProvider.java
@@ -15,166 +15,40 @@
*/
package ghidra.feature.vt.gui.provider.matchtable;
-import static ghidra.feature.vt.api.impl.VTEvent.ASSOCIATION_STATUS_CHANGED;
-import static ghidra.feature.vt.api.impl.VTEvent.MATCH_SET_ADDED;
-import static ghidra.feature.vt.gui.actions.TableSelectionTrackingState.MAINTAIN_SELECTED_ROW_INDEX;
-import static ghidra.feature.vt.gui.actions.TableSelectionTrackingState.MAINTAIN_SELECTED_ROW_VALUE;
-import static ghidra.feature.vt.gui.actions.TableSelectionTrackingState.NO_SELECTION_TRACKING;
-import static ghidra.feature.vt.gui.plugin.VTPlugin.FILTERED_ICON;
-import static ghidra.feature.vt.gui.plugin.VTPlugin.UNFILTERED_ICON;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.ACCEPT_MATCH_OPTIONS_NAME;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.APPLY_DATA_NAME_ON_ACCEPT;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.APPLY_FUNCTION_NAME_ON_ACCEPT;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.APPLY_IMPLIED_MATCHES_OPTION;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.APPLY_MARKUP_OPTIONS_NAME;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_CREATE_IMPLIED_MATCH;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_VT_DATA_CORRELATOR;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_VT_DUPLICATE_FUNCTION_CORRELATOR;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_VT_EXACT_FUNCTION_CORRELATORS;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_VT_IMPLIED_MATCH_CORRELATOR;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_VT_OPTIONS_NAME;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_VT_REFERENCE_CORRELATORS;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_VT_SYMBOL_CORRELATOR;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.CALLING_CONVENTION;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.CALL_FIXUP;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.CREATE_IMPLIED_MATCHES_OPTION;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DATA_CORRELATOR_MIN_LEN_OPTION;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DATA_MATCH_DATA_TYPE;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_CALLING_CONVENTION;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_CALL_FIXUP;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_DATA_MATCH_DATA_TYPE;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_EOL_COMMENTS;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_FUNCTION_NAME;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_FUNCTION_RETURN_TYPE;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_FUNCTION_SIGNATURE;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_HIGHEST_NAME_PRIORITY;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_IGNORE_EXCLUDED_MARKUP_ITEMS;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_IGNORE_INCOMPLETE_MARKUP_ITEMS;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_INLINE;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_LABELS;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_NO_RETURN;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_PARAMETER_COMMENTS;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_PARAMETER_DATA_TYPES;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_PARAMETER_NAMES;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_PARAMETER_NAMES_REPLACE_IF_SAME_PRIORITY;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_PLATE_COMMENTS;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_POST_COMMENTS;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_PRE_COMMENTS;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_REPEATABLE_COMMENTS;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_VAR_ARGS;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DISPLAY_APPLY_MARKUP_OPTIONS;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.DUPE_FUNCTION_CORRELATOR_MIN_LEN_OPTION;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.END_OF_LINE_COMMENT;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.FUNCTION_CORRELATOR_MIN_LEN_OPTION;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.FUNCTION_NAME;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.FUNCTION_RETURN_TYPE;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.FUNCTION_SIGNATURE;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.HIGHEST_NAME_PRIORITY;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.IGNORE_EXCLUDED_MARKUP_ITEMS;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.IGNORE_INCOMPLETE_MARKUP_ITEMS;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.INLINE;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.LABELS;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.MAX_CONFLICTS_OPTION;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.MIN_VOTES_OPTION;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.NO_RETURN;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.PARAMETER_COMMENTS;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.PARAMETER_DATA_TYPES;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.PARAMETER_NAMES;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.PARAMETER_NAMES_REPLACE_IF_SAME_PRIORITY;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.PLATE_COMMENT;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.POST_COMMENT;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.PRE_COMMENT;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.REF_CORRELATOR_MIN_CONF_OPTION;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.REF_CORRELATOR_MIN_SCORE_OPTION;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.REPEATABLE_COMMENT;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.RUN_DUPE_FUNCTION_OPTION;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.RUN_EXACT_DATA_OPTION;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.RUN_EXACT_FUNCTION_BYTES_OPTION;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.RUN_EXACT_FUNCTION_INST_OPTION;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.RUN_EXACT_SYMBOL_OPTION;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.RUN_REF_CORRELATORS_OPTION;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.SYMBOL_CORRELATOR_MIN_LEN_OPTION;
-import static ghidra.feature.vt.gui.util.VTOptionDefines.VAR_ARGS;
-import static ghidra.framework.model.DomainObjectEvent.RESTORED;
+import static ghidra.feature.vt.api.impl.VTEvent.*;
+import static ghidra.feature.vt.gui.actions.TableSelectionTrackingState.*;
+import static ghidra.feature.vt.gui.plugin.VTPlugin.*;
+import static ghidra.feature.vt.gui.util.VTOptionDefines.*;
+import static ghidra.framework.model.DomainObjectEvent.*;
-import java.awt.Adjustable;
-import java.awt.BorderLayout;
-import java.awt.Dimension;
-import java.awt.Rectangle;
-import java.awt.event.FocusAdapter;
-import java.awt.event.FocusEvent;
-import java.awt.event.MouseEvent;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
import java.util.List;
-import java.util.Set;
-import javax.swing.BorderFactory;
-import javax.swing.Icon;
-import javax.swing.JButton;
-import javax.swing.JComponent;
-import javax.swing.JPanel;
-import javax.swing.JScrollBar;
-import javax.swing.JTable;
-import javax.swing.ListSelectionModel;
+import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
-import javax.swing.table.TableCellRenderer;
-import javax.swing.table.TableColumn;
-import javax.swing.table.TableColumnModel;
+import javax.swing.table.*;
-import docking.ActionContext;
-import docking.DockingWindowManager;
-import docking.WindowPosition;
+import docking.*;
import docking.action.builder.ActionBuilder;
-import docking.widgets.table.AbstractSortedTableModel;
-import docking.widgets.table.GTable;
-import docking.widgets.table.RowObjectSelectionManager;
-import docking.widgets.table.RowObjectTableModel;
-import docking.widgets.table.SelectionManager;
+import docking.widgets.table.*;
import docking.widgets.table.threaded.ThreadedTableModel;
import generic.theme.GIcon;
import ghidra.app.services.FunctionComparisonService;
+import ghidra.app.services.MatchedFunctionComparisonModel;
import ghidra.feature.vt.api.impl.VTEvent;
import ghidra.feature.vt.api.impl.VersionTrackingChangeRecord;
-import ghidra.feature.vt.api.main.VTMarkupItem;
-import ghidra.feature.vt.api.main.VTMatch;
-import ghidra.feature.vt.api.main.VTSession;
-import ghidra.feature.vt.gui.actions.AcceptMatchAction;
-import ghidra.feature.vt.gui.actions.ApplyBlockedMatchAction;
-import ghidra.feature.vt.gui.actions.ApplyMatchAction;
-import ghidra.feature.vt.gui.actions.ChooseMatchTagAction;
-import ghidra.feature.vt.gui.actions.ClearMatchAction;
-import ghidra.feature.vt.gui.actions.CreateSelectionAction;
-import ghidra.feature.vt.gui.actions.EditAllTagsAction;
-import ghidra.feature.vt.gui.actions.MatchTableSelectionAction;
-import ghidra.feature.vt.gui.actions.RejectMatchAction;
-import ghidra.feature.vt.gui.actions.RemoveMatchAction;
-import ghidra.feature.vt.gui.actions.RemoveMatchTagAction;
-import ghidra.feature.vt.gui.actions.TableSelectionTrackingState;
+import ghidra.feature.vt.api.main.*;
+import ghidra.feature.vt.gui.actions.*;
import ghidra.feature.vt.gui.editors.MatchTagCellEditor;
-import ghidra.feature.vt.gui.filters.AncillaryFilterDialogComponentProvider;
-import ghidra.feature.vt.gui.filters.Filter;
+import ghidra.feature.vt.gui.filters.*;
import ghidra.feature.vt.gui.filters.Filter.FilterEditingStatus;
-import ghidra.feature.vt.gui.filters.FilterDialogModel;
-import ghidra.feature.vt.gui.filters.FilterStatusListener;
-import ghidra.feature.vt.gui.plugin.VTController;
-import ghidra.feature.vt.gui.plugin.VTControllerListener;
-import ghidra.feature.vt.gui.plugin.VTPlugin;
-import ghidra.feature.vt.gui.plugin.VersionTrackingPluginPackage;
-import ghidra.feature.vt.gui.util.AbstractVTMatchTableModel.DestinationLabelTableColumn;
-import ghidra.feature.vt.gui.util.AbstractVTMatchTableModel.SourceLabelTableColumn;
-import ghidra.feature.vt.gui.util.AbstractVTMatchTableModel.StatusTableColumn;
-import ghidra.feature.vt.gui.util.AbstractVTMatchTableModel.TagTableColumn;
-import ghidra.feature.vt.gui.util.AllTextFilter;
-import ghidra.feature.vt.gui.util.FilterIconFlashTimer;
-import ghidra.feature.vt.gui.util.MatchInfo;
-import ghidra.feature.vt.gui.util.MatchStatusRenderer;
-import ghidra.feature.vt.gui.util.VTSymbolRenderer;
-import ghidra.framework.model.DomainObjectChangeRecord;
-import ghidra.framework.model.DomainObjectChangedEvent;
-import ghidra.framework.model.EventType;
+import ghidra.feature.vt.gui.plugin.*;
+import ghidra.feature.vt.gui.util.*;
+import ghidra.feature.vt.gui.util.AbstractVTMatchTableModel.*;
+import ghidra.framework.model.*;
import ghidra.framework.options.Options;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.ComponentProviderAdapter;
@@ -282,21 +156,19 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
}
private void compareFunctions(VTMatchContext c) {
- Set sourceFunctions = new HashSet<>();
- Set destinationFunctions = new HashSet<>();
+ MatchedFunctionComparisonModel model = new MatchedFunctionComparisonModel();
List matches = c.getFunctionMatches();
for (VTMatch match : matches) {
MatchInfo matchInfo = controller.getMatchInfo(match);
Function sourceFunction = matchInfo.getSourceFunction();
- sourceFunctions.add(sourceFunction);
Function destinationFunction = matchInfo.getDestinationFunction();
- destinationFunctions.add(destinationFunction);
+ model.addMatch(sourceFunction, destinationFunction);
}
FunctionComparisonService service = tool.getService(FunctionComparisonService.class);
- service.compareFunctions(sourceFunctions, destinationFunctions);
+ service.createCustomComparison(model, null);
}
// callback method from the MatchTableSelectionAction
diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/onetomany/VTMatchOneToManyTableProvider.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/onetomany/VTMatchOneToManyTableProvider.java
index 256d94cb98..41a245b539 100644
--- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/onetomany/VTMatchOneToManyTableProvider.java
+++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/onetomany/VTMatchOneToManyTableProvider.java
@@ -34,6 +34,7 @@ import docking.widgets.table.threaded.ThreadedTableModel;
import generic.theme.GColor;
import generic.theme.GIcon;
import ghidra.app.services.FunctionComparisonService;
+import ghidra.app.services.MatchedFunctionComparisonModel;
import ghidra.feature.vt.api.impl.VTEvent;
import ghidra.feature.vt.api.main.*;
import ghidra.feature.vt.gui.actions.*;
@@ -156,30 +157,24 @@ public abstract class VTMatchOneToManyTableProvider extends ComponentProviderAda
private void compareFunctions(VTMatchOneToManyContext c) {
List selectedMatches = c.getSelectedMatches();
- Set leftFunctions = new HashSet<>();
- Set rightFunctions = new HashSet<>();
+ MatchedFunctionComparisonModel model = new MatchedFunctionComparisonModel();
for (VTMatch match : selectedMatches) {
MatchInfo matchInfo = controller.getMatchInfo(match);
- // Whichever codebrowser we are currently in, is what will be on the left
+ // Whichever side we are currently in, is what will be on the left
// side of the compare functions window.
- Function leftFunction = matchInfo.getSourceFunction(),
- rightFunction = matchInfo.getDestinationFunction();
+ Function leftFunction = matchInfo.getSourceFunction();
+ Function rightFunction = matchInfo.getDestinationFunction();
if (!isSource) {
leftFunction = matchInfo.getDestinationFunction();
rightFunction = matchInfo.getSourceFunction();
}
- leftFunctions.add(leftFunction);
- rightFunctions.add(rightFunction);
+ model.addMatch(leftFunction, rightFunction);
}
- // NOTE: in this case the left functions will always be the same function (ie the one in the
- // current codebrowser) so leftFunctions will be size one. The rightFunctions will be one or
- // more since the src/dst match tables contain all possible matches to the current listing
- // function.
FunctionComparisonService service = tool.getService(FunctionComparisonService.class);
- service.compareFunctions(leftFunctions, rightFunctions);
+ service.createCustomComparison(model, null);
}
@Override
diff --git a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLQuery.java b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLQuery.java
index 2778a9e848..cd034c1063 100644
--- a/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLQuery.java
+++ b/Ghidra/Framework/Project/src/main/java/ghidra/framework/protocol/ghidra/GhidraURLQuery.java
@@ -129,7 +129,7 @@ public abstract class GhidraURLQuery {
content = wrappedContent.getContent(resultHandler);
}
catch (IOException e) {
- resultHandler.handleError("Content Not Found", e.getMessage(), null, e);
+ resultHandler.handleError("Content Not Found", e.getMessage(), ghidraUrl, e);
return;
}
diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionComparisonScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionComparisonScreenShots.java
index ce219cb949..fac7c82046 100644
--- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionComparisonScreenShots.java
+++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionComparisonScreenShots.java
@@ -25,7 +25,7 @@ import javax.swing.table.TableColumnModel;
import org.junit.Test;
import docking.action.DockingActionIf;
-import docking.widgets.dialogs.TableChooserDialog;
+import docking.widgets.dialogs.TableSelectionDialog;
import docking.widgets.table.GFilterTable;
import docking.widgets.table.GTable;
import ghidra.app.cmd.disassemble.DisassembleCommand;
@@ -94,12 +94,11 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator {
f2.setName("FunctionB", SourceType.USER_DEFINED);
destListing.setComment(addr(0x004118c0), CodeUnit.PLATE_COMMENT, null);
- FunctionComparisonProviderManager providerMgr =
- getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin);
- FunctionComparisonProvider functionComparisonProvider =
- providerMgr.compareFunctions(f1, f2);
+ plugin.createComparison(f1, f2);
+ FunctionComparisonProvider provider =
+ waitForComponentProvider(FunctionComparisonProvider.class);
FunctionComparisonPanel functionComparisonPanel =
- functionComparisonProvider.getComponent();
+ provider.getComponent();
runSwing(() -> {
functionComparisonPanel.setCurrentTabbedComponent("Listing View");
ListingCodeComparisonPanel dualListing =
@@ -125,9 +124,7 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator {
Function f1 = getFunction(sourceProgram, addr(0x004118f0));
Function f2 = getFunction(destinationProgram, addr(0x004118c0));
- FunctionComparisonProviderManager providerMgr =
- getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin);
- providerMgr.compareFunctions(f1, f2);
+ plugin.createComparison(f1, f2);
captureActionIcon("Add Functions To Comparison");
}
@@ -137,9 +134,7 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator {
Function f1 = getFunction(sourceProgram, addr(0x004118f0));
Function f2 = getFunction(destinationProgram, addr(0x004118c0));
- FunctionComparisonProviderManager providerMgr =
- getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin);
- providerMgr.compareFunctions(f1, f2);
+ plugin.createComparison(f1, f2);
captureActionIcon("Remove Functions");
}
@@ -149,9 +144,7 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator {
Function f1 = getFunction(sourceProgram, addr(0x004118f0));
Function f2 = getFunction(destinationProgram, addr(0x004118c0));
- FunctionComparisonProviderManager providerMgr =
- getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin);
- providerMgr.compareFunctions(CollectionUtils.asSet(f1, f2));
+ plugin.createComparison(CollectionUtils.asSet(f1, f2));
captureActionIcon("Compare Next Function");
}
@@ -161,13 +154,7 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator {
Function f1 = getFunction(sourceProgram, addr(0x004118f0));
Function f2 = getFunction(destinationProgram, addr(0x004118c0));
- FunctionComparisonProviderManager providerMgr =
- getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin);
- FunctionComparisonProvider functionComparisonProvider =
- providerMgr.compareFunctions(CollectionUtils.asSet(f1, f2));
- MultiFunctionComparisonPanel panel =
- (MultiFunctionComparisonPanel) functionComparisonProvider.getComponent();
- panel.getFocusedComponent().setSelectedIndex(1);
+ plugin.createComparison(CollectionUtils.asSet(f1, f2));
captureActionIcon("Compare Previous Function");
}
@@ -177,21 +164,18 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator {
Function f1 = getFunction(sourceProgram, addr(0x004118f0));
Function f2 = getFunction(destinationProgram, addr(0x004118c0));
- FunctionComparisonProviderManager providerMgr =
- getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin);
- providerMgr.compareFunctions(CollectionUtils.asSet(f1, f2));
+ plugin.createComparison(CollectionUtils.asSet(f1, f2));
waitForSwing();
- DockingActionIf openTableAction = getAction(plugin, "Add Functions To Comparison");
+ DockingActionIf openTableAction = getAction(tool, "Add Functions To Comparison");
performAction(openTableAction, false);
- TableChooserDialog> dialog =
- waitForDialogComponent(TableChooserDialog.class);
+ TableSelectionDialog> dialog = waitForDialogComponent(TableSelectionDialog.class);
setColumnSizes(dialog);
captureDialog(dialog);
}
- private void setColumnSizes(TableChooserDialog> dialog) {
+ private void setColumnSizes(TableSelectionDialog> dialog) {
// note: these values are rough values found by trial-and-error
GFilterTable> filter = (GFilterTable>) getInstanceField("gFilterTable", dialog);
diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/compare/CompareFunctionsDecompilerViewTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/compare/CompareFunctionsDecompilerViewTest.java
index 9c01bd5428..cc22a7b198 100644
--- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/compare/CompareFunctionsDecompilerViewTest.java
+++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/compare/CompareFunctionsDecompilerViewTest.java
@@ -15,18 +15,23 @@
*/
package ghidra.app.plugin.compare;
+import static ghidra.util.datastruct.Duo.Side.*;
import static org.junit.Assert.*;
+import java.util.List;
import java.util.Set;
import org.junit.*;
-import ghidra.app.plugin.core.functioncompare.*;
+import ghidra.app.plugin.core.functioncompare.FunctionComparisonPlugin;
+import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider;
+import ghidra.app.services.FunctionComparisonModel;
import ghidra.codecompare.decompile.CDisplay;
import ghidra.codecompare.decompile.DecompilerCodeComparisonPanel;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.test.*;
+import ghidra.util.datastruct.Duo.Side;
/**
* Tests for the {@link FunctionComparisonPlugin function comparison plugin}
@@ -39,7 +44,6 @@ public class CompareFunctionsDecompilerViewTest extends AbstractGhidraHeadedInte
private Function fun1;
private Function fun2;
private FunctionComparisonPlugin plugin;
- private FunctionComparisonProvider provider;
@Before
public void setUp() throws Exception {
@@ -65,10 +69,13 @@ public class CompareFunctionsDecompilerViewTest extends AbstractGhidraHeadedInte
@Test
public void testDecompDifView() throws Exception {
- Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(fun1, fun2);
- provider = compareFunctions(functions);
+ Set functions = Set.of(fun1, fun2);
+ compareFunctions(functions);
- CompareFunctionsTestUtility.checkSourceFunctions(provider, fun1, fun2);
+ FunctionComparisonProvider provider =
+ waitForComponentProvider(FunctionComparisonProvider.class);
+
+ checkFunctions(provider, LEFT, fun1, fun1, fun2);
DecompilerCodeComparisonPanel panel =
(DecompilerCodeComparisonPanel) provider
.getCodeComparisonPanelByName(DecompilerCodeComparisonPanel.NAME);
@@ -78,6 +85,18 @@ public class CompareFunctionsDecompilerViewTest extends AbstractGhidraHeadedInte
assertHasLines(panel.getRightPanel(), 23);
}
+ private void checkFunctions(FunctionComparisonProvider provider, Side side,
+ Function activeFunction, Function... functions) {
+ Set funcs = Set.of(functions);
+
+ FunctionComparisonModel model = provider.getModel();
+ assertEquals(activeFunction, model.getActiveFunction(side));
+
+ List fcs = model.getFunctions(side);
+ assertEquals(fcs.size(), funcs.size());
+ assertTrue(fcs.containsAll(funcs));
+ }
+
private void assertHasLines(CDisplay panel, int lineCount) {
assertEquals(lineCount, panel.getDecompilerPanel().getLines().size());
}
@@ -88,11 +107,9 @@ public class CompareFunctionsDecompilerViewTest extends AbstractGhidraHeadedInte
waitForSwing();
}
- private FunctionComparisonProvider compareFunctions(Set functions) {
- provider = runSwing(() -> plugin.compareFunctions(functions));
- provider.setVisible(true);
+ private void compareFunctions(Set functions) {
+ runSwing(() -> plugin.createComparison(functions));
waitForSwing();
- return provider;
}
private Program buildTestProgram() throws Exception {