GP-4634 Refactored Function Compare Service and added action to add

functions to the last comparison window.
This commit is contained in:
ghidragon 2024-06-04 15:33:20 -04:00
parent c19facf226
commit ddd2f22c28
45 changed files with 2086 additions and 3015 deletions

View file

@ -23,9 +23,9 @@ import org.apache.commons.collections4.IteratorUtils;
import generic.lsh.vector.*; import generic.lsh.vector.*;
import ghidra.app.decompiler.DecompileException; import ghidra.app.decompiler.DecompileException;
import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider;
import ghidra.app.script.GhidraScript; import ghidra.app.script.GhidraScript;
import ghidra.app.services.FunctionComparisonService; import ghidra.app.services.FunctionComparisonService;
import ghidra.app.services.MatchedFunctionComparisonModel;
import ghidra.app.tablechooser.*; import ghidra.app.tablechooser.*;
import ghidra.features.bsim.query.*; import ghidra.features.bsim.query.*;
import ghidra.features.bsim.query.client.Configuration; import ghidra.features.bsim.query.client.Configuration;
@ -341,7 +341,7 @@ public class LocalBSimQueryScript extends GhidraScript {
class CompareMatchesExecutor implements TableChooserExecutor { class CompareMatchesExecutor implements TableChooserExecutor {
private FunctionComparisonService compareService; private FunctionComparisonService compareService;
private FunctionComparisonProvider comparisonProvider; private MatchedFunctionComparisonModel model;
public CompareMatchesExecutor() { public CompareMatchesExecutor() {
compareService = state.getTool().getService(FunctionComparisonService.class); compareService = state.getTool().getService(FunctionComparisonService.class);
@ -355,14 +355,11 @@ public class LocalBSimQueryScript extends GhidraScript {
@Override @Override
public boolean execute(AddressableRowObject rowObject) { public boolean execute(AddressableRowObject rowObject) {
LocalBSimMatch match = (LocalBSimMatch) rowObject; LocalBSimMatch match = (LocalBSimMatch) rowObject;
if (comparisonProvider == null) { if (model == null) {
comparisonProvider = model = new MatchedFunctionComparisonModel();
compareService.compareFunctions(match.getSourceFunc(), match.getTargetFunc()); compareService.createCustomComparison(model, null);
}
else {
compareService.compareFunctions(match.getSourceFunc(), match.getTargetFunc(),
comparisonProvider);
} }
model.addMatch(match.getSourceFunc(), match.getTargetFunc());
return false; return false;
} }
} }

View file

@ -35,7 +35,6 @@ import docking.action.builder.ToggleActionBuilder;
import docking.widgets.table.RowObjectTableModel; import docking.widgets.table.RowObjectTableModel;
import generic.lsh.vector.LSHVectorFactory; import generic.lsh.vector.LSHVectorFactory;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.features.bsim.gui.BSimSearchPlugin; import ghidra.features.bsim.gui.BSimSearchPlugin;
import ghidra.features.bsim.gui.filters.BSimFilterType; import ghidra.features.bsim.gui.filters.BSimFilterType;
@ -272,22 +271,24 @@ public class BSimSearchResultsProvider extends ComponentProviderAdapter {
Msg.error(this, "Function Comparison Service not found!"); Msg.error(this, "Function Comparison Service not found!");
return; return;
} }
FunctionComparisonProvider comparisonProvider = service.createFunctionComparisonProvider(); MatchedFunctionComparisonModel model = new MatchedFunctionComparisonModel();
comparisonProvider.removeAddFunctionsAction();
List<BSimMatchResult> selectedRowObjects = matchesTable.getSelectedRowObjects(); List<BSimMatchResult> selectedRowObjects = matchesTable.getSelectedRowObjects();
Set<Program> openedPrograms = new HashSet<>(); Set<Program> openedPrograms = new HashSet<>();
for (BSimMatchResult row : selectedRowObjects) { for (BSimMatchResult row : selectedRowObjects) {
try { try {
Function originalFunction = getOriginalFunction(row); Function originalFunction = getOriginalFunction(row);
Function matchFunction = getMatchFunction(row, openedPrograms); Function matchFunction = getMatchFunction(row, openedPrograms);
comparisonProvider.getModel().compareFunctions(originalFunction, matchFunction); model.addMatch(originalFunction, matchFunction);
} }
catch (FunctionComparisonException e) { catch (FunctionComparisonException e) {
Msg.showError(this, null, "Unable to Compare Functions", Msg.showError(this, null, "Unable to Compare Functions",
"Compare Functions: " + e.getMessage()); "Compare Functions: " + e.getMessage());
} }
} }
comparisonProvider.setCloseListener(() -> { if (model.isEmpty()) {
return;
}
service.createCustomComparison(model, () -> {
for (Program remote : openedPrograms) { for (Program remote : openedPrograms) {
remote.release(BSimSearchResultsProvider.this); remote.release(BSimSearchResultsProvider.this);
} }

View file

@ -11,21 +11,33 @@
<H1><A name="FunctionComparisonPlugin"></A> <A name="Function_Comparison"></A> <A name= <H1><A name="FunctionComparisonPlugin"></A> <A name="Function_Comparison"></A> <A name=
"FunctionComparison"></A> Function Comparison Window</H1> "FunctionComparison"></A> Function Comparison Window</H1>
<P>The Function Comparison window provides a way to compare two or more <P>The Function Comparison window provides a way to compare two or more
functions in a simple side-by-side panel. </P> functions in a simple side-by-side panel. </P>
<BR><BR>
<CENTER>
<IMG alt="" border="0" src="images/FunctionComparisonWindow.png">
</CENTER><BR>
<BR>
<BLOCKQUOTE>
<P>To begin, select a function (or multiple functions) from the listing or <P>To begin, select a function (or multiple functions) from the listing or
the <A HREF="help/topics/FunctionWindowPlugin/function_window.htm">function table</a>. the <A HREF="help/topics/FunctionWindowPlugin/function_window.htm">function table</a>.
Then right-click and select the <b>Compare Selected Functions</b> option.</P> Then right-click and select the <b>Compare Selected Functions</b> option.</P>
<P><A name="Dual_Listing"></A>A new function comparison window will appear (subsequent <P><A name="Dual_Listing"></A>A new function comparison window will appear (subsequent
invocations of this option will create a new tab in the existing window).</P> invocations of this option will create a new tab in the existing window).</P>
<BR><BR>
<CENTER> <BLOCKQUOTE>
<IMG alt="" border="0" src="images/FunctionComparisonWindow.png"> <A name="Function_Comparison_Add_To"></A>
</CENTER><BR> <P><IMG src="help/shared/tip.png" border="0">Additional functions can be added to the
<BR> last comparison window using the <I><B>Add To Last Comparison</B></I> popup action that will
appear on components that can supply one or more functions. (Currently supported in the Listing
and the Functions Window.)
</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H2><A name="Listing_View"></A>Listing View</H2> <H2><A name="Listing_View"></A>Listing View</H2>
@ -176,7 +188,7 @@
Header</A>.</P> Header</A>.</P>
</BLOCKQUOTE> </BLOCKQUOTE>
<H4><A name="Dual_Listing_Toggle_Orientation"></A>Show Listings Side-by-Side</H4> <H4><A name="Dual_Listing_View_Toggle_Orientation"></A>Show Listings Side-by-Side</H4>
<BLOCKQUOTE> <BLOCKQUOTE>
<P>The two listings which display each of the functions can be displayed side by side or <P>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 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. </P> a new function comparison window populated with the called functions. </P>
</BLOCKQUOTE> </BLOCKQUOTE>
<H4><A name="Dual_Decompiler_Toggle_Orientation"></A>Show Decompilers Side-by-Side</H4> <H4><A name="Dual_Decompiler_View_Toggle_Orientation"></A>Show Decompilers Side-by-Side</H4>
<BLOCKQUOTE> <BLOCKQUOTE>
<P> This toggles the decompiler panels between a vertical split and a horizontal split. </P> <P> This toggles the decompiler panels between a vertical split and a horizontal split. </P>
</BLOCKQUOTE> </BLOCKQUOTE>

View file

@ -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<Function> getFunctions();
}

View file

@ -15,13 +15,16 @@
*/ */
package ghidra.app.context; package ghidra.app.context;
import java.util.HashSet;
import java.util.Set;
import docking.ComponentProvider; import docking.ComponentProvider;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.*;
import ghidra.program.util.ProgramSelection;
public class ProgramLocationActionContext extends ProgramActionContext { public class ProgramLocationActionContext extends ProgramActionContext
implements FunctionSupplierContext {
private final ProgramLocation location; private final ProgramLocation location;
private final ProgramSelection selection; private final ProgramSelection selection;
@ -54,7 +57,6 @@ public class ProgramLocationActionContext extends ProgramActionContext {
public ProgramSelection getHighlight() { public ProgramSelection getHighlight() {
return highlight == null ? new ProgramSelection() : highlight; return highlight == null ? new ProgramSelection() : highlight;
} }
/** /**
@ -94,4 +96,39 @@ public class ProgramLocationActionContext extends ProgramActionContext {
public boolean hasHighlight() { public boolean hasHighlight() {
return (highlight != null && !highlight.isEmpty()); 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<Function> getFunctions() {
Set<Function> 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);
}
} }

View file

@ -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 <code>source</code> function may be associated with one
* or more <code>target</code> functions.
* <p>
* This is the basic unit for the
* {@link FunctionComparisonModel function comparison data model}
*/
public class FunctionComparison implements Comparable<FunctionComparison> {
private Function source;
/** Use a tree so functions are always kept in sorted order */
private FunctionComparator functionComparator = new FunctionComparator();
private Set<Function> 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<Function> 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<Function> functions) {
targets.addAll(functions);
}
/**
* Removes the given function from the target list.
* <p>
* 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<Function> {
@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);
}
}
}

View file

@ -15,9 +15,9 @@
*/ */
package ghidra.app.plugin.core.functioncompare; package ghidra.app.plugin.core.functioncompare;
import java.util.List;
import ghidra.app.services.FunctionComparisonModel; 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 * Allows subscribers to register for {@link FunctionComparisonModel function
@ -26,9 +26,15 @@ import ghidra.app.services.FunctionComparisonModel;
public interface FunctionComparisonModelListener { public interface FunctionComparisonModelListener {
/** /**
* Invoked when the comparison model has changed * Notification that the selected function changed on one side or the other.
* * @param side the side whose selected function changed
* @param model the current state of the model * @param function the new selected function for the given side
*/ */
public void modelChanged(List<FunctionComparison> 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();
} }

View file

@ -37,7 +37,6 @@ import generic.theme.GIcon;
import ghidra.app.util.viewer.listingpanel.ListingCodeComparisonPanel; import ghidra.app.util.viewer.listingpanel.ListingCodeComparisonPanel;
import ghidra.app.util.viewer.util.*; import ghidra.app.util.viewer.util.*;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSet;
import ghidra.program.model.address.AddressSetView; import ghidra.program.model.address.AddressSetView;
@ -77,28 +76,19 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
private JTabbedPane tabbedPane; private JTabbedPane tabbedPane;
private Map<String, JComponent> tabNameToComponentMap; private Map<String, JComponent> tabNameToComponentMap;
protected PluginTool tool;
protected ComponentProviderAdapter provider;
private List<CodeComparisonPanel> codeComparisonPanels; private List<CodeComparisonPanel> codeComparisonPanels;
private ToggleScrollLockAction toggleScrollLockAction; private ToggleScrollLockAction toggleScrollLockAction;
private boolean syncScrolling = false; private boolean syncScrolling = false;
private Duo<ComparisonData> comparisonData = new Duo<ComparisonData>(); private Duo<ComparisonData> comparisonData = new Duo<ComparisonData>();
/** public FunctionComparisonPanel(PluginTool tool, String owner) {
* 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;
this.comparisonData = new Duo<>(EMPTY, EMPTY); this.comparisonData = new Duo<>(EMPTY, EMPTY);
this.codeComparisonPanels = getCodeComparisonPanels();
codeComparisonPanels = getCodeComparisonPanels(tool, owner);
tabNameToComponentMap = new HashMap<>(); tabNameToComponentMap = new HashMap<>();
createMainPanel(); createMainPanel();
createActions(); createActions(owner);
setScrollingSyncState(true); setScrollingSyncState(true);
help.registerHelp(this, new HelpLocation(HELP_TOPIC, "Function Comparison")); help.registerHelp(this, new HelpLocation(HELP_TOPIC, "Function Comparison"));
} }
@ -223,13 +213,6 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
tabChanged(); 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 * 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 * Remove all views in the tabbed pane
*/ */
public void dispose() { public void dispose() {
tool.removeComponentProvider(provider);
tabbedPane.removeAll(); tabbedPane.removeAll();
setVisible(false); setVisible(false);
@ -514,16 +496,16 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
/** /**
* Creates the actions available for this panel * Creates the actions available for this panel
*/ */
private void createActions() { private void createActions(String owner) {
toggleScrollLockAction = new ToggleScrollLockAction(); toggleScrollLockAction = new ToggleScrollLockAction(owner);
} }
/** /**
* Action that sets the scrolling state of the comparison panels * Action that sets the scrolling state of the comparison panels
*/ */
private class ToggleScrollLockAction extends ToggleDockingAction { private class ToggleScrollLockAction extends ToggleDockingAction {
ToggleScrollLockAction() { ToggleScrollLockAction(String owner) {
super("Synchronize Scrolling of Dual View", provider.getName()); super("Synchronize Scrolling of Dual View", owner);
setDescription("Lock/Unlock Synchronized Scrolling of Dual View"); setDescription("Lock/Unlock Synchronized Scrolling of Dual View");
setToolBarData(new ToolBarData(UNSYNC_SCROLLING_ICON, SCROLLING_GROUP)); setToolBarData(new ToolBarData(UNSYNC_SCROLLING_ICON, SCROLLING_GROUP));
setEnabled(true); setEnabled(true);
@ -550,25 +532,27 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener {
* *
* @return the CodeComparisonPanels which are extension points * @return the CodeComparisonPanels which are extension points
*/ */
private List<CodeComparisonPanel> getCodeComparisonPanels() { private List<CodeComparisonPanel> getCodeComparisonPanels(PluginTool tool, String owner) {
if (codeComparisonPanels == null) { if (codeComparisonPanels == null) {
codeComparisonPanels = createAllPossibleCodeComparisonPanels(); codeComparisonPanels = createAllPossibleCodeComparisonPanels(tool, owner);
codeComparisonPanels.sort((p1, p2) -> p1.getName().compareTo(p2.getName())); codeComparisonPanels.sort((p1, p2) -> p1.getName().compareTo(p2.getName()));
} }
return codeComparisonPanels; return codeComparisonPanels;
} }
@SuppressWarnings({ "rawtypes", "unchecked" }) private List<CodeComparisonPanel> createAllPossibleCodeComparisonPanels(PluginTool tool,
private ArrayList<CodeComparisonPanel> createAllPossibleCodeComparisonPanels() { String owner) {
ArrayList<CodeComparisonPanel> instances =
new ArrayList<>(); List<CodeComparisonPanel> instances = new ArrayList<>();
List<Class<? extends CodeComparisonPanel>> classes = List<Class<? extends CodeComparisonPanel>> classes =
ClassSearcher.getClasses(CodeComparisonPanel.class); ClassSearcher.getClasses(CodeComparisonPanel.class);
for (Class<? extends CodeComparisonPanel> panelClass : classes) { for (Class<? extends CodeComparisonPanel> panelClass : classes) {
try { try {
Constructor<? extends CodeComparisonPanel> constructor = Constructor<? extends CodeComparisonPanel> constructor =
panelClass.getConstructor(String.class, PluginTool.class); panelClass.getConstructor(String.class, PluginTool.class);
CodeComparisonPanel panel = constructor.newInstance(provider.getName(), tool); CodeComparisonPanel panel = constructor.newInstance(owner, tool);
instances.add(panel); instances.add(panel);
} }
catch (NoSuchMethodException | SecurityException | InstantiationException catch (NoSuchMethodException | SecurityException | InstantiationException

View file

@ -15,16 +15,16 @@
*/ */
package ghidra.app.plugin.core.functioncompare; package ghidra.app.plugin.core.functioncompare;
import java.util.Set; import java.util.*;
import java.util.function.Supplier; import java.util.function.Consumer;
import docking.action.builder.ActionBuilder;
import ghidra.app.CorePluginPackage; import ghidra.app.CorePluginPackage;
import ghidra.app.context.FunctionSupplierContext;
import ghidra.app.events.*; import ghidra.app.events.*;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin; import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsAction; import ghidra.app.services.*;
import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsFromListingAction;
import ghidra.app.services.FunctionComparisonService;
import ghidra.framework.model.*; import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginInfo;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
@ -33,7 +33,9 @@ import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramChangeRecord; import ghidra.program.util.ProgramChangeRecord;
import ghidra.program.util.ProgramEvent; import ghidra.program.util.ProgramEvent;
import ghidra.util.HelpLocation;
import ghidra.util.Swing; import ghidra.util.Swing;
import utility.function.Callback;
/** /**
* Allows users to create function comparisons that are displayed * Allows users to create function comparisons that are displayed
@ -58,31 +60,18 @@ import ghidra.util.Swing;
public class FunctionComparisonPlugin extends ProgramPlugin public class FunctionComparisonPlugin extends ProgramPlugin
implements DomainObjectListener, FunctionComparisonService { implements DomainObjectListener, FunctionComparisonService {
static final String MENU_PULLRIGHT = "CompareFunctions"; // Keep a stack of recently added providers so that the "add to comparison" service methods
static final String POPUP_MENU_GROUP = "CompareFunction"; // can easily add to the last created provider.
private Deque<FunctionComparisonProvider> providers = new ArrayDeque<>();
private FunctionComparisonProviderManager functionComparisonManager;
/**
* Constructor
*
* @param tool the tool that owns this plugin
*/
public FunctionComparisonPlugin(PluginTool tool) { public FunctionComparisonPlugin(PluginTool tool) {
super(tool); super(tool);
functionComparisonManager = new FunctionComparisonProviderManager(this); createActions();
}
@Override
protected void init() {
CompareFunctionsAction compareFunctionsAction =
new CompareFunctionsFromListingAction(tool, getName());
tool.addAction(compareFunctionsAction);
} }
@Override @Override
public void dispose() { public void dispose() {
functionComparisonManager.dispose(); foreEachProvider(p -> p.closeComponent());
} }
@Override @Override
@ -92,8 +81,8 @@ public class FunctionComparisonPlugin extends ProgramPlugin
@Override @Override
protected void programClosed(Program program) { protected void programClosed(Program program) {
functionComparisonManager.closeProviders(program);
program.removeListener(this); program.removeListener(this);
foreEachProvider(p -> p.programClosed(program));
} }
/** /**
@ -111,7 +100,7 @@ public class FunctionComparisonPlugin extends ProgramPlugin
EventType eventType = doRecord.getEventType(); EventType eventType = doRecord.getEventType();
if (eventType == DomainObjectEvent.RESTORED) { if (eventType == DomainObjectEvent.RESTORED) {
functionComparisonManager.domainObjectRestored((Program) ev.getSource()); domainObjectRestored((Program) ev.getSource());
} }
else if (eventType == ProgramEvent.FUNCTION_REMOVED) { else if (eventType == ProgramEvent.FUNCTION_REMOVED) {
ProgramChangeRecord rec = (ProgramChangeRecord) ev.getChangeRecord(i); 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<FunctionComparisonProvider> comparer) {
if (Swing.isSwingThread()) {
return comparer.get();
}
return Swing.runNow(comparer);
}
void providerClosed(FunctionComparisonProvider provider) { void providerClosed(FunctionComparisonProvider provider) {
functionComparisonManager.providerClosed(provider); providers.remove(provider);
} }
void removeFunction(Function function) {
Swing.runIfSwingOrRunLater(() -> doRemoveFunction(function));
}
private void foreEachProvider(Consumer<FunctionComparisonProvider> c) {
// copy needed because this may cause callbacks to remove a provider from our list
List<FunctionComparisonProvider> 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 // Service Methods
//================================================================================================== //==================================================================================================
@Override @Override
public void removeFunction(Function function) { public void createComparison(Collection<Function> functions) {
runOnSwingNonBlocking(() -> functionComparisonManager.removeFunction(function)); if (functions.isEmpty()) {
return;
}
DefaultFunctionComparisonModel model = new DefaultFunctionComparisonModel(functions);
Swing.runLater(() -> createProvider(model));
} }
@Override @Override
public void removeFunction(Function function, FunctionComparisonProvider provider) { public void createComparison(Function left, Function right) {
runOnSwingNonBlocking(() -> functionComparisonManager.removeFunction(function, provider)); DefaultFunctionComparisonModel model = new DefaultFunctionComparisonModel(left, right);
Swing.runLater(() -> createProvider(model));
} }
@Override @Override
public FunctionComparisonProvider createFunctionComparisonProvider() { public void addToComparison(Collection<Function> functions) {
return getFromSwingBlocking(() -> functionComparisonManager.createProvider()); FunctionComparisonProvider lastProvider = findLastDefaultProviderModel();
if (lastProvider == null) {
createComparison(functions);
}
else {
DefaultFunctionComparisonModel model =
(DefaultFunctionComparisonModel) lastProvider.getModel();
Swing.runLater(() -> model.addFunctions(functions));
}
} }
@Override @Override
public FunctionComparisonProvider compareFunctions(Function source, Function target) { public void addToComparison(Function function) {
return getFromSwingBlocking( addToComparison(Arrays.asList(function));
() -> functionComparisonManager.compareFunctions(source, target));
} }
@Override @Override
public FunctionComparisonProvider compareFunctions(Set<Function> functions) { public void createCustomComparison(FunctionComparisonModel model, Callback closeListener) {
return getFromSwingBlocking(() -> functionComparisonManager.compareFunctions(functions)); Swing.runLater(() -> createProvider(model, closeListener));
}
@Override
public FunctionComparisonProvider compareFunctions(Set<Function> sourceFunctions,
Set<Function> destinationFunctions) {
return getFromSwingBlocking(() -> functionComparisonManager
.compareFunctions(sourceFunctions, destinationFunctions));
}
@Override
public void compareFunctions(Set<Function> functions, FunctionComparisonProvider provider) {
runOnSwingNonBlocking(
() -> functionComparisonManager.compareFunctions(functions, provider));
}
@Override
public void compareFunctions(Function source, Function target,
FunctionComparisonProvider provider) {
runOnSwingNonBlocking(
() -> functionComparisonManager.compareFunctions(source, target, provider));
} }
} }

View file

@ -19,14 +19,21 @@ import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import javax.swing.Icon;
import docking.ActionContext; import docking.ActionContext;
import docking.Tool; import docking.Tool;
import docking.action.DockingAction; import docking.action.*;
import docking.action.DockingActionIf; import docking.action.builder.ActionBuilder;
import docking.action.builder.ToggleActionBuilder;
import docking.actions.PopupActionProvider; import docking.actions.PopupActionProvider;
import ghidra.app.services.FunctionComparisonModel; import docking.widgets.dialogs.TableSelectionDialog;
import ghidra.app.services.FunctionComparisonService; 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.ListingCodeComparisonPanel;
import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.app.util.viewer.listingpanel.ListingPanel;
import ghidra.app.util.viewer.util.CodeComparisonPanel; import ghidra.app.util.viewer.util.CodeComparisonPanel;
@ -34,7 +41,10 @@ import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program; 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; import utility.function.Callback;
/** /**
@ -44,62 +54,55 @@ import utility.function.Callback;
*/ */
public class FunctionComparisonProvider extends ComponentProviderAdapter public class FunctionComparisonProvider extends ComponentProviderAdapter
implements PopupActionProvider, FunctionComparisonModelListener { 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"; private static final Icon ADD_TO_COMPARISON_ICON =
protected FunctionComparisonPanel functionComparisonPanel; new GIcon("icon.plugin.functioncompare.open.function.table");
protected FunctionComparisonPlugin plugin; 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 Callback closeListener = Callback.dummy();
private ToggleDockingAction navigateToAction;
/** public FunctionComparisonProvider(FunctionComparisonPlugin plugin,
* Constructor FunctionComparisonModel model, Callback closeListener) {
* super(plugin.getTool(), "Function Comparison Provider", plugin.getName());
* @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);
this.plugin = plugin; this.plugin = plugin;
setTransient(); this.model = model;
model = new FunctionComparisonModel(); this.closeListener = Callback.dummyIfNull(closeListener);
functionComparisonPanel = new MultiFunctionComparisonPanel(this, tool, model);
model.addFunctionComparisonModelListener(this); 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 @Override
public FunctionComparisonPanel getComponent() { public FunctionComparisonPanel getComponent() {
if (functionComparisonPanel == null) {
functionComparisonPanel = new FunctionComparisonPanel(this, tool);
}
return functionComparisonPanel; return functionComparisonPanel;
} }
@Override
public void closeComponent() {
super.closeComponent();
closeListener.call();
closeListener = Callback.dummy();
}
@Override @Override
public String toString() { public String toString() {
StringBuffer buff = new StringBuffer(); StringBuffer buff = new StringBuffer();
@ -121,18 +124,28 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
} }
@Override @Override
public void removeFromTool() { public void modelDataChanged() {
tool.removePopupActionProvider(this); updateTabAndTitle();
super.removeFromTool(); tool.contextChanged(this);
plugin.providerClosed(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 @Override
public void modelChanged(List<FunctionComparison> data) { public void activeFunctionChanged(Side side, Function function) {
this.model.setComparisons(data); updateTabAndTitle();
functionComparisonPanel.reload(); tool.contextChanged(this);
setTabText(functionComparisonPanel.getDescription()); if (navigateToAction.isSelected()) {
closeIfEmpty(); goToFunction(function);
}
}
@Override
public void contextChanged() {
super.contextChanged();
maybeGoToActiveFunction();
} }
@Override @Override
@ -157,15 +170,6 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
return model; 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 * Removes any functions being displayed by this provider that are from
* the given program. If there are no functions left to display, the * 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); functionComparisonPanel.writeConfigState(getName(), saveState);
} }
/** @Override
* Perform initialization for this provider and its panel public void removeFromTool() {
*/ tool.removePopupActionProvider(this);
protected void initFunctionComparisonPanel() { super.removeFromTool();
setTabText(functionComparisonPanel.getDescription()); dispose();
addSpecificCodeComparisonActions();
tool.addPopupActionProvider(this);
setHelpLocation(new HelpLocation(HELP_TOPIC, "Function Comparison"));
} }
/** private void updateTabAndTitle() {
* Returns true if the comparison panel is empty String description = functionComparisonPanel.getDescription();
* setTabText(description);
* @return true if the panel is empty setTitle(description);
*/
boolean isEmpty() { }
return functionComparisonPanel.isEmpty();
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 <b>On</b> 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<FunctionRowObject> diag = new TableSelectionDialog<>(
"Select Functions: " + currentProgram.getName(), functionTableModel, true);
tool.showDialog(diag);
List<FunctionRowObject> rows = diag.getSelectionItems();
if (CollectionUtils.isBlank(rows)) {
return; // the table chooser can return null if the operation was cancelled
}
List<Function> 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 * Closes this provider if there are no comparisons to view
*/ */
void closeIfEmpty() { private void closeIfEmpty() {
if (isEmpty()) { if (model.isEmpty()) {
closeComponent(); 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) { public CodeComparisonPanel getCodeComparisonPanelByName(String name) {
return functionComparisonPanel.getCodeComparisonPanelByName(name); return functionComparisonPanel.getCodeComparisonPanelByName(name);
} }
public void dispose() { private void dispose() {
plugin.providerClosed(this);
closeListener.call();
closeListener = Callback.dummy();
functionComparisonPanel.dispose(); functionComparisonPanel.dispose();
} }
} }

View file

@ -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:
* <li>create new providers</li>
* <li>add comparisons to existing providers</li>
* <li>remove comparisons</li>
* <li>notify subscribers when providers are opened/closed</li>
*/
public class FunctionComparisonProviderManager implements FunctionComparisonProviderListener {
private Set<FunctionComparisonProvider> providers = new CopyOnWriteArraySet<>();
private Set<ComponentProviderActivationListener> 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<Function> 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<Function> sourceFunctions,
Set<Function> 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<Function> 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));
}
}

View file

@ -20,8 +20,6 @@ import static ghidra.util.datastruct.Duo.Side.*;
import java.awt.*; import java.awt.*;
import java.awt.event.ItemEvent; import java.awt.event.ItemEvent;
import java.awt.event.ItemListener; import java.awt.event.ItemListener;
import java.util.Iterator;
import java.util.Set;
import javax.swing.*; import javax.swing.*;
@ -29,295 +27,180 @@ import ghidra.app.services.FunctionComparisonModel;
import ghidra.app.util.viewer.util.CodeComparisonPanel; import ghidra.app.util.viewer.util.CodeComparisonPanel;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.util.datastruct.Duo;
import ghidra.util.datastruct.Duo.Side; import ghidra.util.datastruct.Duo.Side;
import help.Help;
import help.HelpService;
/** /**
* Extends the basic {@link FunctionComparisonPanel one-to-one comparison panel} * Extends the basic {@link FunctionComparisonPanel one-to-one comparison panel}
* to allow a many-to-many relationship. The panel provides a pair of combo * 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 * boxes above the function display area that allows users to select which
* functions are to be compared. * functions are to be compared.
* <p> * <P>
* Throughout this class the terms <code>source</code> and <code>target</code> * This behavior of this class is driven by the given {@link FunctionComparisonModel}. The default
* are used when referencing functions. This is because the model that backs * model displays the same set of functions on both sides. But the model interface allows for
* this panel maintains a relationship between the functions being compared * other behaviors such as having different sets of function on each side and even changing the
* such that each source function can only be compared to a specific set * set of functions on one side base on what is selected on the other side.
* of target functions. For all practical purposes, the source functions
* appear in the left-side panel and targets appear on the right.
*
*/ */
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<Function> sourceFunctionsCB;
/** Functions that will show up on the right side of the panel */
private JComboBox<Function> targetFunctionsCB;
/** Data models backing the source and target combo boxes */
private DefaultComboBoxModel<Function> sourceFunctionsCBModel;
private DefaultComboBoxModel<Function> targetFunctionsCBModel;
protected static final HelpService help = Help.getHelpService();
public static final String HELP_TOPIC = "FunctionComparison"; public static final String HELP_TOPIC = "FunctionComparison";
private FunctionComparisonModel model;
private Duo<JComboBox<Function>> comboBoxes;
private Duo<ItemListener> comboListeners;
/** /**
* Constructor * Constructor
* *
* @param provider the comparison provider associated with this panel * @param provider the comparison provider associated with this panel
* @param tool the active plugin tool * @param tool the active plugin tool
* @param model the comparison data model
*/ */
public MultiFunctionComparisonPanel(MultiFunctionComparisonProvider provider, PluginTool tool) { public MultiFunctionComparisonPanel(FunctionComparisonProvider provider, PluginTool tool,
super(provider, tool); FunctionComparisonModel model) {
super(tool, provider.getName());
this.model = model;
model.addFunctionComparisonModelListener(this);
JPanel choicePanel = new JPanel(new GridLayout(1, 2)); buildComboPanels();
choicePanel.add(createSourcePanel());
choicePanel.add(createTargetPanel());
add(choicePanel, BorderLayout.NORTH);
// 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)); getComparisonPanels().forEach(p -> p.setShowDataTitles(false));
setPreferredSize(new Dimension(1200, 600)); 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 @Override
public void reload() { public void activeFunctionChanged(Side side, Function function) {
updateComboBoxSelectIfNeeded(side, function);
reloadSourceList(); loadFunctions(model.getActiveFunction(LEFT), model.getActiveFunction(RIGHT));
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);
} }
/** @Override
* Returns the combo box (source or target) which has focus public void modelDataChanged() {
* intializeComboBox(LEFT);
* @return the focused component intializeComboBox(RIGHT);
*/ loadFunctions(model.getActiveFunction(LEFT), model.getActiveFunction(RIGHT));
public JComboBox<Function> getFocusedComponent() {
CodeComparisonPanel currentComponent = getCurrentComponent();
Side side = currentComponent.getActiveSide();
return side == LEFT ? sourceFunctionsCB : targetFunctionsCB;
} }
public Side getFocusedSide() { @Override
public void dispose() {
model.removeFunctionComparisonModelListener(this);
super.dispose();
}
Side getActiveSide() {
CodeComparisonPanel currentComponent = getCurrentComponent(); CodeComparisonPanel currentComponent = getCurrentComponent();
return currentComponent.getActiveSide(); return currentComponent.getActiveSide();
} }
/** boolean canCompareNextFunction() {
* Returns the source combo box Side activeSide = getActiveSide();
* JComboBox<Function> combo = comboBoxes.get(activeSide);
* @return the source combo box int index = combo.getSelectedIndex();
*/ return index < combo.getModel().getSize() - 1;
public JComboBox<Function> getSourceComponent() {
return sourceFunctionsCB;
} }
/** boolean canComparePreviousFunction() {
* Returns the target combo box Side activeSide = getActiveSide();
* JComboBox<Function> combo = comboBoxes.get(activeSide);
* @return the target combo box int index = combo.getSelectedIndex();
*/ return index > 0;
public JComboBox<Function> getTargetComponent() {
return targetFunctionsCB;
} }
/** void compareNextFunction() {
* Clears out and reloads the source function list. Any selection currently Side activeSide = getActiveSide();
* made on the list will be reestablished. JComboBox<Function> combo = comboBoxes.get(activeSide);
*/ int index = combo.getSelectedIndex();
private void reloadSourceList() { combo.setSelectedIndex(index + 1);
// Save off any selected item so we can restore if it later
Function selection = (Function) sourceFunctionsCBModel.getSelectedItem();
// Remove all functions
sourceFunctionsCBModel.removeAllElements();
// Reload the functions
FunctionComparisonModel model = ((FunctionComparisonProvider) provider).getModel();
Iterator<FunctionComparison> compIter = model.getComparisons().iterator();
while (compIter.hasNext()) {
FunctionComparison fc = compIter.next();
sourceFunctionsCBModel.addElement(fc.getSource());
} }
restoreSelection(sourceFunctionsCB, selection); void comparePreviousFunction() {
Side activeSide = getActiveSide();
JComboBox<Function> combo = comboBoxes.get(activeSide);
int index = combo.getSelectedIndex();
combo.setSelectedIndex(index - 1);
} }
/** boolean canRemoveActiveFunction() {
* Clears out and reloads the target function list with functions Side activeSide = getActiveSide();
* associated with the given source function. Any selection currently made return model.getActiveFunction(activeSide) != null;
* on the list will be reestablished.
*
* @param source the selected source function
*/
private void reloadTargetList(Function source) {
// Save off any selected item so we can restore if it later
Function selection = (Function) targetFunctionsCBModel.getSelectedItem();
// 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<FunctionComparison> compIter = model.getComparisons().iterator();
while (compIter.hasNext()) {
FunctionComparison fc = compIter.next();
if (fc.getSource().equals(source)) {
Set<Function> targets = fc.getTargets();
targetFunctionsCBModel.addAll(targets);
}
} }
restoreSelection(targetFunctionsCB, selection); void removeActiveFunction() {
Side activeSide = getActiveSide();
// we don't want the initial target to match the source as that is a pointless comparison model.removeFunction(model.getActiveFunction(activeSide));
fixupTargetSelectionToNotMatchSource(source);
} }
private void fixupTargetSelectionToNotMatchSource(Function source) { private void buildComboPanels() {
if (targetFunctionsCB.getSelectedItem() != source) { JPanel choicePanel = new JPanel(new GridLayout(1, 2));
return; createComboBoxes();
} choicePanel.add(createPanel(LEFT));
for (int i = 0; i < targetFunctionsCB.getItemCount(); i++) { choicePanel.add(createPanel(RIGHT));
if (targetFunctionsCB.getItemAt(i) != source) { add(choicePanel, BorderLayout.NORTH);
targetFunctionsCB.setSelectedIndex(i);
return;
}
}
} }
/** private void intializeComboBox(Side side) {
* Sets the text on the current tab to match whatever is displayed in the JComboBox<Function> comboBox = comboBoxes.get(side);
* comparison panels comboBox.removeItemListener(comboListeners.get(side));
*/
private void updateTabText() { DefaultComboBoxModel<Function> comboModel =
String tabText = getDescription(); (DefaultComboBoxModel<Function>) comboBox.getModel();
provider.setTabText(tabText); comboModel.removeAllElements();
provider.setTitle(tabText); comboModel.addAll(model.getFunctions(side));
Function activeFunction = model.getActiveFunction(side);
if (activeFunction != null) {
comboBox.setSelectedItem(activeFunction);
} }
/** comboBox.addItemListener(comboListeners.get(side));
* 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<Function> cb, Function selection) {
ComboBoxModel<Function> 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) { private void createComboBoxes() {
cb.setSelectedIndex(0); createComboBoxListeners();
} JComboBox<Function> leftComboBox = buildComboBox(LEFT);
JComboBox<Function> rightComboBox = buildComboBox(RIGHT);
comboBoxes = new Duo<>(leftComboBox, rightComboBox);
} }
/** private void createComboBoxListeners() {
* Creates the panel displaying the source combo box ItemListener leftListener = e -> comboChanged(e, LEFT);
* <p> ItemListener rightListener = e -> comboChanged(e, RIGHT);
* Note: The custom renderer is used so the name of the program associated comboListeners = new Duo<>(leftListener, rightListener);
* 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<br> private void comboChanged(ItemEvent e, Side side) {
* eg: "init (notepad)"<br> if (e.getStateChange() == ItemEvent.DESELECTED) {
* return; // only care when a function is selected
* @return the source panel }
*/ model.setActiveFunction(side, (Function) e.getItem());
private JPanel createSourcePanel() { }
private JComboBox<Function> buildComboBox(Side side) {
DefaultComboBoxModel<Function> leftModel = new DefaultComboBoxModel<>();
JComboBox<Function> 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()); JPanel panel = new JPanel(new BorderLayout());
sourceFunctionsCB = new JComboBox<>(); JComboBox<Function> comboBox = comboBoxes.get(side);
sourceFunctionsCBModel = new DefaultComboBoxModel<>(); panel.add(comboBox, BorderLayout.CENTER);
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; return panel;
} }
/** private void updateComboBoxSelectIfNeeded(Side side, Function function) {
* Creates the panel for the target functions selection components JComboBox<Function> combo = comboBoxes.get(side);
* <p> if (combo.getSelectedItem() == function) {
* 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<br>
* eg: "init (notepad)"<br>
*
* @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; return;
} }
combo.removeItemListener(comboListeners.get(side));
Function selected = (Function) targetFunctionsCBModel.getSelectedItem(); combo.setSelectedItem(function);
loadFunctions((Function) sourceFunctionsCBModel.getSelectedItem(), selected); combo.addItemListener(comboListeners.get(side));
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;
} }
/** /**

View file

@ -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);
}
}

View file

@ -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
* <p>
* 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<Function> functions = getSelectedFunctions(context);
comparisonService.compareFunctions(functions);
}
@Override
public boolean isEnabledForContext(ActionContext actionContext) {
Set<Function> 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<Function> 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);
}
}

View file

@ -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.
* <p>
* 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<Function> getSelectedFunctions(ActionContext actionContext) {
Set<Function> 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<FunctionRowObject> 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?).
* <p>
* 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;
}
}

View file

@ -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<Function> getSelectedFunctions(ActionContext actionContext) {
ListingActionContext listingContext = (ListingActionContext) actionContext;
ProgramSelection selection = listingContext.getSelection();
Program program = listingContext.getProgram();
FunctionManager functionManager = program.getFunctionManager();
Set<Function> functions = new HashSet<>();
FunctionIterator functionIter = functionManager.getFunctions(selection, true);
for (Function selectedFunction : functionIter) {
functions.add(selectedFunction);
}
return functions;
}
}

View file

@ -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:
* <ul>
* <li>focus is gained on either the left or right panels</li>
* <li>the function displayed in a comparison panel changes</li>
* </ul>
* Note that the GoTo will only operate on the comparison panel that
* <b>has focus</b>. 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 <b>On</b> 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<Function> 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<Function> sourceCombo = comparisonPanel.getSourceComponent();
JComboBox<Function> 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<CodeComparisonPanel> 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);
}
}
}
}

View file

@ -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<Function> 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<Function> focusedComponent = panel.getFocusedComponent();
focusedComponent.setSelectedIndex(focusedComponent.getSelectedIndex() + 1);
}
}

View file

@ -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<FunctionRowObject> diag = new TableSelectionDialog<>(
"Select Functions: " + currentProgram.getName(), model, true);
tool.showDialog(diag);
List<FunctionRowObject> rows = diag.getSelectionItems();
if (CollectionUtils.isBlank(rows)) {
return; // the table chooser can return null if the operation was cancelled
}
Set<Function> functions =
rows.stream().map(row -> row.getFunction()).collect(Collectors.toSet());
comparisonService.compareFunctions(new HashSet<>(functions), provider);
}
}

View file

@ -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<Function> focusedComponent = panel.getFocusedComponent();
return focusedComponent.getSelectedIndex() > 0;
}
@Override
public void actionPerformed(ActionContext context) {
ComponentProvider provider = context.getComponentProvider();
MultiFunctionComparisonPanel panel = (MultiFunctionComparisonPanel) provider.getComponent();
JComboBox<Function> focusedComponent = panel.getFocusedComponent();
focusedComponent.setSelectedIndex(focusedComponent.getSelectedIndex() - 1);
}
}

View file

@ -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<Function> focusedComponent = panel.getFocusedComponent();
return focusedComponent.getSelectedIndex() != -1;
}
@Override
public void actionPerformed(ActionContext context) {
MultiFunctionComparisonProvider provider =
(MultiFunctionComparisonProvider) context.getComponentProvider();
JComboBox<Function> focusedComponent =
((MultiFunctionComparisonPanel) provider.getComponent()).getFocusedComponent();
Function selectedFunction = (Function) focusedComponent.getSelectedItem();
provider.removeFunctions(new HashSet<>(Arrays.asList(selectedFunction)));
provider.contextChanged();
}
}

View file

@ -41,6 +41,8 @@ public class FunctionTableModel extends AddressBasedTableModel<FunctionRowObject
public FunctionTableModel(PluginTool tool, Program program) { public FunctionTableModel(PluginTool tool, Program program) {
super("Functions", tool, program, null); super("Functions", tool, program, null);
functionMgr = program != null ? program.getFunctionManager() : null;
} }
@Override @Override
@ -82,12 +84,7 @@ public class FunctionTableModel extends AddressBasedTableModel<FunctionRowObject
public void reload(Program newProgram) { public void reload(Program newProgram) {
this.setProgram(newProgram); this.setProgram(newProgram);
if (newProgram != null) { functionMgr = program != null ? program.getFunctionManager() : null;
functionMgr = newProgram.getFunctionManager();
}
else {
functionMgr = null;
}
reload(); reload();
} }

View file

@ -18,12 +18,10 @@ package ghidra.app.plugin.core.functionwindow;
import static ghidra.framework.model.DomainObjectEvent.*; import static ghidra.framework.model.DomainObjectEvent.*;
import static ghidra.program.util.ProgramEvent.*; import static ghidra.program.util.ProgramEvent.*;
import docking.action.DockingAction;
import ghidra.app.CorePluginPackage; import ghidra.app.CorePluginPackage;
import ghidra.app.events.ProgramClosedPluginEvent; import ghidra.app.events.ProgramClosedPluginEvent;
import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin; import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsFromFunctionTableAction;
import ghidra.app.services.FunctionComparisonService; import ghidra.app.services.FunctionComparisonService;
import ghidra.framework.model.DomainObjectListener; import ghidra.framework.model.DomainObjectListener;
import ghidra.framework.model.DomainObjectListenerBuilder; import ghidra.framework.model.DomainObjectListenerBuilder;
@ -35,8 +33,6 @@ import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.Symbol; import ghidra.program.model.symbol.Symbol;
import ghidra.program.util.ProgramChangeRecord; import ghidra.program.util.ProgramChangeRecord;
import ghidra.util.table.SelectionNavigationAction;
import ghidra.util.table.actions.MakeProgramSelectionAction;
import ghidra.util.task.SwingUpdateManager; import ghidra.util.task.SwingUpdateManager;
//@formatter:off //@formatter:off
@ -51,8 +47,6 @@ import ghidra.util.task.SwingUpdateManager;
//@formatter:on //@formatter:on
public class FunctionWindowPlugin extends ProgramPlugin { public class FunctionWindowPlugin extends ProgramPlugin {
private DockingAction selectAction;
private DockingAction compareFunctionsAction;
private FunctionWindowProvider provider; private FunctionWindowProvider provider;
private SwingUpdateManager swingMgr; private SwingUpdateManager swingMgr;
private DomainObjectListener domainObjectListener; private DomainObjectListener domainObjectListener;
@ -68,7 +62,6 @@ public class FunctionWindowPlugin extends ProgramPlugin {
super.init(); super.init();
provider = new FunctionWindowProvider(this); provider = new FunctionWindowProvider(this);
domainObjectListener = createDomainObjectListener(); domainObjectListener = createDomainObjectListener();
createActions();
/** /**
* Kicks the tool actions to set the proper enablement when selection changes * Kicks the tool actions to set the proper enablement when selection changes
@ -94,17 +87,14 @@ public class FunctionWindowPlugin extends ProgramPlugin {
@Override @Override
public void serviceAdded(Class<?> interfaceClass, Object service) { public void serviceAdded(Class<?> interfaceClass, Object service) {
if (interfaceClass == FunctionComparisonService.class) { if (interfaceClass == FunctionComparisonService.class) {
compareFunctionsAction = new CompareFunctionsFromFunctionTableAction(tool, getName()); provider.createCompareAction();
tool.addLocalAction(provider, compareFunctionsAction);
tool.contextChanged(provider);
} }
} }
@Override @Override
public void serviceRemoved(Class<?> interfaceClass, Object service) { public void serviceRemoved(Class<?> interfaceClass, Object service) {
if (interfaceClass == FunctionComparisonService.class) { if (interfaceClass == FunctionComparisonService.class) {
tool.removeLocalAction(provider, compareFunctionsAction); provider.removeCompareAction();
compareFunctionsAction = null;
} }
} }
@ -175,17 +165,6 @@ public class FunctionWindowPlugin extends ProgramPlugin {
return currentProgram; 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() { void showFunctions() {
provider.showFunctions(); provider.showFunctions();
} }

View file

@ -18,20 +18,24 @@ package ghidra.app.plugin.core.functionwindow;
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.util.HashSet; import java.util.*;
import java.util.Set;
import javax.swing.*; import javax.swing.*;
import javax.swing.table.*; import javax.swing.table.*;
import docking.ActionContext; import docking.ActionContext;
import docking.DefaultActionContext; import docking.DefaultActionContext;
import docking.action.DockingAction;
import docking.action.builder.ActionBuilder;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.app.context.FunctionSupplierContext;
import ghidra.app.services.FunctionComparisonService;
import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.table.*; import ghidra.util.table.*;
import ghidra.util.table.actions.MakeProgramSelectionAction;
/** /**
* Provider that displays all functions in the selected program * Provider that displays all functions in the selected program
@ -39,6 +43,7 @@ import ghidra.util.table.*;
public class FunctionWindowProvider extends ComponentProviderAdapter { public class FunctionWindowProvider extends ComponentProviderAdapter {
public static final Icon ICON = new GIcon("icon.plugin.functionwindow.provider"); 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 FunctionWindowPlugin plugin;
private GhidraTable functionTable; private GhidraTable functionTable;
@ -48,6 +53,8 @@ public class FunctionWindowProvider extends ComponentProviderAdapter {
private GhidraTableFilterPanel<FunctionRowObject> tableFilterPanel; private GhidraTableFilterPanel<FunctionRowObject> tableFilterPanel;
private GhidraThreadedTablePanel<FunctionRowObject> threadedTablePanel; private GhidraThreadedTablePanel<FunctionRowObject> threadedTablePanel;
private DockingAction compareAction;
/** /**
* Constructor * Constructor
* *
@ -62,6 +69,41 @@ public class FunctionWindowProvider extends ComponentProviderAdapter {
tool = plugin.getTool(); tool = plugin.getTool();
mainPanel = createWorkPanel(); mainPanel = createWorkPanel();
tool.addComponentProvider(this, false); 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<Function> functions = new HashSet<>();
int[] selectedRows = functionTable.getSelectedRows();
List<FunctionRowObject> 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 @Override
@ -76,7 +118,7 @@ public class FunctionWindowProvider extends ComponentProviderAdapter {
@Override @Override
public ActionContext getActionContext(MouseEvent event) { public ActionContext getActionContext(MouseEvent event) {
return new DefaultActionContext(this, functionTable); return new FunctionWindowActionContext();
} }
@Override @Override
@ -231,4 +273,32 @@ public class FunctionWindowProvider extends ComponentProviderAdapter {
public boolean isTransient() { public boolean isTransient() {
return false; 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<Function> getFunctions() {
Set<Function> functions = new HashSet<>();
int[] selectedRows = functionTable.getSelectedRows();
if (selectedRows.length == 0) {
return Collections.emptySet();
}
List<FunctionRowObject> functionRowObjects = functionModel.getRowObjects(selectedRows);
for (FunctionRowObject functionRowObject : functionRowObjects) {
Function rowFunction = functionRowObject.getFunction();
functions.add(rowFunction);
}
return functions;
}
}
} }

View file

@ -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> FUNCTION_COMPARATOR = new FunctionComparator();
private List<FunctionComparisonModelListener> listeners = new ArrayList<>();
protected Duo<Function> 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<Function> {
@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);
}
}
}

View file

@ -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<Function> functions = new HashSet<>();
public DefaultFunctionComparisonModel(Collection<Function> functions) {
this.functions.addAll(functions);
List<Function> 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<Function> getFunctions(Side side) {
return getOrderedFunctions();
}
@Override
public void removeFunction(Function function) {
removeFunctions(Set.of(function));
}
@Override
public void removeFunctions(Collection<Function> functionsToRemove) {
int beforeSize = functions.size();
functions.removeAll(functionsToRemove);
int afterSize = functions.size();
if (beforeSize != afterSize) {
fixupActiveFunctions();
fireModelDataChanged();
}
}
@Override
public void removeFunctions(Program program) {
Set<Function> 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<Function> 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<Function> getOrderedFunctions() {
List<Function> 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);
}
}

View file

@ -18,24 +18,14 @@
*/ */
package ghidra.app.services; package ghidra.app.services;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List; 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.FunctionComparisonModelListener;
import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider; import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.Msg; import ghidra.util.datastruct.Duo.Side;
import ghidra.util.task.TaskLauncher;
/** /**
* A collection of {@link FunctionComparison function comparison} * 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 * Note: Subscribers may register to be informed of changes to this model via the
* {@link FunctionComparisonModelListener comparison model listener} interface. * {@link FunctionComparisonModelListener comparison model listener} interface.
*/ */
public class FunctionComparisonModel { public interface FunctionComparisonModel {
private List<FunctionComparison> comparisons = new ArrayList<>();
private List<FunctionComparisonModelListener> listeners = new ArrayList<>();
/** /**
* Adds the given subscriber to the list of those to be notified of model * Adds the given listener to the list of those to be notified of model changes.
* changes
* *
* @param listener the model change subscriber * @param listener the listener to add
*/ */
public void addFunctionComparisonModelListener(FunctionComparisonModelListener listener) { public void addFunctionComparisonModelListener(FunctionComparisonModelListener listener);
listeners.add(listener);
}
/** /**
* Returns a list of all comparisons in the model, in sorted order by * Removes the given listener from the list of those to be notified of model changes.
* source function name
* *
* @return a list of all comparisons in the model * @param listener the listener to remove
*/ */
public List<FunctionComparison> getComparisons() { public void removeFunctionComparisonModelListener(FunctionComparisonModelListener listener);
List<FunctionComparison> toReturn = new ArrayList<>();
toReturn.addAll(comparisons);
Collections.sort(toReturn);
return toReturn;
}
/** /**
* Replaces the current model with the comparisons provided * Sets the function for the given side. The function must be one of the functions from that
* * side's set of functions
* @param comparisons the new comparison model * @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<FunctionComparison> comparisons) { public boolean setActiveFunction(Side side, Function function);
this.comparisons = comparisons;
}
/** /**
* Adds a single comparison to the model * Returns the active (selected) function for the given side.
* * @param side the side to get the active function for
* @param comparison the comparison to add * @return the active function for the given side
*/ */
public void addComparison(FunctionComparison comparison) { public Function getActiveFunction(Side side);
comparisons.add(comparison);
}
/** /**
* Returns a list of all targets in the model (across all comparisons) for * Returns the list of all functions on the given side that could be made active.
* a given source function * @param side the side to get functions for
* * @return the list of all functions on the given side that could be made active
* @param source the source function
* @return list of associated target functions
*/ */
public Set<Function> getTargets(Function source) { public List<Function> getFunctions(Side side);
Set<Function> targets = new HashSet<>();
for (FunctionComparison fc : comparisons) {
if (fc.getSource().equals(source)) {
targets.addAll(fc.getTargets());
}
}
return targets;
}
/** /**
* Updates the model with a set of functions to compare. This will add the * Removes the given function from both sides of the comparison.
* functions to any existing {@link FunctionComparison comparisons} in the
* model and create new comparisons for functions not represented.
* <p>
* 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<Function> 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.
* <p>
* 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<Function> sourceFunctions,
Set<Function> 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
* *
* @param function the function to remove * @param function the function to remove
*/ */
public void removeFunction(Function function) { public void removeFunction(Function function);
doRemoveFunction(function);
fireModelChanged();
}
/** /**
* 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 * @param functions the functions to remove
*/ */
public void removeFunctions(Collection<Function> functions) { public void removeFunctions(Collection<Function> functions);
for (Function function : functions) {
doRemoveFunction(function);
}
fireModelChanged();
}
private void doRemoveFunction(Function function) {
List<FunctionComparison> comparisonsToRemove = new ArrayList<>();
Iterator<FunctionComparison> 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);
}
/** /**
* Removes all functions in the model that come from the given * Removes all functions from the given program from both sides of the comparison
* program * @param program that program whose functions should be removed from this model
*
* @param program the program to remove functions from
*/ */
public void removeFunctions(Program program) { public void removeFunctions(Program program);
Set<Function> allFunctions = getSourceFunctions();
allFunctions.addAll(getTargetFunctions());
Set<Function> functionsToRemove = allFunctions.stream()
.filter(f -> f.getProgram().equals(program))
.collect(Collectors.toSet());
removeFunctions(functionsToRemove);
}
/** /**
* Returns all source functions in the model * Returns true if the model has no function to compare.
* * @return true if the model has no functions to compare
* @return a set of all source functions
*/ */
public Set<Function> getSourceFunctions() { public boolean isEmpty();
Set<Function> items = new HashSet<>();
for (FunctionComparison fc : comparisons) {
items.add(fc.getSource());
}
return items;
}
/**
* Returns all target functions in the model
*
* @return a set of all target functions
*/
public Set<Function> getTargetFunctions() {
Set<Function> items = new HashSet<>();
Iterator<FunctionComparison> 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<Function> getTargetFunctions(Function source) {
Set<Function> items = new HashSet<>();
Iterator<FunctionComparison> 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:
* <li>comparison 1:</li>
* <ul>
* <li> source: f1</li>
* <li> targets: f2, f3</li>
* </ul>
* <li>comparison 2:</li>
* <ul>
* <li> source: f2</li>
* <li> targets: f1, f3</li>
* </ul>
* <li>comparison 3:</li>
* <ul>
* <li> source: f3</li>
* <li> targets: f1, f2</li>
* </ul>
*
* 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.
* <p>
* 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<Function> 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<Function> 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<Function> 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));
}
}

View file

@ -15,125 +15,77 @@
*/ */
package ghidra.app.services; 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.FunctionComparisonPlugin;
import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider; import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider;
import ghidra.framework.plugintool.ServiceInfo; import ghidra.framework.plugintool.ServiceInfo;
import ghidra.program.model.listing.Function; 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 * side-by-side in a {@link FunctionComparisonProvider}. Each side in the
* display will allow the user to select one or more functions * display will allow the user to select one or more functions
* *
* <p>Concurrent usage: All work performed by this service will be done on the Swing thread. * <p>Concurrent usage: All work performed by this service will be done asynchronously on the
* Further, all calls that do not return a value will be run immediately if the caller is on * Swing thread.
* 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.
*/ */
@ServiceInfo(defaultProvider = FunctionComparisonPlugin.class) @ServiceInfo(defaultProvider = FunctionComparisonPlugin.class)
public interface FunctionComparisonService { public interface FunctionComparisonService {
/** /**
* Creates a comparison provider that allows comparisons between a functions. * Creates a function comparison window where each side can display any of the given 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.
* <p>
* 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.
* <p>
* 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.
*
* @param functions the functions to compare * @param functions the functions to compare
* @return the new comparison provider
*/ */
public FunctionComparisonProvider compareFunctions(Set<Function> functions); public void createComparison(Collection<Function> functions);
/** /**
* Creates a comparison between two sets of functions, where all the functions in source list can * Creates a function comparison window for the two given functions. Each side can select
* be compared against all functions in the destination list. * either function, but initially the left function will be shown in the left panel and the
* <p> * right function will be shown in the right panel.
* Note that this method will always create a new provider. * @param left the function to initially show in the left panel
* * @param right the function to initially show in the right panel
* @param sourceFunctions
* @param destinationFunctions
* @return the new comparison provider
*/ */
public FunctionComparisonProvider compareFunctions(Set<Function> sourceFunctions, public void createComparison(Function left, Function right);
Set<Function> destinationFunctions);
/** /**
* Creates a comparison between two functions, where the source function * Adds the given function to each side the last created comparison window or creates
* will be shown on the left side of the comparison dialog and the target * a new comparison if none exists. The right panel will be changed to show the new function.
* on the right. * Note that this method will not add to any provider created via the
* <p> * {@link #createCustomComparison(FunctionComparisonModel, Callback)}. Those providers
* Note that this will always create a new provider; if you want to add * are private to the client that created them. They take in a model, so if the client wants
* functions to an existing comparison, use * to add to those providers, it must retain a handle to the model and add functions directly
* {@link #compareFunctions(Function, Function, FunctionComparisonProvider) this} * to the model.
* variant that takes a provider. * @param function the function to be added to the last function comparison window
*
* @param source a function in the comparison
* @param target a function in the comparison
* @return the new comparison provider
*/ */
public FunctionComparisonProvider compareFunctions(Function source, Function target); public void addToComparison(Function function);
/** /**
* Creates a comparison between a set of functions, adding them to the * Adds the given functions to each side the last created comparison window or creates
* given comparison provider. Each function in the given set will be added * a new comparison if none exists. The right panel will be change to show a random function
* to both sides of the comparison, allowing users to compare any functions * from the new functions. Note that this method will not add to any comparison windows created
* in the existing provider with the new set. * with a custom comparison model.
* * @param functions the functions to be added to the last function comparison window
* @see #compareFunctions(Set)
* @param functions the functions to compare
* @param provider the provider to add the comparisons to
*/ */
public void compareFunctions(Set<Function> functions, public void addToComparison(Collection<Function> functions);
FunctionComparisonProvider provider);
/** /**
* Creates a comparison between two functions and adds it to a given * Creates a custom function comparison window. The default model shows all functions on both
* comparison provider. The existing comparisons in the provider will not * sides. This method allows the client to provide a custom comparison model which can have
* be affected, unless the provider already contains a comparison with * more control over what functions can be selected on each side. One such custom model
* the same source function; in this case the given target will be added * is the {@link MatchedFunctionComparisonModel} which gives a unique set of functions on the
* to that comparisons' list of targets. * right side, depending on what is selected on the left side.
* <P>
* 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.
* *
* @see #compareFunctions(Function, Function) * @param model the custom function comparison model
* @param source a function in the comparison * @param closeListener an optional callback if the client wants to be notified when the
* @param target a function in the comparison * associated function comparison windows is closed.
* @param provider the provider to add the comparison to
*/ */
public void compareFunctions(Function source, Function target, public void createCustomComparison(FunctionComparisonModel model,
FunctionComparisonProvider provider); Callback closeListener);
/**
* 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);
} }

View file

@ -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<Function, Set<Function>> 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<Function> 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<Function> it = sourceToTargetsMap.keySet().iterator();
while (it.hasNext()) {
Function source = it.next();
Set<Function> 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<Function> functionsToRemove = findFunctions(program);
removeFunctions(functionsToRemove);
}
private Set<Function> findFunctions(Program program) {
Set<Function> functions = new HashSet<>();
for (Entry<Function, Set<Function>> entry : sourceToTargetsMap.entrySet()) {
Function source = entry.getKey();
Set<Function> 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<Function> 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<Function> getTargetFunctions() {
List<Function> targets = new ArrayList<>();
Function source = getActiveFunction(LEFT);
if (source != null) {
targets.addAll(sourceToTargetsMap.get(source));
}
Collections.sort(targets, FUNCTION_COMPARATOR);
return targets;
}
public List<Function> getSourceFunctions() {
List<Function> 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<Function> 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);
}
}

View file

@ -101,8 +101,9 @@ public class ProgramOpener {
} }
catch (IOException e) { catch (IOException e) {
Msg.showError(this, null, "Program Open Failed", 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); return openProgram(locator, locator.getDomainFile(), monitor);
} }

View file

@ -36,6 +36,7 @@ import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.HTMLUtilities; import ghidra.util.HTMLUtilities;
import ghidra.util.HelpLocation;
import ghidra.util.classfinder.ClassSearcher; import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.classfinder.ExtensionPoint; import ghidra.util.classfinder.ExtensionPoint;
import ghidra.util.datastruct.Duo; import ghidra.util.datastruct.Duo;
@ -385,6 +386,8 @@ public abstract class CodeComparisonPanel extends JPanel
super(name + " Toggle Orientation", "FunctionComparison"); super(name + " Toggle Orientation", "FunctionComparison");
setDescription( setDescription(
"<html>Toggle the layout to be either side by side or one above the other"); "<html>Toggle the layout to be either side by side or one above the other");
setHelpLocation(
new HelpLocation("FunctionComparison", "Dual_" + name + "_Toggle_Orientation"));
setEnabled(true); setEnabled(true);
MenuData menuData = MenuData menuData =
new MenuData(new String[] { "Show " + name + " Side-by-Side" }, "Orientation"); new MenuData(new String[] { "Show " + name + " Side-by-Side" }, "Orientation");

View file

@ -19,9 +19,10 @@ import static ghidra.util.datastruct.Duo.Side.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.awt.Window; import java.awt.Window;
import java.util.Date; import java.util.*;
import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.JComboBox;
import javax.swing.JPanel; import javax.swing.JPanel;
import org.junit.*; import org.junit.*;
@ -32,8 +33,8 @@ import docking.widgets.dialogs.TableSelectionDialog;
import docking.widgets.table.GFilterTable; import docking.widgets.table.GFilterTable;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.plugin.core.function.FunctionPlugin; import ghidra.app.plugin.core.function.FunctionPlugin;
import ghidra.app.plugin.core.functionwindow.FunctionRowObject; import ghidra.app.services.FunctionComparisonModel;
import ghidra.app.plugin.core.functionwindow.FunctionTableModel; import ghidra.app.services.MatchedFunctionComparisonModel;
import ghidra.program.database.ProgramBuilder; import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.data.ByteDataType; import ghidra.program.model.data.ByteDataType;
@ -42,12 +43,13 @@ import ghidra.program.model.listing.*;
import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramLocation;
import ghidra.test.AbstractGhidraHeadedIntegrationTest; import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv; import ghidra.test.TestEnv;
import ghidra.util.datastruct.Duo.Side;
/** /**
* Tests for the {@link FunctionComparisonPlugin function comparison plugin} * Tests for the {@link FunctionComparisonPlugin function comparison plugin}
* that involve the GUI * that involve the GUI
*/ */
public class CompareFunctionsSlowTest extends AbstractGhidraHeadedIntegrationTest { public class CompareFunctionsProviderTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env; private TestEnv env;
private Program program1; private Program program1;
@ -79,75 +81,63 @@ public class CompareFunctionsSlowTest extends AbstractGhidraHeadedIntegrationTes
@Test @Test
public void testRemoveLastItem() throws Exception { public void testRemoveLastItem() throws Exception {
Set<Function> functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo); provider = compareFunctions(Set.of(foo));
provider = compareFunctions(functions); assertTrue(provider.isVisible());
runSwing(() -> plugin.removeFunction(foo, provider)); plugin.removeFunction(foo);
waitForSwing();
assertFalse(provider.isVisible()); assertFalse(provider.isVisible());
} }
@Test @Test
public void testCloseProgram() throws Exception { public void testCloseProgram() throws Exception {
Set<Function> functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); Set<Function> functions = Set.of(foo, bar);
provider = compareFunctions(functions); provider = compareFunctions(functions);
CompareFunctionsTestUtility.checkSourceFunctions(provider, foo, bar); checkFunctions(LEFT, foo, foo, bar);
CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo, bar); checkFunctions(RIGHT, bar, foo, bar);
CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, foo, bar);
runSwing(() -> plugin.programClosed(program1)); runSwing(() -> plugin.programClosed(program1));
waitForSwing();
CompareFunctionsTestUtility.checkSourceFunctions(provider, bar); checkFunctions(LEFT, bar, bar);
CompareFunctionsTestUtility.checkTargetFunctions(provider, bar, bar); checkFunctions(RIGHT, bar, bar);
runSwing(() -> plugin.programClosed(program2)); runSwing(() -> plugin.programClosed(program2));
waitForSwing();
CompareFunctionsTestUtility.checkSourceFunctions(provider); assertFalse(provider.isVisible());
assertFalse(provider.isInTool());
} }
@Test @Test
public void testNextPreviousAction() { public void testNextPreviousAction() {
Set<Function> functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); Set<Function> functions = Set.of(foo, bar);
provider = compareFunctions(functions); 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 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); ActionContext context = provider.getActionContext(null);
assertTrue(nextAction.isEnabledForContext(context)); assertEnabled(nextAction, context);
assertFalse(prevAction.isEnabledForContext(context)); assertNotEnabled(previousAction, context);
performAction(nextAction); performAction(nextAction);
context = provider.getActionContext(null); context = provider.getActionContext(null);
assertFalse(nextAction.isEnabledForContext(context)); assertNotEnabled(nextAction, context);
assertTrue(prevAction.isEnabledForContext(context)); assertEnabled(previousAction, context);
} }
@Test @Test
public void testNextPreviousActionSwitchPanelFocus() { public void testNextPreviousActionSwitchPanelFocus() {
Set<Function> functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); Set<Function> functions = Set.of(foo, bar);
provider = compareFunctions(functions); 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 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); ActionContext context = provider.getActionContext(null);
assertTrue(nextAction.isEnabledForContext(context)); assertEnabled(nextAction, context);
assertFalse(prevAction.isEnabledForContext(context)); assertNotEnabled(previousAction, context);
performAction(nextAction);
context = provider.getActionContext(null);
assertFalse(nextAction.isEnabledForContext(context));
assertTrue(prevAction.isEnabledForContext(context));
JPanel rightPanel = JPanel rightPanel =
provider.getComponent().getDualListingPanel().getListingPanel(RIGHT).getFieldPanel(); provider.getComponent().getDualListingPanel().getListingPanel(RIGHT).getFieldPanel();
@ -155,20 +145,17 @@ public class CompareFunctionsSlowTest extends AbstractGhidraHeadedIntegrationTes
waitForSwing(); waitForSwing();
provider.getComponent().updateActionEnablement(); provider.getComponent().updateActionEnablement();
// right panel has focus, so nextAction should be disabled and previous should be enabled
context = provider.getActionContext(null); context = provider.getActionContext(null);
assertTrue(nextAction.isEnabledForContext(context)); assertNotEnabled(nextAction, context);
assertFalse(prevAction.isEnabledForContext(context)); assertEnabled(previousAction, context);
} }
@Test @Test
public void testOpenFunctionTableActionForAdd() { public void testOpenFunctionTableActionForAdd() {
Set<Function> functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); Set<Function> functions = Set.of(foo, bar);
provider = compareFunctions(functions); 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"); DockingActionIf openTableAction = getAction(plugin, "Add Functions To Comparison");
performAction(openTableAction, provider, false); performAction(openTableAction, provider, false);
@ -179,33 +166,28 @@ public class CompareFunctionsSlowTest extends AbstractGhidraHeadedIntegrationTes
@Test @Test
public void testAddFunctionToExistingCompare() { public void testAddFunctionToExistingCompare() {
Set<Function> functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo); Set<Function> functions = Set.of(foo);
provider = compareFunctions(functions); provider = compareFunctions(functions);
// Must do this or there will be no "active" provider in the actions initiated below assertEquals(provider.getModel().getFunctions(LEFT).size(), 1);
clickComponentProvider(provider); assertTrue(provider.getModel().getFunctions(LEFT).contains(foo));
assertEquals(provider.getModel().getSourceFunctions().size(), 1);
assertTrue(provider.getModel().getSourceFunctions().contains(foo));
DockingActionIf openTableAction = getAction(plugin, "Add Functions To Comparison"); DockingActionIf openTableAction = getAction(plugin, "Add Functions To Comparison");
performAction(openTableAction, provider, false); performAction(openTableAction, provider, false);
@SuppressWarnings("unchecked") TableSelectionDialog<?> chooser =
TableSelectionDialog<FunctionTableModel> chooser =
waitForDialogComponent(TableSelectionDialog.class); waitForDialogComponent(TableSelectionDialog.class);
@SuppressWarnings("unchecked")
GFilterTable<FunctionRowObject> table = GFilterTable<?> table = (GFilterTable<?>) getInstanceField("gFilterTable", chooser);
(GFilterTable<FunctionRowObject>) getInstanceField("gFilterTable", chooser);
waitForCondition(() -> table.getModel().getRowCount() == 2); waitForCondition(() -> table.getModel().getRowCount() == 2);
clickTableCell(table.getTable(), 1, 0, 1); clickTableCell(table.getTable(), 1, 0, 1);
pressButtonByText(chooser, "OK"); pressButtonByText(chooser, "OK");
waitForSwing(); waitForSwing();
assertEquals(provider.getModel().getSourceFunctions().size(), 2); assertEquals(provider.getModel().getFunctions(LEFT).size(), 2);
assertTrue(provider.getModel().getSourceFunctions().contains(foo)); assertTrue(provider.getModel().getFunctions(LEFT).contains(foo));
assertTrue(provider.getModel().getSourceFunctions().contains(bat)); assertTrue(provider.getModel().getFunctions(LEFT).contains(bat));
} }
/** /**
@ -215,29 +197,99 @@ public class CompareFunctionsSlowTest extends AbstractGhidraHeadedIntegrationTes
*/ */
@Test @Test
public void testDeleteFunctionFromListing() { public void testDeleteFunctionFromListing() {
Set<Function> functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo, bar); Set<Function> functions = Set.of(foo, bar);
provider = compareFunctions(functions); provider = compareFunctions(functions);
assertEquals(provider.getModel().getSourceFunctions().size(), 2); assertEquals(provider.getModel().getFunctions(LEFT).size(), 2);
assertTrue(provider.getModel().getSourceFunctions().contains(foo)); assertTrue(provider.getModel().getFunctions(LEFT).contains(foo));
assertTrue(provider.getModel().getSourceFunctions().contains(bar)); assertTrue(provider.getModel().getFunctions(LEFT).contains(bar));
Address addr = program1.getAddressFactory().getAddress("10018cf"); Address address = program1.getAddressFactory().getAddress("10018cf");
ProgramLocation loc = new ProgramLocation(program1, addr); ProgramLocation loc = new ProgramLocation(program1, address);
cbPlugin.goTo(loc); cbPlugin.goTo(loc);
DockingActionIf deleteAction = getAction(functionPlugin, "Delete Function"); DockingActionIf deleteAction = getAction(functionPlugin, "Delete Function");
performAction(deleteAction, cbPlugin.getProvider().getActionContext(null), true); performAction(deleteAction, cbPlugin.getProvider().getActionContext(null), true);
waitForSwing(); waitForSwing();
assertEquals(provider.getModel().getSourceFunctions().size(), 1); assertEquals(provider.getModel().getFunctions(LEFT).size(), 1);
assertTrue(provider.getModel().getSourceFunctions().contains(bar)); 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<Function> 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<Function> functions) { private FunctionComparisonProvider compareFunctions(Set<Function> functions) {
provider = runSwing(() -> plugin.compareFunctions(functions)); runSwing(() -> plugin.createComparison(functions));
provider.setVisible(true);
waitForSwing(); waitForSwing();
return provider; return waitForComponentProvider(FunctionComparisonProvider.class);
} }
/** /**
@ -258,6 +310,17 @@ public class CompareFunctionsSlowTest extends AbstractGhidraHeadedIntegrationTes
return builder; return builder;
} }
private void checkFunctions(Side side, Function activeFunction, Function... functions) {
Set<Function> funcs = Set.of(functions);
FunctionComparisonModel model = provider.getModel();
assertEquals(activeFunction, model.getActiveFunction(side));
List<Function> fcs = model.getFunctions(side);
assertEquals(fcs.size(), funcs.size());
assertTrue(fcs.containsAll(funcs));
}
/** /**
* Builds a program with 1 function * Builds a program with 1 function
*/ */

View file

@ -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.
* <li>The API methods being tested: {@link FunctionComparisonService}</li>
* <li>The model being used for verification: {@link FunctionComparison}</li>
*/
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<Function> functions = CompareFunctionsTestUtility.getFunctionsAsSet();
provider = compare(functions);
assertNull(provider);
}
@Test
public void testSetOneFunction() throws Exception {
Set<Function> functions = CompareFunctionsTestUtility.getFunctionsAsSet(foo);
provider = compare(functions);
CompareFunctionsTestUtility.checkSourceFunctions(provider, foo);
CompareFunctionsTestUtility.checkTargetFunctions(provider, foo, foo);
}
@Test
public void testSetDuplicateFunctionDifferentProviders() throws Exception {
Set<Function> 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<Function> 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<Function> 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<Function> functions1 = CompareFunctionsTestUtility.getFunctionsAsSet(one, two);
Set<Function> 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<Function> functions1 = CompareFunctionsTestUtility.getFunctionsAsSet(foo, two);
Set<Function> 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<Function> functions1 = CompareFunctionsTestUtility.getFunctionsAsSet(foo, two);
Set<Function> functions2 = CompareFunctionsTestUtility.getFunctionsAsSet(bar, three);
Set<Function> 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<Function> 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<Function> 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<Function> 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<Function> 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<Function> 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<Function> 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<Function> 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<Function> 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<Function> sources = model.getSourceFunctions();
assertEquals(2, sources.size());
assertTrue(sources.contains(foo));
assertTrue(sources.contains(junk));
Set<Function> 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<Function> functions, FunctionComparisonProvider fp) {
runSwing(() -> plugin.compareFunctions(functions, fp));
}
private FunctionComparisonProvider compare(Set<Function> 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;
}
}

View file

@ -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<Function> funcs = new HashSet<>(Arrays.asList(functions));
Set<Function> 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<Function> targetsAsList = new HashSet<>(Arrays.asList(targets));
Set<Function> 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<Function> getFunctionsAsSet(Function... functions) {
Set<Function> 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<Function, Set<Function>> getFunctionsAsMap(Function source,
Function... targets) {
Set<Function> targetSet = getFunctionsAsSet(targets);
Map<Function, Set<Function>> map = new HashMap<>();
map.put(source, targetSet);
return map;
}
}

View file

@ -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.
* <ul>
* <li>The API methods being tested: {@link FunctionComparisonService}</li>
* <li>The model being used for verification: {@link DefaultFunctionComparisonModel}</li>
* </ul>
*/
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<Function> 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<Function> 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<Function> 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<Function> 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;
}
}
}

View file

@ -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.
* <li>The API methods being tested: {@link FunctionComparisonService}</li>
* <li>The model being used for verification: {@link DefaultFunctionComparisonModel}</li>
*/
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;
}
}
}

View file

@ -20,7 +20,6 @@ import static ghidra.util.datastruct.Duo.Side.*;
import docking.ActionContext; import docking.ActionContext;
import docking.action.MenuData; import docking.action.MenuData;
import ghidra.app.decompiler.ClangFuncNameToken; import ghidra.app.decompiler.ClangFuncNameToken;
import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider;
import ghidra.app.services.FunctionComparisonService; import ghidra.app.services.FunctionComparisonService;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
@ -106,10 +105,7 @@ public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedTokensAc
Msg.error(this, "Function Comparison Service not found!"); Msg.error(this, "Function Comparison Service not found!");
return; return;
} }
service.createComparison(leftFunction, rightFunction);
FunctionComparisonProvider comparisonProvider = service.createFunctionComparisonProvider();
comparisonProvider.removeAddFunctionsAction();
comparisonProvider.getModel().compareFunctions(leftFunction, rightFunction);
} }
private Function getFuncFromToken(ClangFuncNameToken funcToken, Program program) { private Function getFuncFromToken(ClangFuncNameToken funcToken, Program program) {

View file

@ -55,7 +55,7 @@ import resources.MultiIcon;
public class DecompilerCodeComparisonPanel public class DecompilerCodeComparisonPanel
extends CodeComparisonPanel { extends CodeComparisonPanel {
public static final String NAME = "Decompile Diff View"; public static final String NAME = "Decompiler View";
private boolean isStale = true; private boolean isStale = true;

View file

@ -368,7 +368,7 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter
statusPanel.add(statusLabel, BorderLayout.CENTER); statusPanel.add(statusLabel, BorderLayout.CENTER);
dualTablePanel.add(statusPanel, BorderLayout.SOUTH); dualTablePanel.add(statusPanel, BorderLayout.SOUTH);
functionComparisonPanel = new FunctionComparisonPanel(this, tool); functionComparisonPanel = new FunctionComparisonPanel(tool, getName());
addSpecificCodeComparisonActions(); addSpecificCodeComparisonActions();
functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.NAME); functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.NAME);
functionComparisonPanel.setTitlePrefixes("Source:", "Destination:"); functionComparisonPanel.setTitlePrefixes("Source:", "Destination:");

View file

@ -150,7 +150,7 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter
markupItemsTablePanel.add(tablePanel, BorderLayout.CENTER); markupItemsTablePanel.add(tablePanel, BorderLayout.CENTER);
markupItemsTablePanel.add(filterAreaPanel, BorderLayout.SOUTH); markupItemsTablePanel.add(filterAreaPanel, BorderLayout.SOUTH);
functionComparisonPanel = new FunctionComparisonPanel(this, tool); functionComparisonPanel = new FunctionComparisonPanel(tool, getName());
addSpecificCodeComparisonActions(); addSpecificCodeComparisonActions();
functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.NAME); functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.NAME);
functionComparisonPanel.setTitlePrefixes("Source:", "Destination:"); functionComparisonPanel.setTitlePrefixes("Source:", "Destination:");

View file

@ -15,166 +15,40 @@
*/ */
package ghidra.feature.vt.gui.provider.matchtable; 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.*;
import static ghidra.feature.vt.api.impl.VTEvent.MATCH_SET_ADDED; import static ghidra.feature.vt.gui.actions.TableSelectionTrackingState.*;
import static ghidra.feature.vt.gui.actions.TableSelectionTrackingState.MAINTAIN_SELECTED_ROW_INDEX; import static ghidra.feature.vt.gui.plugin.VTPlugin.*;
import static ghidra.feature.vt.gui.actions.TableSelectionTrackingState.MAINTAIN_SELECTED_ROW_VALUE; import static ghidra.feature.vt.gui.util.VTOptionDefines.*;
import static ghidra.feature.vt.gui.actions.TableSelectionTrackingState.NO_SELECTION_TRACKING; import static ghidra.framework.model.DomainObjectEvent.*;
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 java.awt.Adjustable; import java.awt.*;
import java.awt.BorderLayout; import java.awt.event.*;
import java.awt.Dimension; import java.util.*;
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.util.List; import java.util.List;
import java.util.Set;
import javax.swing.BorderFactory; import javax.swing.*;
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.event.ListSelectionEvent; import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener; import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableCellRenderer; import javax.swing.table.*;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import docking.ActionContext; import docking.*;
import docking.DockingWindowManager;
import docking.WindowPosition;
import docking.action.builder.ActionBuilder; import docking.action.builder.ActionBuilder;
import docking.widgets.table.AbstractSortedTableModel; import docking.widgets.table.*;
import docking.widgets.table.GTable;
import docking.widgets.table.RowObjectSelectionManager;
import docking.widgets.table.RowObjectTableModel;
import docking.widgets.table.SelectionManager;
import docking.widgets.table.threaded.ThreadedTableModel; import docking.widgets.table.threaded.ThreadedTableModel;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.app.services.FunctionComparisonService; import ghidra.app.services.FunctionComparisonService;
import ghidra.app.services.MatchedFunctionComparisonModel;
import ghidra.feature.vt.api.impl.VTEvent; import ghidra.feature.vt.api.impl.VTEvent;
import ghidra.feature.vt.api.impl.VersionTrackingChangeRecord; import ghidra.feature.vt.api.impl.VersionTrackingChangeRecord;
import ghidra.feature.vt.api.main.VTMarkupItem; import ghidra.feature.vt.api.main.*;
import ghidra.feature.vt.api.main.VTMatch; import ghidra.feature.vt.gui.actions.*;
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.gui.editors.MatchTagCellEditor; import ghidra.feature.vt.gui.editors.MatchTagCellEditor;
import ghidra.feature.vt.gui.filters.AncillaryFilterDialogComponentProvider; import ghidra.feature.vt.gui.filters.*;
import ghidra.feature.vt.gui.filters.Filter;
import ghidra.feature.vt.gui.filters.Filter.FilterEditingStatus; import ghidra.feature.vt.gui.filters.Filter.FilterEditingStatus;
import ghidra.feature.vt.gui.filters.FilterDialogModel; import ghidra.feature.vt.gui.plugin.*;
import ghidra.feature.vt.gui.filters.FilterStatusListener; import ghidra.feature.vt.gui.util.*;
import ghidra.feature.vt.gui.plugin.VTController; import ghidra.feature.vt.gui.util.AbstractVTMatchTableModel.*;
import ghidra.feature.vt.gui.plugin.VTControllerListener; import ghidra.framework.model.*;
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.framework.options.Options; import ghidra.framework.options.Options;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.ComponentProviderAdapter;
@ -282,21 +156,19 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
} }
private void compareFunctions(VTMatchContext c) { private void compareFunctions(VTMatchContext c) {
Set<Function> sourceFunctions = new HashSet<>(); MatchedFunctionComparisonModel model = new MatchedFunctionComparisonModel();
Set<Function> destinationFunctions = new HashSet<>();
List<VTMatch> matches = c.getFunctionMatches(); List<VTMatch> matches = c.getFunctionMatches();
for (VTMatch match : matches) { for (VTMatch match : matches) {
MatchInfo matchInfo = controller.getMatchInfo(match); MatchInfo matchInfo = controller.getMatchInfo(match);
Function sourceFunction = matchInfo.getSourceFunction(); Function sourceFunction = matchInfo.getSourceFunction();
sourceFunctions.add(sourceFunction);
Function destinationFunction = matchInfo.getDestinationFunction(); Function destinationFunction = matchInfo.getDestinationFunction();
destinationFunctions.add(destinationFunction); model.addMatch(sourceFunction, destinationFunction);
} }
FunctionComparisonService service = tool.getService(FunctionComparisonService.class); FunctionComparisonService service = tool.getService(FunctionComparisonService.class);
service.compareFunctions(sourceFunctions, destinationFunctions); service.createCustomComparison(model, null);
} }
// callback method from the MatchTableSelectionAction // callback method from the MatchTableSelectionAction

