mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 17:59:46 +02:00
GP-4634 Refactored Function Compare Service and added action to add
functions to the last comparison window.
This commit is contained in:
parent
c19facf226
commit
ddd2f22c28
45 changed files with 2086 additions and 3015 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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:");
|
||||||
|
|
|
@ -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:");
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue