diff --git a/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/FunctionComparison.htm b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/FunctionComparison.htm index b211f66635..420ff2d108 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/FunctionComparison.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/FunctionComparison/FunctionComparison.htm @@ -8,7 +8,8 @@ -

+

Function Comparison Window

@@ -21,21 +22,18 @@

+ +

To Compare Functions, select one or more functions from the Listing, Decompiler or the + Functions Table, then + right-click and select the Compare Functions(s) action (In the Listing, it is + Function Compare Function(s)). -

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

+ +

If an existing function comparison window is + already showing, the Compare Function(s) action will add the selected functions to + the existing comparison. To get a new Function Comparison window, use the + Compare in New Window action instead.

-

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

- -
- -

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

diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramLocationActionContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramLocationActionContext.java index 0038465489..0f89d4738d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramLocationActionContext.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/context/ProgramLocationActionContext.java @@ -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; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/model/DefaultFunctionComparisonModel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/model/AnyToAnyFunctionComparisonModel.java similarity index 93% rename from Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/model/DefaultFunctionComparisonModel.java rename to Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/model/AnyToAnyFunctionComparisonModel.java index 30c84a9d44..f5d06f36f1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/model/DefaultFunctionComparisonModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/model/AnyToAnyFunctionComparisonModel.java @@ -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 functions = new HashSet<>(); - public DefaultFunctionComparisonModel(Collection functions) { + public AnyToAnyFunctionComparisonModel(Collection functions) { this.functions.addAll(functions); List 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)); } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/codecompare/model/DefaultComparisonModelTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/codecompare/model/AnyToAnyFunctionComparisonModelTest.java similarity index 93% rename from Ghidra/Features/Base/src/test/java/ghidra/features/base/codecompare/model/DefaultComparisonModelTest.java rename to Ghidra/Features/Base/src/test/java/ghidra/features/base/codecompare/model/AnyToAnyFunctionComparisonModelTest.java index 90430867d9..6c2cd6f7fe 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/features/base/codecompare/model/DefaultComparisonModelTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/codecompare/model/AnyToAnyFunctionComparisonModelTest.java @@ -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. *
    *
  • The API methods being tested: {@link FunctionComparisonService}
  • - *
  • The model being used for verification: {@link DefaultFunctionComparisonModel}
  • + *
  • The model being used for verification: {@link AnyToAnyFunctionComparisonModel}
  • *
*/ -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 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 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 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 set = Set.of(b1, b2, a1, a2); - return new DefaultFunctionComparisonModel(set); + return new AnyToAnyFunctionComparisonModel(set); } private class TestFunctionComparisonModelListener implements FunctionComparisonModelListener { diff --git a/Ghidra/Features/Base/src/test/java/ghidra/features/base/codecompare/model/MatchedFunctionComparisonModelTest.java b/Ghidra/Features/Base/src/test/java/ghidra/features/base/codecompare/model/MatchedFunctionComparisonModelTest.java index fb816a2031..8b696e0004 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/features/base/codecompare/model/MatchedFunctionComparisonModelTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/features/base/codecompare/model/MatchedFunctionComparisonModelTest.java @@ -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. *
  • The API methods being tested: {@link FunctionComparisonService}
  • - *
  • The model being used for verification: {@link DefaultFunctionComparisonModel}
  • + *
  • The model being used for verification: {@link AnyToAnyFunctionComparisonModel}
  • */ public class MatchedFunctionComparisonModelTest extends AbstractGhidraHeadedIntegrationTest { diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/plugin/FunctionComparisonPlugin.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/plugin/FunctionComparisonPlugin.java index 0ddda5906e..98959a5ca6 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/plugin/FunctionComparisonPlugin.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/plugin/FunctionComparisonPlugin.java @@ -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 providers = new ArrayDeque<>(); + private Set 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 c) { // copy needed because this may cause callbacks to remove a provider from our list List 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 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)); } } diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/plugin/FunctionComparisonProvider.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/plugin/FunctionComparisonProvider.java index 217fbf9da1..c9b23b8b0e 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/plugin/FunctionComparisonProvider.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/plugin/FunctionComparisonProvider.java @@ -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 On means to navigate to " + - "whatever function is selected in the comparison panel, when focus changes" + - " or a new function is selected.")) - .helpLocation(new HelpLocation(HELP_TOPIC, "Navigate_To_Function")) - .toolBarIcon(NAV_FUNCTION_ICON) - .onAction(c -> maybeGoToActiveFunction()) - .buildAndInstallLocal(this); + .description(HTMLUtilities.toHTML("Toggle On means to navigate to " + + "whatever function is selected in the comparison panel, when focus changes" + + " or a new function is selected.")) + .helpLocation(new HelpLocation(HELP_TOPIC, "Navigate_To_Function")) + .toolBarIcon(NAV_FUNCTION_ICON) + .onAction(c -> maybeGoToActiveFunction()) + .buildAndInstallLocal(this); - if (model instanceof DefaultFunctionComparisonModel) { + 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 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 functions) { + if (model instanceof AnyToAnyFunctionComparisonModel anyToAnyModel) { + anyToAnyModel.addFunctions(functions); + } + } }