View file

@ -34,6 +34,7 @@ import docking.widgets.table.threaded.ThreadedTableModel;
import generic.theme.GColor; import generic.theme.GColor;
import generic.theme.GIcon; import generic.theme.GIcon;
import ghidra.app.services.FunctionComparisonService; import ghidra.app.services.FunctionComparisonService;
import ghidra.app.services.MatchedFunctionComparisonModel;
import ghidra.feature.vt.api.impl.VTEvent; import ghidra.feature.vt.api.impl.VTEvent;
import ghidra.feature.vt.api.main.*; import ghidra.feature.vt.api.main.*;
import ghidra.feature.vt.gui.actions.*; import ghidra.feature.vt.gui.actions.*;
@ -156,30 +157,24 @@ public abstract class VTMatchOneToManyTableProvider extends ComponentProviderAda
private void compareFunctions(VTMatchOneToManyContext c) { private void compareFunctions(VTMatchOneToManyContext c) {
List<VTMatch> selectedMatches = c.getSelectedMatches(); List<VTMatch> selectedMatches = c.getSelectedMatches();
Set<Function> leftFunctions = new HashSet<>();
Set<Function> rightFunctions = new HashSet<>();
MatchedFunctionComparisonModel model = new MatchedFunctionComparisonModel();
for (VTMatch match : selectedMatches) { for (VTMatch match : selectedMatches) {
MatchInfo matchInfo = controller.getMatchInfo(match); 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. // side of the compare functions window.
Function leftFunction = matchInfo.getSourceFunction(), Function leftFunction = matchInfo.getSourceFunction();
rightFunction = matchInfo.getDestinationFunction(); Function rightFunction = matchInfo.getDestinationFunction();
if (!isSource) { if (!isSource) {
leftFunction = matchInfo.getDestinationFunction(); leftFunction = matchInfo.getDestinationFunction();
rightFunction = matchInfo.getSourceFunction(); rightFunction = matchInfo.getSourceFunction();
} }
leftFunctions.add(leftFunction); model.addMatch(leftFunction, rightFunction);
rightFunctions.add(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); FunctionComparisonService service = tool.getService(FunctionComparisonService.class);
service.compareFunctions(leftFunctions, rightFunctions); service.createCustomComparison(model, null);
} }
@Override @Override

View file

@ -129,7 +129,7 @@ public abstract class GhidraURLQuery {
content = wrappedContent.getContent(resultHandler); content = wrappedContent.getContent(resultHandler);
} }
catch (IOException e) { catch (IOException e) {
resultHandler.handleError("Content Not Found", e.getMessage(), null, e); resultHandler.handleError("Content Not Found", e.getMessage(), ghidraUrl, e);
return; return;
} }

View file

@ -25,7 +25,7 @@ import javax.swing.table.TableColumnModel;
import org.junit.Test; import org.junit.Test;
import docking.action.DockingActionIf; import docking.action.DockingActionIf;
import docking.widgets.dialogs.TableChooserDialog; import docking.widgets.dialogs.TableSelectionDialog;
import docking.widgets.table.GFilterTable; import docking.widgets.table.GFilterTable;
import docking.widgets.table.GTable; import docking.widgets.table.GTable;
import ghidra.app.cmd.disassemble.DisassembleCommand; import ghidra.app.cmd.disassemble.DisassembleCommand;
@ -94,12 +94,11 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator {
f2.setName("FunctionB", SourceType.USER_DEFINED); f2.setName("FunctionB", SourceType.USER_DEFINED);
destListing.setComment(addr(0x004118c0), CodeUnit.PLATE_COMMENT, null); destListing.setComment(addr(0x004118c0), CodeUnit.PLATE_COMMENT, null);
FunctionComparisonProviderManager providerMgr = plugin.createComparison(f1, f2);
getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin); FunctionComparisonProvider provider =
FunctionComparisonProvider functionComparisonProvider = waitForComponentProvider(FunctionComparisonProvider.class);
providerMgr.compareFunctions(f1, f2);
FunctionComparisonPanel functionComparisonPanel = FunctionComparisonPanel functionComparisonPanel =
functionComparisonProvider.getComponent(); provider.getComponent();
runSwing(() -> { runSwing(() -> {
functionComparisonPanel.setCurrentTabbedComponent("Listing View"); functionComparisonPanel.setCurrentTabbedComponent("Listing View");
ListingCodeComparisonPanel dualListing = ListingCodeComparisonPanel dualListing =
@ -125,9 +124,7 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator {
Function f1 = getFunction(sourceProgram, addr(0x004118f0)); Function f1 = getFunction(sourceProgram, addr(0x004118f0));
Function f2 = getFunction(destinationProgram, addr(0x004118c0)); Function f2 = getFunction(destinationProgram, addr(0x004118c0));
FunctionComparisonProviderManager providerMgr = plugin.createComparison(f1, f2);
getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin);
providerMgr.compareFunctions(f1, f2);
captureActionIcon("Add Functions To Comparison"); captureActionIcon("Add Functions To Comparison");
} }
@ -137,9 +134,7 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator {
Function f1 = getFunction(sourceProgram, addr(0x004118f0)); Function f1 = getFunction(sourceProgram, addr(0x004118f0));
Function f2 = getFunction(destinationProgram, addr(0x004118c0)); Function f2 = getFunction(destinationProgram, addr(0x004118c0));
FunctionComparisonProviderManager providerMgr = plugin.createComparison(f1, f2);
getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin);
providerMgr.compareFunctions(f1, f2);
captureActionIcon("Remove Functions"); captureActionIcon("Remove Functions");
} }
@ -149,9 +144,7 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator {
Function f1 = getFunction(sourceProgram, addr(0x004118f0)); Function f1 = getFunction(sourceProgram, addr(0x004118f0));
Function f2 = getFunction(destinationProgram, addr(0x004118c0)); Function f2 = getFunction(destinationProgram, addr(0x004118c0));
FunctionComparisonProviderManager providerMgr = plugin.createComparison(CollectionUtils.asSet(f1, f2));
getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin);
providerMgr.compareFunctions(CollectionUtils.asSet(f1, f2));
captureActionIcon("Compare Next Function"); captureActionIcon("Compare Next Function");
} }
@ -161,13 +154,7 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator {
Function f1 = getFunction(sourceProgram, addr(0x004118f0)); Function f1 = getFunction(sourceProgram, addr(0x004118f0));
Function f2 = getFunction(destinationProgram, addr(0x004118c0)); Function f2 = getFunction(destinationProgram, addr(0x004118c0));
FunctionComparisonProviderManager providerMgr = plugin.createComparison(CollectionUtils.asSet(f1, f2));
getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin);
FunctionComparisonProvider functionComparisonProvider =
providerMgr.compareFunctions(CollectionUtils.asSet(f1, f2));
MultiFunctionComparisonPanel panel =
(MultiFunctionComparisonPanel) functionComparisonProvider.getComponent();
panel.getFocusedComponent().setSelectedIndex(1);
captureActionIcon("Compare Previous Function"); captureActionIcon("Compare Previous Function");
} }
@ -177,21 +164,18 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator {
Function f1 = getFunction(sourceProgram, addr(0x004118f0)); Function f1 = getFunction(sourceProgram, addr(0x004118f0));
Function f2 = getFunction(destinationProgram, addr(0x004118c0)); Function f2 = getFunction(destinationProgram, addr(0x004118c0));
FunctionComparisonProviderManager providerMgr = plugin.createComparison(CollectionUtils.asSet(f1, f2));
getInstanceFieldByClassType(FunctionComparisonProviderManager.class, plugin);
providerMgr.compareFunctions(CollectionUtils.asSet(f1, f2));
waitForSwing(); waitForSwing();
DockingActionIf openTableAction = getAction(plugin, "Add Functions To Comparison"); DockingActionIf openTableAction = getAction(tool, "Add Functions To Comparison");
performAction(openTableAction, false); performAction(openTableAction, false);
TableChooserDialog<?> dialog = TableSelectionDialog<?> dialog = waitForDialogComponent(TableSelectionDialog.class);
waitForDialogComponent(TableChooserDialog.class);
setColumnSizes(dialog); setColumnSizes(dialog);
captureDialog(dialog); captureDialog(dialog);
} }
private void setColumnSizes(TableChooserDialog<?> dialog) { private void setColumnSizes(TableSelectionDialog<?> dialog) {
// note: these values are rough values found by trial-and-error // note: these values are rough values found by trial-and-error
GFilterTable<?> filter = (GFilterTable<?>) getInstanceField("gFilterTable", dialog); GFilterTable<?> filter = (GFilterTable<?>) getInstanceField("gFilterTable", dialog);

View file

@ -15,18 +15,23 @@
*/ */
package ghidra.app.plugin.compare; package ghidra.app.plugin.compare;
import static ghidra.util.datastruct.Duo.Side.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.util.List;
import java.util.Set; import java.util.Set;
import org.junit.*; 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.CDisplay;
import ghidra.codecompare.decompile.DecompilerCodeComparisonPanel; import ghidra.codecompare.decompile.DecompilerCodeComparisonPanel;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.test.*; import ghidra.test.*;
import ghidra.util.datastruct.Duo.Side;
/** /**
* Tests for the {@link FunctionComparisonPlugin function comparison plugin} * Tests for the {@link FunctionComparisonPlugin function comparison plugin}
@ -39,7 +44,6 @@ public class CompareFunctionsDecompilerViewTest extends AbstractGhidraHeadedInte
private Function fun1; private Function fun1;
private Function fun2; private Function fun2;
private FunctionComparisonPlugin plugin; private FunctionComparisonPlugin plugin;
private FunctionComparisonProvider provider;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
@ -65,10 +69,13 @@ public class CompareFunctionsDecompilerViewTest extends AbstractGhidraHeadedInte
@Test @Test
public void testDecompDifView() throws Exception { public void testDecompDifView() throws Exception {
Set<Function> functions = CompareFunctionsTestUtility.getFunctionsAsSet(fun1, fun2); Set<Function> functions = Set.of(fun1, fun2);
provider = compareFunctions(functions); compareFunctions(functions);
CompareFunctionsTestUtility.checkSourceFunctions(provider, fun1, fun2); FunctionComparisonProvider provider =
waitForComponentProvider(FunctionComparisonProvider.class);
checkFunctions(provider, LEFT, fun1, fun1, fun2);
DecompilerCodeComparisonPanel panel = DecompilerCodeComparisonPanel panel =
(DecompilerCodeComparisonPanel) provider (DecompilerCodeComparisonPanel) provider
.getCodeComparisonPanelByName(DecompilerCodeComparisonPanel.NAME); .getCodeComparisonPanelByName(DecompilerCodeComparisonPanel.NAME);
@ -78,6 +85,18 @@ public class CompareFunctionsDecompilerViewTest extends AbstractGhidraHeadedInte
assertHasLines(panel.getRightPanel(), 23); assertHasLines(panel.getRightPanel(), 23);
} }
private void checkFunctions(FunctionComparisonProvider provider, Side side,
Function activeFunction, Function... functions) {
Set<Function> funcs = Set.of(functions);
FunctionComparisonModel model = provider.getModel();
assertEquals(activeFunction, model.getActiveFunction(side));
List<Function> fcs = model.getFunctions(side);
assertEquals(fcs.size(), funcs.size());
assertTrue(fcs.containsAll(funcs));
}
private void assertHasLines(CDisplay panel, int lineCount) { private void assertHasLines(CDisplay panel, int lineCount) {
assertEquals(lineCount, panel.getDecompilerPanel().getLines().size()); assertEquals(lineCount, panel.getDecompilerPanel().getLines().size());
} }
@ -88,11 +107,9 @@ public class CompareFunctionsDecompilerViewTest extends AbstractGhidraHeadedInte
waitForSwing(); waitForSwing();
} }
private FunctionComparisonProvider compareFunctions(Set<Function> functions) { private void compareFunctions(Set<Function> functions) {
provider = runSwing(() -> plugin.compareFunctions(functions)); runSwing(() -> plugin.createComparison(functions));
provider.setVisible(true);
waitForSwing(); waitForSwing();
return provider;
} }
private Program buildTestProgram() throws Exception { private Program buildTestProgram() throws Exception {