Merge remote-tracking branch 'origin/GP-4619_ghidragon_tweak_compare_funcions_actions_in_listing_and_decompiler--SQUASHED' into Ghidra_11.2

This commit is contained in:
Ryan Kurtz 2024-09-11 06:16:19 -04:00
commit 3edd98fc69
7 changed files with 145 additions and 87 deletions

View File

@ -8,7 +8,8 @@
</HEAD>
<BODY>
<H1><A name="FunctionComparisonPlugin"></A> <A name="Function_Comparison"></A> <A name=
<A name="Function_Comparison"></A>
<H1><A name="FunctionComparisonPlugin"></A> <A name=
"FunctionComparison"></A> Function Comparison Window</H1>
@ -21,21 +22,18 @@
</CENTER><BR>
<BR>
<BLOCKQUOTE>
<A name="Function_Comparison_Actions"></A>
<P>To Compare Functions, select one or more functions from the Listing, Decompiler or the
<A HREF="help/topics/FunctionWindowPlugin/function_window.htm">Functions Table</A>, then
right-click and select the <B>Compare Functions(s)</B> action (In the Listing, it is
<B>Function <IMG src="help/shared/arrow.gif" border="0"> Compare Function(s)</B>).
<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>.
Then right-click and select the <b>Compare Selected Functions</b> option.</P>
<P><IMG src="help/shared/tip.png" border="0">If an existing function comparison window is
already showing, the <B>Compare Function(s)</B> action will add the selected functions to
the existing comparison. To get a new <B>Function Comparison</B> window, use the
<B>Compare in New Window</B> action instead.</P>
<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>
<BLOCKQUOTE>
<A name="Function_Comparison_Add_To"></A>
<P><IMG src="help/shared/tip.png" border="0">Additional functions can be added to the
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>

View File

@ -4,9 +4,9 @@
* 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.
@ -125,10 +125,14 @@ public class ProgramLocationActionContext extends ProgramActionContext
}
private Function getFunctionForLocation() {
if (!(location instanceof FunctionLocation functionLocation)) {
return null;
if (location instanceof FunctionLocation functionLocation) {
Address functionAddress = functionLocation.getFunctionAddress();
return program.getFunctionManager().getFunctionAt(functionAddress);
}
Address functionAddress = functionLocation.getFunctionAddress();
return program.getFunctionManager().getFunctionAt(functionAddress);
Address address = getAddress();
if (address != null) {
return program.getFunctionManager().getFunctionContaining(address);
}
return null;
}
}

View File

