GP-4251: Added Function Compare action to the Version Tracking main

match table and associated match tables.
This commit is contained in:
isabella3412 2024-01-30 22:48:58 +00:00 committed by Ryan Kurtz
parent 77aa79caf1
commit f982e9bba5
12 changed files with 494 additions and 67 deletions

View file

@ -19,13 +19,19 @@ import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
import ghidra.app.CorePluginPackage; import ghidra.app.CorePluginPackage;
import ghidra.app.events.*; import ghidra.app.events.ProgramActivatedPluginEvent;
import ghidra.app.events.ProgramClosedPluginEvent;
import ghidra.app.events.ProgramSelectionPluginEvent;
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.plugin.core.functioncompare.actions.CompareFunctionsAction;
import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsFromListingAction; import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsFromListingAction;
import ghidra.app.services.FunctionComparisonService; import ghidra.app.services.FunctionComparisonService;
import ghidra.framework.model.*; import ghidra.framework.model.DomainObjectChangeRecord;
import ghidra.framework.model.DomainObjectChangedEvent;
import ghidra.framework.model.DomainObjectEvent;
import ghidra.framework.model.DomainObjectListener;
import ghidra.framework.model.EventType;
import ghidra.framework.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginInfo;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
@ -170,6 +176,13 @@ public class FunctionComparisonPlugin extends ProgramPlugin
return getFromSwingBlocking(() -> functionComparisonManager.compareFunctions(functions)); return getFromSwingBlocking(() -> functionComparisonManager.compareFunctions(functions));
} }
@Override
public FunctionComparisonProvider compareFunctions(Set<Function> sourceFunctions,
Set<Function> destinationFunctions) {
return getFromSwingBlocking(() -> functionComparisonManager
.compareFunctions(sourceFunctions, destinationFunctions));
}
@Override @Override
public void compareFunctions(Set<Function> functions, FunctionComparisonProvider provider) { public void compareFunctions(Set<Function> functions, FunctionComparisonProvider provider) {
runOnSwingNonBlocking( runOnSwingNonBlocking(

View file

@ -20,7 +20,10 @@ import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import docking.ComponentProviderActivationListener; import docking.ComponentProviderActivationListener;
import ghidra.framework.model.*; import ghidra.framework.model.DomainObjectChangeRecord;
import ghidra.framework.model.DomainObjectChangedEvent;
import ghidra.framework.model.DomainObjectEvent;
import ghidra.framework.model.EventType;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@ -81,6 +84,23 @@ public class FunctionComparisonProviderManager implements FunctionComparisonProv
return provider; 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 * Creates a new comparison comparison between two functions
* *

View file

@ -18,12 +18,20 @@
*/ */
package ghidra.app.services; package ghidra.app.services;
import java.util.*; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import ghidra.app.plugin.core.functioncompare.*; import ghidra.app.plugin.core.functioncompare.FunctionComparison;
import ghidra.app.plugin.core.functioncompare.FunctionComparisonModelListener;
import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider;
import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
import ghidra.util.Msg; import ghidra.util.Msg;
@ -128,6 +136,35 @@ public class FunctionComparisonModel {
fireModelChanged(); 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 * 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 * the given source, the target will simply be added to it; otherwise a

View file

@ -63,6 +63,19 @@ public interface FunctionComparisonService {
*/ */
public FunctionComparisonProvider compareFunctions(Set<Function> functions); public FunctionComparisonProvider compareFunctions(Set<Function> functions);
/**
* Creates a comparison between two sets of functions, where all the functions in source list can
* be compared against all functions in the destination list.
* <p>
* Note that this method will always create a new provider.
*
* @param sourceFunctions
* @param destinationFunctions
* @return the new comparison provider
*/
public FunctionComparisonProvider compareFunctions(Set<Function> sourceFunctions,
Set<Function> destinationFunctions);
/** /**
* Creates a comparison between two functions, where the source function * Creates a comparison between two functions, where the source function
* will be shown on the left side of the comparison dialog and the target * will be shown on the left side of the comparison dialog and the target

View file

@ -383,11 +383,19 @@
<BR> <BR>
<BR> <BR>
<P align="left"><A name="Match_Table_Settings"></A>The <b>Settings</b> <IMG src="images/settings16.gif" border="0"> <P align="left"><A name="Match_Table_Settings"></A>The <b>Settings</b> <IMG src="images/settings16.gif" border="0">
action will bring up the version tracking accept and apply options.</P> action will bring up the version tracking accept and apply options.</P>
<P align="left"><A name="Match_Table_Compare_Functions"></A>The <b>Compare Functions</b>
action allows users to visually compare selected matched functions. To initiate this action,
select one or more matches from the Version Tracking Matches Table, then choose
<b>Compare Functions</b> from the pop up menu. This will open a new
<A href="help/topics/FunctionComparison/FunctionComparison.htm">Function Comparison</A>
table containing a list of source functions and a list of destination functions.
The user can choose one from each list at a time to visually compare to each other.
Note: You cannot compare Data matches or External Functions using this action, so
if you select either of these as matches they will not be populated in the table.</P>
</BLOCKQUOTE> </BLOCKQUOTE>
<H2><A name="Match_Filters"></A>Match Filters</H2> <H2><A name="Match_Filters"></A>Match Filters</H2>

View file

@ -110,6 +110,24 @@
<H2><A name="Related_Match_Actions"></A>Actions</H2> <H2><A name="Related_Match_Actions"></A>Actions</H2>
<BLOCKQUOTE>
<H3><A name="Compare_Functions">Compare Functions</A></H3>
<BLOCKQUOTE>
<P>To initiate this action, navigate to the <B>Related Matches</B> table in either the
Destination Tool or the Source Tool. Select one or more matches in the table, then choose
<B>Compare Functions</B> from the right-mouse menu. This action will open a new
<A HREF="help/topics/FunctionComparison/FunctionComparison.htm">Function Comparison</A>
table. The left side of the window will display the function in the current tool location.
The right side of the window will contain a pull-down list with the selected matched
function(s) from the other program. This allows a user to visually compare the function at
the current tool location with the selected matched function(s) from the other program.</P>
<P>Note: You cannot compare Data matches or External Functions using this action, so
if you select either of these as matches they will not be populated in the table.</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<BLOCKQUOTE> <BLOCKQUOTE>
<H3><A name="Select_Same_Match_In_Version_Tracking_Matches_Table">Select Match in VT <H3><A name="Select_Same_Match_In_Version_Tracking_Matches_Table">Select Match in VT
Matches Table</A> <IMG alt="" border="0" src="Icons.MAKE_SELECTION_ICON"></H3> Matches Table</A> <IMG alt="" border="0" src="Icons.MAKE_SELECTION_ICON"></H3>

View file

@ -16,8 +16,8 @@
package ghidra.feature.vt.gui.plugin; package ghidra.feature.vt.gui.plugin;
import java.net.URL; import java.net.URL;
import java.util.List; import java.util.*;
import java.util.Set; import java.util.stream.Collectors;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.JFrame; import javax.swing.JFrame;
@ -42,6 +42,7 @@ import ghidra.framework.model.*;
import ghidra.framework.options.Options; import ghidra.framework.options.Options;
import ghidra.framework.options.SaveState; import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.PluginException;
import ghidra.framework.plugintool.util.PluginStatus; import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.framework.preferences.Preferences; import ghidra.framework.preferences.Preferences;
import ghidra.program.model.address.AddressSetView; import ghidra.program.model.address.AddressSetView;
@ -119,6 +120,7 @@ public class VTPlugin extends Plugin {
new ImpliedMatchAssociationHook(controller); new ImpliedMatchAssociationHook(controller);
initializeOptions(); initializeOptions();
} }
private DockingActionIf getToolAction(String actionName) { private DockingActionIf getToolAction(String actionName) {
@ -141,9 +143,37 @@ public class VTPlugin extends Plugin {
@Override @Override
protected void init() { protected void init() {
addCustomPlugins();
maybeShowHelp(); maybeShowHelp();
} }
private void addCustomPlugins() {
List<String> names = new ArrayList<>(
List.of("ghidra.app.plugin.core.functioncompare.FunctionComparisonPlugin"));
List<Plugin> plugins = tool.getManagedPlugins();
Set<String> existingNames =
plugins.stream().map(c -> c.getName()).collect(Collectors.toSet());
// Note: we check to see if the plugins we want to add have already been added to the tool.
// We should not needed to do this, but once the tool has been saved with the plugins added,
// they will get added again the next time the tool is loaded. Adding this check here seems
// easier than modifying the default to file to load the plugins, since the amount of xml
// required for that is non-trivial.
try {
for (String className : names) {
if (!existingNames.contains(className)) {
tool.addPlugin(className);
}
}
}
catch (PluginException e) {
Msg.error(this, "Unable to load plugin", e);
}
}
private void maybeShowHelp() { private void maybeShowHelp() {
if (SystemUtilities.isInDevelopmentMode() || SystemUtilities.isInTestingMode()) { if (SystemUtilities.isInDevelopmentMode() || SystemUtilities.isInTestingMode()) {
return; // don't show help for dev mode return; // don't show help for dev mode

View file

@ -658,7 +658,7 @@ public class VTSubToolManager implements VTControllerListener, OptionsChangeList
* *
* @return The source tool from the VT session. * @return The source tool from the VT session.
*/ */
PluginTool getSourceTool() { public PluginTool getSourceTool() {
return sourceTool; return sourceTool;
} }

View file

@ -15,9 +15,12 @@
*/ */
package ghidra.feature.vt.gui.provider.matchtable; package ghidra.feature.vt.gui.provider.matchtable;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import docking.DefaultActionContext; import docking.DefaultActionContext;
import ghidra.feature.vt.api.main.VTAssociation;
import ghidra.feature.vt.api.main.VTAssociationType;
import ghidra.feature.vt.api.main.VTMatch; import ghidra.feature.vt.api.main.VTMatch;
import ghidra.feature.vt.api.main.VTSession; import ghidra.feature.vt.api.main.VTSession;
@ -37,7 +40,25 @@ public class VTMatchContext extends DefaultActionContext {
return selectedMatches; return selectedMatches;
} }
public int getSelectedRowCount() {
return selectedMatches.size();
}
public VTSession getSession() { public VTSession getSession() {
return session; return session;
} }
public List<VTMatch> getFunctionMatches() {
List<VTMatch> functionMatches = new ArrayList<>();
for (VTMatch match : selectedMatches) {
VTAssociation association = match.getAssociation();
if (association.getType() != VTAssociationType.FUNCTION) {
continue;
}
functionMatches.add(match);
}
return functionMatches;
}
} }

View file

@ -15,39 +15,170 @@
*/ */
package ghidra.feature.vt.gui.provider.matchtable; package ghidra.feature.vt.gui.provider.matchtable;
import static ghidra.feature.vt.api.impl.VTEvent.*; import static ghidra.feature.vt.api.impl.VTEvent.ASSOCIATION_STATUS_CHANGED;
import static ghidra.feature.vt.gui.actions.TableSelectionTrackingState.*; import static ghidra.feature.vt.api.impl.VTEvent.MATCH_SET_ADDED;
import static ghidra.feature.vt.gui.plugin.VTPlugin.*; import static ghidra.feature.vt.gui.actions.TableSelectionTrackingState.MAINTAIN_SELECTED_ROW_INDEX;
import static ghidra.feature.vt.gui.util.VTOptionDefines.*; import static ghidra.feature.vt.gui.actions.TableSelectionTrackingState.MAINTAIN_SELECTED_ROW_VALUE;
import static ghidra.framework.model.DomainObjectEvent.*; import static ghidra.feature.vt.gui.actions.TableSelectionTrackingState.NO_SELECTION_TRACKING;
import static ghidra.feature.vt.gui.plugin.VTPlugin.FILTERED_ICON;
import static ghidra.feature.vt.gui.plugin.VTPlugin.UNFILTERED_ICON;
import static ghidra.feature.vt.gui.util.VTOptionDefines.ACCEPT_MATCH_OPTIONS_NAME;
import static ghidra.feature.vt.gui.util.VTOptionDefines.APPLY_DATA_NAME_ON_ACCEPT;
import static ghidra.feature.vt.gui.util.VTOptionDefines.APPLY_FUNCTION_NAME_ON_ACCEPT;
import static ghidra.feature.vt.gui.util.VTOptionDefines.APPLY_IMPLIED_MATCHES_OPTION;
import static ghidra.feature.vt.gui.util.VTOptionDefines.APPLY_MARKUP_OPTIONS_NAME;
import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_CREATE_IMPLIED_MATCH;
import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_VT_DATA_CORRELATOR;
import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_VT_DUPLICATE_FUNCTION_CORRELATOR;
import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_VT_EXACT_FUNCTION_CORRELATORS;
import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_VT_IMPLIED_MATCH_CORRELATOR;
import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_VT_OPTIONS_NAME;
import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_VT_REFERENCE_CORRELATORS;
import static ghidra.feature.vt.gui.util.VTOptionDefines.AUTO_VT_SYMBOL_CORRELATOR;
import static ghidra.feature.vt.gui.util.VTOptionDefines.CALLING_CONVENTION;
import static ghidra.feature.vt.gui.util.VTOptionDefines.CALL_FIXUP;
import static ghidra.feature.vt.gui.util.VTOptionDefines.CREATE_IMPLIED_MATCHES_OPTION;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DATA_CORRELATOR_MIN_LEN_OPTION;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DATA_MATCH_DATA_TYPE;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_CALLING_CONVENTION;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_CALL_FIXUP;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_DATA_MATCH_DATA_TYPE;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_EOL_COMMENTS;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_FUNCTION_NAME;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_FUNCTION_RETURN_TYPE;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_FUNCTION_SIGNATURE;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_HIGHEST_NAME_PRIORITY;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_IGNORE_EXCLUDED_MARKUP_ITEMS;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_IGNORE_INCOMPLETE_MARKUP_ITEMS;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_INLINE;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_LABELS;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_NO_RETURN;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_PARAMETER_COMMENTS;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_PARAMETER_DATA_TYPES;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_PARAMETER_NAMES;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_PARAMETER_NAMES_REPLACE_IF_SAME_PRIORITY;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_PLATE_COMMENTS;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_POST_COMMENTS;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_PRE_COMMENTS;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_REPEATABLE_COMMENTS;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DEFAULT_OPTION_FOR_VAR_ARGS;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DISPLAY_APPLY_MARKUP_OPTIONS;
import static ghidra.feature.vt.gui.util.VTOptionDefines.DUPE_FUNCTION_CORRELATOR_MIN_LEN_OPTION;
import static ghidra.feature.vt.gui.util.VTOptionDefines.END_OF_LINE_COMMENT;
import static ghidra.feature.vt.gui.util.VTOptionDefines.FUNCTION_CORRELATOR_MIN_LEN_OPTION;
import static ghidra.feature.vt.gui.util.VTOptionDefines.FUNCTION_NAME;
import static ghidra.feature.vt.gui.util.VTOptionDefines.FUNCTION_RETURN_TYPE;
import static ghidra.feature.vt.gui.util.VTOptionDefines.FUNCTION_SIGNATURE;
import static ghidra.feature.vt.gui.util.VTOptionDefines.HIGHEST_NAME_PRIORITY;
import static ghidra.feature.vt.gui.util.VTOptionDefines.IGNORE_EXCLUDED_MARKUP_ITEMS;
import static ghidra.feature.vt.gui.util.VTOptionDefines.IGNORE_INCOMPLETE_MARKUP_ITEMS;
import static ghidra.feature.vt.gui.util.VTOptionDefines.INLINE;
import static ghidra.feature.vt.gui.util.VTOptionDefines.LABELS;
import static ghidra.feature.vt.gui.util.VTOptionDefines.MAX_CONFLICTS_OPTION;
import static ghidra.feature.vt.gui.util.VTOptionDefines.MIN_VOTES_OPTION;
import static ghidra.feature.vt.gui.util.VTOptionDefines.NO_RETURN;
import static ghidra.feature.vt.gui.util.VTOptionDefines.PARAMETER_COMMENTS;
import static ghidra.feature.vt.gui.util.VTOptionDefines.PARAMETER_DATA_TYPES;
import static ghidra.feature.vt.gui.util.VTOptionDefines.PARAMETER_NAMES;
import static ghidra.feature.vt.gui.util.VTOptionDefines.PARAMETER_NAMES_REPLACE_IF_SAME_PRIORITY;
import static ghidra.feature.vt.gui.util.VTOptionDefines.PLATE_COMMENT;
import static ghidra.feature.vt.gui.util.VTOptionDefines.POST_COMMENT;
import static ghidra.feature.vt.gui.util.VTOptionDefines.PRE_COMMENT;
import static ghidra.feature.vt.gui.util.VTOptionDefines.REF_CORRELATOR_MIN_CONF_OPTION;
import static ghidra.feature.vt.gui.util.VTOptionDefines.REF_CORRELATOR_MIN_SCORE_OPTION;
import static ghidra.feature.vt.gui.util.VTOptionDefines.REPEATABLE_COMMENT;
import static ghidra.feature.vt.gui.util.VTOptionDefines.RUN_DUPE_FUNCTION_OPTION;
import static ghidra.feature.vt.gui.util.VTOptionDefines.RUN_EXACT_DATA_OPTION;
import static ghidra.feature.vt.gui.util.VTOptionDefines.RUN_EXACT_FUNCTION_BYTES_OPTION;
import static ghidra.feature.vt.gui.util.VTOptionDefines.RUN_EXACT_FUNCTION_INST_OPTION;
import static ghidra.feature.vt.gui.util.VTOptionDefines.RUN_EXACT_SYMBOL_OPTION;
import static ghidra.feature.vt.gui.util.VTOptionDefines.RUN_REF_CORRELATORS_OPTION;
import static ghidra.feature.vt.gui.util.VTOptionDefines.SYMBOL_CORRELATOR_MIN_LEN_OPTION;
import static ghidra.feature.vt.gui.util.VTOptionDefines.VAR_ARGS;
import static ghidra.framework.model.DomainObjectEvent.RESTORED;
import java.awt.*; import java.awt.Adjustable;
import java.awt.event.*; import java.awt.BorderLayout;
import java.util.*; import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import javax.swing.*; import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener; import javax.swing.event.ListSelectionListener;
import javax.swing.table.*; import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import docking.*; import docking.ActionContext;
import docking.widgets.table.*; import docking.DockingWindowManager;
import docking.WindowPosition;
import docking.action.builder.ActionBuilder;
import docking.widgets.table.AbstractSortedTableModel;
import docking.widgets.table.GTable;
import docking.widgets.table.RowObjectSelectionManager;
import docking.widgets.table.RowObjectTableModel;
import docking.widgets.table.SelectionManager;
import docking.widgets.table.threaded.ThreadedTableModel; import docking.widgets.table.threaded.ThreadedTableModel;
import generic.theme.GIcon;
import ghidra.app.services.FunctionComparisonService;
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.*; import ghidra.feature.vt.api.main.VTMarkupItem;
import ghidra.feature.vt.gui.actions.*; import ghidra.feature.vt.api.main.VTMatch;
import ghidra.feature.vt.api.main.VTSession;
import ghidra.feature.vt.gui.actions.AcceptMatchAction;
import ghidra.feature.vt.gui.actions.ApplyBlockedMatchAction;
import ghidra.feature.vt.gui.actions.ApplyMatchAction;
import ghidra.feature.vt.gui.actions.ChooseMatchTagAction;
import ghidra.feature.vt.gui.actions.ClearMatchAction;
import ghidra.feature.vt.gui.actions.CreateSelectionAction;
import ghidra.feature.vt.gui.actions.EditAllTagsAction;
import ghidra.feature.vt.gui.actions.MatchTableSelectionAction;
import ghidra.feature.vt.gui.actions.RejectMatchAction;
import ghidra.feature.vt.gui.actions.RemoveMatchAction;
import ghidra.feature.vt.gui.actions.RemoveMatchTagAction;
import ghidra.feature.vt.gui.actions.TableSelectionTrackingState;
import ghidra.feature.vt.gui.editors.MatchTagCellEditor; import ghidra.feature.vt.gui.editors.MatchTagCellEditor;
import ghidra.feature.vt.gui.filters.*; import ghidra.feature.vt.gui.filters.AncillaryFilterDialogComponentProvider;
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.plugin.*; import ghidra.feature.vt.gui.filters.FilterDialogModel;
import ghidra.feature.vt.gui.util.*; import ghidra.feature.vt.gui.filters.FilterStatusListener;
import ghidra.feature.vt.gui.util.AbstractVTMatchTableModel.*; import ghidra.feature.vt.gui.plugin.VTController;
import ghidra.framework.model.*; import ghidra.feature.vt.gui.plugin.VTControllerListener;
import ghidra.feature.vt.gui.plugin.VTPlugin;
import ghidra.feature.vt.gui.plugin.VersionTrackingPluginPackage;
import ghidra.feature.vt.gui.util.AbstractVTMatchTableModel.DestinationLabelTableColumn;
import ghidra.feature.vt.gui.util.AbstractVTMatchTableModel.SourceLabelTableColumn;
import ghidra.feature.vt.gui.util.AbstractVTMatchTableModel.StatusTableColumn;
import ghidra.feature.vt.gui.util.AbstractVTMatchTableModel.TagTableColumn;
import ghidra.feature.vt.gui.util.AllTextFilter;
import ghidra.feature.vt.gui.util.FilterIconFlashTimer;
import ghidra.feature.vt.gui.util.MatchInfo;
import ghidra.feature.vt.gui.util.MatchStatusRenderer;
import ghidra.feature.vt.gui.util.VTSymbolRenderer;
import ghidra.framework.model.DomainObjectChangeRecord;
import ghidra.framework.model.DomainObjectChangedEvent;
import ghidra.framework.model.EventType;
import ghidra.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;
import ghidra.program.model.listing.Function;
import ghidra.util.HelpLocation; import ghidra.util.HelpLocation;
import ghidra.util.SystemUtilities; import ghidra.util.SystemUtilities;
import ghidra.util.exception.AssertException; import ghidra.util.exception.AssertException;
@ -60,6 +191,8 @@ import help.HelpService;
public class VTMatchTableProvider extends ComponentProviderAdapter public class VTMatchTableProvider extends ComponentProviderAdapter
implements FilterDialogModel<VTMatch>, VTControllerListener { implements FilterDialogModel<VTMatch>, VTControllerListener {
private static final Icon COMPARISON_ICON = new GIcon("icon.plugin.functioncompare.new");
private static final String TITLE = "Version Tracking Matches"; private static final String TITLE = "Version Tracking Matches";
private static final String TABLE_SELECTION_STATE = "TABLE_SELECTION_STATE"; private static final String TABLE_SELECTION_STATE = "TABLE_SELECTION_STATE";
@ -128,6 +261,42 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
addLocalAction(new CreateSelectionAction(controller)); addLocalAction(new CreateSelectionAction(controller));
tableSelectionStateAction = new MatchTableSelectionAction(this); tableSelectionStateAction = new MatchTableSelectionAction(this);
addLocalAction(tableSelectionStateAction); addLocalAction(tableSelectionStateAction);
new ActionBuilder("Compare Functions", getName()).popupMenuPath("Compare Functions")
.popupMenuGroup("Selection")
.popupMenuIcon(COMPARISON_ICON)
.keyBinding("shift c")
.sharedKeyBinding()
.description("Compares the Function(s) with its remote match")
.helpLocation(
new HelpLocation("VersionTrackingPlugin", "Match_Table_Compare_Functions"))
.withContext(VTMatchContext.class)
.enabledWhen(this::isValidFunctionComparison)
.onAction(this::compareFunctions)
.buildAndInstallLocal(this);
}
private boolean isValidFunctionComparison(VTMatchContext context) {
List<VTMatch> functionMatches = context.getFunctionMatches();
return !functionMatches.isEmpty();
}
private void compareFunctions(VTMatchContext c) {
Set<Function> sourceFunctions = new HashSet<>();
Set<Function> destinationFunctions = new HashSet<>();
List<VTMatch> matches = c.getFunctionMatches();
for (VTMatch match : matches) {
MatchInfo matchInfo = controller.getMatchInfo(match);
Function sourceFunction = matchInfo.getSourceFunction();
sourceFunctions.add(sourceFunction);
Function destinationFunction = matchInfo.getDestinationFunction();
destinationFunctions.add(destinationFunction);
}
FunctionComparisonService service = tool.getService(FunctionComparisonService.class);
service.compareFunctions(sourceFunctions, destinationFunctions);
} }
// callback method from the MatchTableSelectionAction // callback method from the MatchTableSelectionAction
@ -601,12 +770,14 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
@Override @Override
public void optionsChanged(Options options) { public void optionsChanged(Options options) {
// implemented as ControllerListener. Don't care about options changed right now. // implemented as ControllerListener. Don't care about options changed right
// now.
} }
@Override @Override
public void markupItemSelected(VTMarkupItem markupItem) { public void markupItemSelected(VTMarkupItem markupItem) {
// Do nothing since the matches table doesn't need to respond to the mark-up that is selected. // Do nothing since the matches table doesn't need to respond to the mark-up
// that is selected.
} }
private void initializeOptions() { private void initializeOptions() {
@ -828,8 +999,8 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
} }
/** /**
* Forces a refilter, even though filtering operations may be disabled. The reload * Forces a refilter, even though filtering operations may be disabled. The
* is necessary since the model contents may have changed * reload is necessary since the model contents may have changed
*/ */
@Override @Override
public void forceRefilter() { public void forceRefilter() {
@ -891,20 +1062,22 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
} }
/** /**
* A class meant to override the default table selection behavior <b>in special situations</b>. * A class meant to override the default table selection behavior <b>in special
* situations</b>.
* <p> * <p>
* <u>Issue 1:</u> Accepting or applying a match can trigger the match to be filtered out * <u>Issue 1:</u> Accepting or applying a match can trigger the match to be
* of the table. The default SelectionManager does not restore the selection for that item, * filtered out of the table. The default SelectionManager does not restore the
* as it knows that the item is gone. * selection for that item, as it knows that the item is gone.
* <p> * <p>
* <u>Issue 2:</u> Accepting or applying a match can trigger the match to be moved due to a * <u>Issue 2:</u> Accepting or applying a match can trigger the match to be
* sort operation after the edit. * moved due to a sort operation after the edit.
* <p> * <p>
* <u>Desired Behavior:</u> Have the selection restored to the previous location, even if the * <u>Desired Behavior:</u> Have the selection restored to the previous
* item is moved or removed. * location, even if the item is moved or removed.
* <p> * <p>
* Creating this object will cancel the default behavior. Calling <tt>restoreSelection</tt> * Creating this object will cancel the default behavior. Calling
* will set the new selection, depending upon the conditions described above. * <tt>restoreSelection</tt> will set the new selection, depending upon the
* conditions described above.
*/ */
private class SelectionOverrideMemento { private class SelectionOverrideMemento {
private final int row; private final int row;
@ -952,7 +1125,8 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
} }
// At this point the selection model may still believe that its selection is the // At this point the selection model may still believe that its selection is the
// value we are setting. Calling clearSelection() will kick the model. Without the // value we are setting. Calling clearSelection() will kick the model. Without
// the
// kick, the setSelectionInterval() call we make may ultimately have no effect. // kick, the setSelectionInterval() call we make may ultimately have no effect.
selectionModel.clearSelection(); selectionModel.clearSelection();
@ -989,8 +1163,8 @@ public class VTMatchTableProvider extends ComponentProviderAdapter
} }
/** /**
* Override the built-in SelectionManager so that we can respond to the current table * Override the built-in SelectionManager so that we can respond to the current
* selection mode. * table selection mode.
*/ */
private class VTMatchTableSelectionManager extends RowObjectSelectionManager<VTMatch> { private class VTMatchTableSelectionManager extends RowObjectSelectionManager<VTMatch> {
VTMatchTableSelectionManager(JTable table, AbstractSortedTableModel<VTMatch> tableModel) { VTMatchTableSelectionManager(JTable table, AbstractSortedTableModel<VTMatch> tableModel) {

View file

@ -15,9 +15,12 @@
*/ */
package ghidra.feature.vt.gui.provider.onetomany; package ghidra.feature.vt.gui.provider.onetomany;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import docking.DefaultActionContext; import docking.DefaultActionContext;
import ghidra.feature.vt.api.main.VTAssociation;
import ghidra.feature.vt.api.main.VTAssociationType;
import ghidra.feature.vt.api.main.VTMatch; import ghidra.feature.vt.api.main.VTMatch;
public class VTMatchOneToManyContext extends DefaultActionContext { public class VTMatchOneToManyContext extends DefaultActionContext {
@ -32,4 +35,23 @@ public class VTMatchOneToManyContext extends DefaultActionContext {
public List<VTMatch> getSelectedMatches() { public List<VTMatch> getSelectedMatches() {
return selectedItems; return selectedItems;
} }
public int getSelectedRowCount() {
return selectedItems.size();
}
public List<VTMatch> getFunctionMatches() {
List<VTMatch> functionMatches = new ArrayList<>();
for (VTMatch match : selectedItems) {
VTAssociation association = match.getAssociation();
if (association.getType() != VTAssociationType.FUNCTION) {
continue;
}
functionMatches.add(match);
}
return functionMatches;
}
} }

View file

@ -15,39 +15,70 @@
*/ */
package ghidra.feature.vt.gui.provider.onetomany; package ghidra.feature.vt.gui.provider.onetomany;
import java.awt.*; import java.awt.Adjustable;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.util.*; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import javax.swing.*; import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JTable;
import javax.swing.JToggleButton;
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.*; import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import docking.ActionContext; import docking.ActionContext;
import docking.action.builder.ActionBuilder;
import docking.widgets.label.GDLabel; import docking.widgets.label.GDLabel;
import docking.widgets.table.GTable; import docking.widgets.table.GTable;
import docking.widgets.table.RowObjectTableModel; import docking.widgets.table.RowObjectTableModel;
import docking.widgets.table.threaded.ThreadedTableModel; 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.feature.vt.api.impl.VTEvent; import ghidra.feature.vt.api.impl.VTEvent;
import ghidra.feature.vt.api.main.*; import ghidra.feature.vt.api.main.VTMarkupItem;
import ghidra.feature.vt.gui.actions.*; import ghidra.feature.vt.api.main.VTMatch;
import ghidra.feature.vt.gui.filters.*; import ghidra.feature.vt.api.main.VTSession;
import ghidra.feature.vt.gui.actions.AcceptMatchAction;
import ghidra.feature.vt.gui.actions.ClearMatchAction;
import ghidra.feature.vt.gui.actions.SetVTMatchFromOneToManyAction;
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.plugin.*; import ghidra.feature.vt.gui.filters.FilterDialogModel;
import ghidra.feature.vt.gui.filters.FilterStatusListener;
import ghidra.feature.vt.gui.plugin.VTController;
import ghidra.feature.vt.gui.plugin.VTControllerListener;
import ghidra.feature.vt.gui.plugin.VTPlugin;
import ghidra.feature.vt.gui.plugin.VTSubToolManager;
import ghidra.feature.vt.gui.plugin.VTSubToolManagerListener;
import ghidra.feature.vt.gui.provider.markuptable.DisplayableListingAddress; import ghidra.feature.vt.gui.provider.markuptable.DisplayableListingAddress;
import ghidra.feature.vt.gui.provider.matchtable.MatchTableRenderer; import ghidra.feature.vt.gui.provider.matchtable.MatchTableRenderer;
import ghidra.feature.vt.gui.util.AbstractVTMatchTableModel.StatusTableColumn; import ghidra.feature.vt.gui.util.AbstractVTMatchTableModel.StatusTableColumn;
import ghidra.feature.vt.gui.util.MatchInfo; import ghidra.feature.vt.gui.util.MatchInfo;
import ghidra.feature.vt.gui.util.MatchStatusRenderer; import ghidra.feature.vt.gui.util.MatchStatusRenderer;
import ghidra.framework.model.*; import ghidra.framework.model.DomainObjectChangeRecord;
import ghidra.framework.model.DomainObjectChangedEvent;
import ghidra.framework.model.DomainObjectEvent;
import ghidra.framework.model.EventType;
import ghidra.framework.options.Options; import ghidra.framework.options.Options;
import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
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.model.symbol.SymbolTable; import ghidra.program.model.symbol.SymbolTable;
@ -58,24 +89,25 @@ import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraThreadedTablePanel; import ghidra.util.table.GhidraThreadedTablePanel;
/** /**
* The docking window that provides a table of the other tool's function matches for the function * The docking window that provides a table of the other tool's function matches
* containing the current cursor location in this tool's listing. * for the function containing the current cursor location in this tool's
* listing.
*/ */
public abstract class VTMatchOneToManyTableProvider extends ComponentProviderAdapter public abstract class VTMatchOneToManyTableProvider extends ComponentProviderAdapter
implements FilterDialogModel<VTMatch>, VTControllerListener, VTSubToolManagerListener { implements FilterDialogModel<VTMatch>, VTControllerListener, VTSubToolManagerListener {
private static final String TITLE_PREFIX = "Version Tracking Matches for "; private static final String TITLE_PREFIX = "Version Tracking Matches for ";
private static final Icon ICON = new GIcon("icon.version.tracking.provider.one.to.many"); private static final Icon ICON = new GIcon("icon.version.tracking.provider.one.to.many");
private static final Icon COMPARISON_ICON = new GIcon("icon.plugin.functioncompare.new");
protected static final Color LOCAL_INFO_FOREGROUND_COLOR = protected static final Color LOCAL_INFO_FOREGROUND_COLOR = new GColor(
new GColor("color.fg.version.tracking.function.match.local.info"); "color.fg.version.tracking.function.match.local.info");
private JComponent component; private JComponent component;
private MatchThreadedTablePanel tablePanel; private MatchThreadedTablePanel tablePanel;
protected GhidraTable matchesTable; protected GhidraTable matchesTable;
private ListSelectionListener matchSelectionListener; private ListSelectionListener matchSelectionListener;
protected VTMatchOneToManyTableModel oneToManyTableModel; protected VTMatchOneToManyTableModel oneToManyTableModel;
private JToggleButton ancillaryFilterButton; private JToggleButton ancillaryFilterButton;
private Set<Filter<VTMatch>> filters = new HashSet<>(); private Set<Filter<VTMatch>> filters = new HashSet<>();
private FilterStatusListener refilterListener = new RefilterListener(); private FilterStatusListener refilterListener = new RefilterListener();
@ -100,7 +132,8 @@ public abstract class VTMatchOneToManyTableProvider extends ComponentProviderAda
private VTMatch pendingMatchSelection; private VTMatch pendingMatchSelection;
public VTMatchOneToManyTableProvider(PluginTool tool, VTController controller, public VTMatchOneToManyTableProvider(PluginTool tool, VTController controller,
VTSubToolManager subToolManager, boolean isSource) { VTSubToolManager subToolManager,
boolean isSource) {
super(tool, TITLE_PREFIX + (isSource ? "Source" : "Destination"), VTPlugin.OWNER); super(tool, TITLE_PREFIX + (isSource ? "Source" : "Destination"), VTPlugin.OWNER);
this.controller = controller; this.controller = controller;
this.subToolManager = subToolManager; this.subToolManager = subToolManager;
@ -130,6 +163,43 @@ public abstract class VTMatchOneToManyTableProvider extends ComponentProviderAda
addLocalAction(new SetVTMatchFromOneToManyAction(controller, true)); addLocalAction(new SetVTMatchFromOneToManyAction(controller, true));
addLocalAction(new ClearMatchAction(controller)); addLocalAction(new ClearMatchAction(controller));
addLocalAction(new AcceptMatchAction(controller)); addLocalAction(new AcceptMatchAction(controller));
new ActionBuilder("Compare Functions", getName())
.popupMenuPath("Compare Functions")
.popupMenuIcon(COMPARISON_ICON)
.popupMenuGroup("AAA_VT_Main")
.keyBinding("shift c")
.sharedKeyBinding()
.description("Compares the Function(s) with its remote match")
.helpLocation(new HelpLocation("VersionTrackingPlugin", "Compare_Functions"))
.withContext(VTMatchOneToManyContext.class)
.enabledWhen(this::isValidFunctionComparison)
.onAction(this::compareFunctions)
.buildAndInstallLocal(this);
}
private boolean isValidFunctionComparison(VTMatchOneToManyContext context) {
List<VTMatch> functionMatches = context.getFunctionMatches();
return !functionMatches.isEmpty();
}
private void compareFunctions(VTMatchOneToManyContext c) {
List<VTMatch> selectedMatches = c.getSelectedMatches();
for (VTMatch match : selectedMatches) {
MatchInfo matchInfo = controller.getMatchInfo(match);
// Whichever codebrowser we are currently in, is what will be on the left
// side of the compare functions window.
Function leftFunction = matchInfo.getSourceFunction(),
rightFunction = matchInfo.getDestinationFunction();
if (!isSource) {
leftFunction = matchInfo.getDestinationFunction();
rightFunction = matchInfo.getSourceFunction();
}
FunctionComparisonService service = tool.getService(FunctionComparisonService.class);
service.compareFunctions(leftFunction, rightFunction);
}
} }
@Override @Override
@ -292,9 +362,9 @@ public abstract class VTMatchOneToManyTableProvider extends ComponentProviderAda
private List<VTMatch> getSelectedMatches() { private List<VTMatch> getSelectedMatches() {
List<VTMatch> list = new ArrayList<>(); List<VTMatch> list = new ArrayList<>();
int selectedRowCount = matchesTable.getSelectedRowCount(); int[] selectedRows = matchesTable.getSelectedRows();
if (selectedRowCount == 1) {
int row = matchesTable.getSelectedRow(); for (int row : selectedRows) {
VTMatch mySelectedMatch = oneToManyTableModel.getRowObject(row); VTMatch mySelectedMatch = oneToManyTableModel.getRowObject(row);
list.add(mySelectedMatch); list.add(mySelectedMatch);
} }
@ -467,7 +537,8 @@ public abstract class VTMatchOneToManyTableProvider extends ComponentProviderAda
@Override @Override
public void markupItemSelected(VTMarkupItem markupItem) { public void markupItemSelected(VTMarkupItem markupItem) {
// Do nothing since the one to many match table doesn't need to respond to the mark-up that is selected. // Do nothing since the one to many match table doesn't need to respond to the
// mark-up that is selected.
} }
//================================================================================================== //==================================================================================================