diff --git a/Ghidra/Features/BSim/ghidra_scripts/LocalBSimQueryScript.java b/Ghidra/Features/BSim/ghidra_scripts/LocalBSimQueryScript.java index 2523dcae72..8e80902e8a 100644 --- a/Ghidra/Features/BSim/ghidra_scripts/LocalBSimQueryScript.java +++ b/Ghidra/Features/BSim/ghidra_scripts/LocalBSimQueryScript.java @@ -23,9 +23,9 @@ import org.apache.commons.collections4.IteratorUtils; import generic.lsh.vector.*; import ghidra.app.decompiler.DecompileException; -import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider; import ghidra.app.script.GhidraScript; import ghidra.app.services.FunctionComparisonService; +import ghidra.app.services.MatchedFunctionComparisonModel; import ghidra.app.tablechooser.*; import ghidra.features.bsim.query.*; import ghidra.features.bsim.query.client.Configuration; @@ -341,7 +341,7 @@ public class LocalBSimQueryScript extends GhidraScript { class CompareMatchesExecutor implements TableChooserExecutor { private FunctionComparisonService compareService; - private FunctionComparisonProvider comparisonProvider; + private MatchedFunctionComparisonModel model; public CompareMatchesExecutor() { compareService = state.getTool().getService(FunctionComparisonService.class); @@ -355,14 +355,11 @@ public class LocalBSimQueryScript extends GhidraScript { @Override public boolean execute(AddressableRowObject rowObject) { LocalBSimMatch match = (LocalBSimMatch) rowObject; - if (comparisonProvider == null) { - comparisonProvider = - compareService.compareFunctions(match.getSourceFunc(), match.getTargetFunc()); - } - else { - compareService.compareFunctions(match.getSourceFunc(), match.getTargetFunc(), - comparisonProvider); + if (model == null) { + model = new MatchedFunctionComparisonModel(); + compareService.createCustomComparison(model, null); } + model.addMatch(match.getSourceFunc(), match.getTargetFunc()); return false; } } diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/results/BSimSearchResultsProvider.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/results/BSimSearchResultsProvider.java index cfbad57423..f2afad0098 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/results/BSimSearchResultsProvider.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/results/BSimSearchResultsProvider.java @@ -35,7 +35,6 @@ import docking.action.builder.ToggleActionBuilder; import docking.widgets.table.RowObjectTableModel; import generic.lsh.vector.LSHVectorFactory; import generic.theme.GIcon; -import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider; import ghidra.app.services.*; import ghidra.features.bsim.gui.BSimSearchPlugin; import ghidra.features.bsim.gui.filters.BSimFilterType; @@ -272,22 +271,24 @@ public class BSimSearchResultsProvider extends ComponentProviderAdapter { Msg.error(this, "Function Comparison Service not found!"); return; } - FunctionComparisonProvider comparisonProvider = service.createFunctionComparisonProvider(); - comparisonProvider.removeAddFunctionsAction(); + MatchedFunctionComparisonModel model = new MatchedFunctionComparisonModel(); List selectedRowObjects = matchesTable.getSelectedRowObjects(); Set openedPrograms = new HashSet<>(); for (BSimMatchResult row : selectedRowObjects) { try { Function originalFunction = getOriginalFunction(row); Function matchFunction = getMatchFunction(row, openedPrograms); - comparisonProvider.getModel().compareFunctions(originalFunction, matchFunction); + model.addMatch(originalFunction, matchFunction); } catch (FunctionComparisonException e) { Msg.showError(this, null, "Unable to Compare Functions", "Compare Functions: " + e.getMessage()); } } - comparisonProvider.setCloseListener(() -> { + if (model.isEmpty()) { + return; + } + service.createCustomComparison(model, () -> { for (Program remote : openedPrograms) { remote.release(BSimSearchResultsProvider.this); } diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/FunctionComparison.htm b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/FunctionComparison.htm index 818f52c0f4..b211f66635 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/FunctionComparison.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/FunctionComparison.htm @@ -10,22 +10,34 @@

Function Comparison Window

+

The Function Comparison window provides a way to compare two or more functions in a simple side-by-side panel.

- + +

+
+ +

+
+
+

To begin, select a function (or multiple functions) from the listing or the function table. Then right-click and select the Compare Selected Functions option.

A new function comparison window will appear (subsequent invocations of this option will create a new tab in the existing window).

-

- -
- -

-
+ +
+ +

Additional functions can be added to the + last comparison window using the Add To Last Comparison popup action that will + appear on components that can supply one or more functions. (Currently supported in the Listing + and the Functions Window.) +

+
+

Listing View

@@ -176,7 +188,7 @@ Header.

-

Show Listings Side-by-Side

+

Show Listings Side-by-Side

The two listings which display each of the functions can be displayed side by side or one above the other. To change how the two listings are positioned relative to each @@ -492,7 +504,7 @@ a new function comparison window populated with the called functions.

-

Show Decompilers Side-by-Side

+

Show Decompilers Side-by-Side

This toggles the decompiler panels between a vertical split and a horizontal split.

diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/context/FunctionSupplierContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/context/FunctionSupplierContext.java new file mode 100644 index 0000000000..5e1c138a20 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/context/FunctionSupplierContext.java @@ -0,0 +1,41 @@ +/* ### + * 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.context; + +import java.util.Set; + +import docking.ActionContext; +import ghidra.program.model.listing.Function; + +/** + * A "mix-in" interface that specific implementers of {@link ActionContext} may also implement if + * they can supply functions in their action context. Actions that want to work on functions + * can look for this interface, which can used in a variety of contexts. + */ +public interface FunctionSupplierContext extends ActionContext { + + /** + * Returns true if this context can supply one or more functions. + * @return true if this context can supply one or more functions + */ + public boolean hasFunctions(); + + /** + * Returns the set of functions that this context object can supply. + * @return the set of functions that this context object can supply + */ + public Set getFunctions(); +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramLocationActionContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramLocationActionContext.java index d4c40daf1f..0038465489 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramLocationActionContext.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramLocationActionContext.java @@ -15,13 +15,16 @@ */ package ghidra.app.context; +import java.util.HashSet; +import java.util.Set; + import docking.ComponentProvider; import ghidra.program.model.address.Address; import ghidra.program.model.listing.*; -import ghidra.program.util.ProgramLocation; -import ghidra.program.util.ProgramSelection; +import ghidra.program.util.*; -public class ProgramLocationActionContext extends ProgramActionContext { +public class ProgramLocationActionContext extends ProgramActionContext + implements FunctionSupplierContext { private final ProgramLocation location; private final ProgramSelection selection; @@ -54,7 +57,6 @@ public class ProgramLocationActionContext extends ProgramActionContext { public ProgramSelection getHighlight() { return highlight == null ? new ProgramSelection() : highlight; - } /** @@ -94,4 +96,39 @@ public class ProgramLocationActionContext extends ProgramActionContext { public boolean hasHighlight() { return (highlight != null && !highlight.isEmpty()); } + + @Override + public boolean hasFunctions() { + if (selection == null || selection.isEmpty()) { + return getFunctionForLocation() != null; + } + // see if selection contains at least one function + FunctionManager functionManager = program.getFunctionManager(); + FunctionIterator functionIter = functionManager.getFunctions(selection, true); + return functionIter.hasNext(); + } + + @Override + public Set getFunctions() { + Set 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 panelClass : classes) { try { Constructor 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 {