@ -4,9 +4,9 @@
* 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.
@ -28,10 +28,10 @@ import ghidra.util.datastruct.Duo.Side;
/**
* Basic FunctionComparisonModel where a set of functions can be compared with each other
*/
public class DefaultFunctionComparisonModel extends AbstractFunctionComparisonModel {
public class AnyToAnyFunctionComparisonModel extends AbstractFunctionComparisonModel {
private Set<Function> functions = new HashSet<>();
public DefaultFunctionComparisonModel(Collection<Function> functions) {
public AnyToAnyFunctionComparisonModel(Collection<Function> functions) {
this.functions.addAll(functions);
List<Function> orderedFunctions = getOrderedFunctions();
if (orderedFunctions.size() == 1) {
@ -44,7 +44,7 @@ public class DefaultFunctionComparisonModel extends AbstractFunctionComparisonMo
}
}
public DefaultFunctionComparisonModel(Function... functions) {
public AnyToAnyFunctionComparisonModel(Function... functions) {
this(Arrays.asList(functions));
}

View File

@ -4,9 +4,9 @@
* 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.
@ -25,7 +25,7 @@ import org.junit.Test;
import generic.test.AbstractGenericTest;
import ghidra.app.services.FunctionComparisonService;
import ghidra.features.base.codecompare.model.DefaultFunctionComparisonModel;
import ghidra.features.base.codecompare.model.AnyToAnyFunctionComparisonModel;
import ghidra.features.base.codecompare.model.FunctionComparisonModelListener;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.data.ByteDataType;
@ -41,10 +41,10 @@ import ghidra.util.datastruct.Duo.Side;
* model directly.
* <ul>
* <li>The API methods being tested: {@link FunctionComparisonService}</li>
* <li>The model being used for verification: {@link DefaultFunctionComparisonModel}</li>
* <li>The model being used for verification: {@link AnyToAnyFunctionComparisonModel}</li>
* </ul>
*/
public class DefaultComparisonModelTest extends AbstractGhidraHeadedIntegrationTest {
public class AnyToAnyFunctionComparisonModelTest extends AbstractGhidraHeadedIntegrationTest {
private Program program1;
private Program program2;
@ -54,7 +54,7 @@ public class DefaultComparisonModelTest extends AbstractGhidraHeadedIntegrationT
private Function b1;
private Function b2;
private Function b3;
private DefaultFunctionComparisonModel model;
private AnyToAnyFunctionComparisonModel model;
@Before
public void setUp() throws Exception {
@ -66,7 +66,7 @@ public class DefaultComparisonModelTest extends AbstractGhidraHeadedIntegrationT
@Test
public void testSetNoFunctions() throws Exception {
model = new DefaultFunctionComparisonModel(new HashSet<>());
model = new AnyToAnyFunctionComparisonModel(new HashSet<>());
assertTrue(model.isEmpty());
assertEquals(0, model.getFunctions(LEFT).size());
assertEquals(0, model.getFunctions(RIGHT).size());
@ -77,7 +77,7 @@ public class DefaultComparisonModelTest extends AbstractGhidraHeadedIntegrationT
@Test
public void testSetOneFunctions() throws Exception {
Set<Function> set = Set.of(b1);
model = new DefaultFunctionComparisonModel(set);
model = new AnyToAnyFunctionComparisonModel(set);
assertFalse(model.isEmpty());
assertEquals(List.of(b1), model.getFunctions(LEFT));
@ -89,7 +89,7 @@ public class DefaultComparisonModelTest extends AbstractGhidraHeadedIntegrationT
@Test
public void testPairOfFunctions() throws Exception {
Set<Function> set = Set.of(b1, b2);
model = new DefaultFunctionComparisonModel(set);
model = new AnyToAnyFunctionComparisonModel(set);
assertEquals(List.of(b1, b2), model.getFunctions(LEFT));
assertEquals(List.of(b1, b2), model.getFunctions(RIGHT));
@ -235,7 +235,7 @@ public class DefaultComparisonModelTest extends AbstractGhidraHeadedIntegrationT
@Test
public void testSettingBadFunctionActive() {
Set<Function> set = Set.of(a1, b1);
model = new DefaultFunctionComparisonModel(set);
model = new AnyToAnyFunctionComparisonModel(set);
assertEquals(a1, model.getActiveFunction(LEFT));
model.setActiveFunction(LEFT, a3);
@ -280,9 +280,9 @@ public class DefaultComparisonModelTest extends AbstractGhidraHeadedIntegrationT
return builder;
}
private DefaultFunctionComparisonModel createTestModel() {
private AnyToAnyFunctionComparisonModel createTestModel() {
Set<Function> set = Set.of(b1, b2, a1, a2);
return new DefaultFunctionComparisonModel(set);
return new AnyToAnyFunctionComparisonModel(set);
}
private class TestFunctionComparisonModelListener implements FunctionComparisonModelListener {

View File

@ -4,9 +4,9 @@
* 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.
@ -39,7 +39,7 @@ import ghidra.util.datastruct.Duo.Side;
* 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>
* <li>The model being used for verification: {@link AnyToAnyFunctionComparisonModel}</li>
*/
public class MatchedFunctionComparisonModelTest extends AbstractGhidraHeadedIntegrationTest {

View File

@ -4,9 +4,9 @@
* 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.
@ -21,11 +21,12 @@ import java.util.function.Consumer;
import docking.action.builder.ActionBuilder;
import ghidra.app.CorePluginPackage;
import ghidra.app.context.FunctionSupplierContext;
import ghidra.app.context.ListingActionContext;
import ghidra.app.events.*;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.services.*;
import ghidra.features.base.codecompare.model.DefaultFunctionComparisonModel;
import ghidra.app.services.FunctionComparisonService;
import ghidra.features.base.codecompare.model.AnyToAnyFunctionComparisonModel;
import ghidra.features.base.codecompare.model.FunctionComparisonModel;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginInfo;
@ -62,9 +63,8 @@ import utility.function.Callback;
public class FunctionComparisonPlugin extends ProgramPlugin
implements DomainObjectListener, FunctionComparisonService {
// Keep a stack of recently added providers so that the "add to comparison" service methods
// can easily add to the last created provider.
private Deque<FunctionComparisonProvider> providers = new ArrayDeque<>();
private Set<FunctionComparisonProvider> providers = new HashSet<>();
private FunctionComparisonProvider lastActiveProvider;
public FunctionComparisonPlugin(PluginTool tool) {
super(tool);
@ -116,12 +116,21 @@ public class FunctionComparisonPlugin extends ProgramPlugin
void providerClosed(FunctionComparisonProvider provider) {
providers.remove(provider);
if (lastActiveProvider == provider) {
lastActiveProvider = null;
}
}
void removeFunction(Function function) {
Swing.runIfSwingOrRunLater(() -> doRemoveFunction(function));
}
void providerActivated(FunctionComparisonProvider provider) {
if (provider.supportsAddingFunctions()) {
lastActiveProvider = provider;
}
}
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);
@ -134,25 +143,60 @@ public class FunctionComparisonPlugin extends ProgramPlugin
}
private void createActions() {
new ActionBuilder("Compare Functions", getName())
.description("Create Function Comparison")
HelpLocation help = new HelpLocation("FunctionComparison", "Function_Comparison_Actions");
new ActionBuilder("Function Comparison", getName())
.popupMenuPath("Compare Function(s)")
.helpLocation(new HelpLocation("FunctionComparison", "Function_Comparison"))
.popupMenuGroup("Functions", "Z1")
.description("Adds the selected function(s) to the current comparison window.")
.helpLocation(help)
.withContext(FunctionSupplierContext.class)
.enabledWhen(c -> c.hasFunctions())
.enabledWhen(c -> !isListing(c) && c.hasFunctions())
.onAction(c -> addToComparison(c.getFunctions()))
.buildAndInstall(tool);
// same action as above, but with an extra pull right when shown in the listing
new ActionBuilder("Function Comparison (Listing)", getName())
.popupMenuPath("Function", "Compare Function(s)")
.popupMenuGroup("Functions", "Z1")
.description("Adds the selected function(s) to the current comparison window.")
.helpLocation(help)
.withContext(FunctionSupplierContext.class)
.enabledWhen(c -> isListing(c) && c.hasFunctions())
.onAction(c -> addToComparison(c.getFunctions()))
.buildAndInstall(tool);
new ActionBuilder("New Function Comparison", getName())
.popupMenuPath("Compare in New Window")
.popupMenuGroup("Functions", "Z2")
.description("Compare the selected function(s) in a new comparison window.")
.helpLocation(help)
.withContext(FunctionSupplierContext.class)
.enabledWhen(
c -> !isListing(c) && c.hasFunctions() && hasExistingComparison())
.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"))
// same action as above, but with an extra pull right when shown in the listing
new ActionBuilder("New Function Comparison (Listing)", getName())
.popupMenuPath("Function", "Compare in New Window")
.popupMenuGroup("Functions", "Z2")
.description("Compare the selected function(s) in a new comparison window.")
.helpLocation(help)
.withContext(FunctionSupplierContext.class)
.enabledWhen(c -> c.hasFunctions())
.onAction(c -> addToComparison(c.getFunctions()))
.enabledWhen(c -> isListing(c) && c.hasFunctions() && hasExistingComparison())
.onAction(c -> createComparison(c.getFunctions()))
.buildAndInstall(tool);
}
private boolean isListing(FunctionSupplierContext context) {
return context instanceof ListingActionContext;
}
private boolean hasExistingComparison() {
return lastActiveProvider != null;
}
private void doRemoveFunction(Function function) {
@ -168,20 +212,10 @@ public class FunctionComparisonPlugin extends ProgramPlugin
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);
providers.add(provider);
return provider;
}
private FunctionComparisonProvider findLastDefaultProviderModel() {
for (FunctionComparisonProvider provider : providers) {
if (provider.getModel() instanceof DefaultFunctionComparisonModel) {
return provider;
}
}
return null;
}
//==================================================================================================
// Service Methods
//==================================================================================================
@ -190,26 +224,23 @@ public class FunctionComparisonPlugin extends ProgramPlugin
if (functions.isEmpty()) {
return;
}
DefaultFunctionComparisonModel model = new DefaultFunctionComparisonModel(functions);
AnyToAnyFunctionComparisonModel model = new AnyToAnyFunctionComparisonModel(functions);
Swing.runLater(() -> createProvider(model));
}
@Override
public void createComparison(Function left, Function right) {
DefaultFunctionComparisonModel model = new DefaultFunctionComparisonModel(left, right);
AnyToAnyFunctionComparisonModel model = new AnyToAnyFunctionComparisonModel(left, right);
Swing.runLater(() -> createProvider(model));
}
@Override
public void addToComparison(Collection<Function> functions) {
FunctionComparisonProvider lastProvider = findLastDefaultProviderModel();
if (lastProvider == null) {
if (lastActiveProvider == null) {
createComparison(functions);
}
else {
DefaultFunctionComparisonModel model =
(DefaultFunctionComparisonModel) lastProvider.getModel();
Swing.runLater(() -> model.addFunctions(functions));
Swing.runLater(() -> lastActiveProvider.addFunctions(functions));
}
}

View File

@ -4,9 +4,9 @@
* 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.
@ -291,15 +291,15 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
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);
.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) {
if (model instanceof AnyToAnyFunctionComparisonModel) {
createDefaultModelActions();
}
}
@ -313,7 +313,7 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
.popupMenuGroup(ADD_COMPARISON_GROUP)
.toolBarIcon(ADD_TO_COMPARISON_ICON)
.toolBarGroup(ADD_COMPARISON_GROUP)
.enabledWhen(c -> model instanceof DefaultFunctionComparisonModel)
.enabledWhen(c -> model instanceof AnyToAnyFunctionComparisonModel)
.onAction(c -> addFunctions())
.buildAndInstallLocal(this);
@ -335,7 +335,7 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
List<Function> functions =
rows.stream().map(row -> row.getFunction()).collect(Collectors.toList());
if (model instanceof DefaultFunctionComparisonModel defaultModel) {
if (model instanceof AnyToAnyFunctionComparisonModel defaultModel) {
defaultModel.addFunctions(functions);
}
@ -388,4 +388,29 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter
closeListener = Callback.dummy();
functionComparisonPanel.dispose();
}
@Override
public void componentActivated() {
plugin.providerActivated(this);
}
/**
* Returns true if this provider is using the {@link AnyToAnyFunctionComparisonModel} which
* allows adding functions. The other model ({@link MatchedFunctionComparisonModel} ) only
* allows functions to be added in matched pairs.
* @return true if this provider supports adding functions to the comparison
*/
public boolean supportsAddingFunctions() {
return model instanceof AnyToAnyFunctionComparisonModel;
}
/**
* Adds functions to the comparison model if the model supports it.
* @param functions the functions to add to the comparison
*/
public void addFunctions(Collection<Function> functions) {
if (model instanceof AnyToAnyFunctionComparisonModel anyToAnyModel) {
anyToAnyModel.addFunctions(functions);
}
}
}