From 3c90216365320a5babdbbe0638955401b5e93cf5 Mon Sep 17 00:00:00 2001
From: dragonmacher <48328597+dragonmacher@users.noreply.github.com>
Date: Mon, 18 Aug 2025 17:14:26 -0400
Subject: [PATCH] GP-3648 - Add Function Graph to Function Comparison display
---
.../examples/graph/SampleGraphProvider.java | 10 +-
...tor.java => BSimDBConnectTaskManager.java} | 18 +-
.../BSimPostgresDBConnectionManager.java | 17 +-
.../file/BSimH2FileDBConnectionManager.java | 14 +-
.../Base/data/ExtensionPoint.manifest | 2 +-
.../FunctionComparison/FunctionComparison.htm | 27 +-
.../listing/ExternalAddConflictPanel.java | 4 +-
.../app/merge/tool/ListingMergePanel.java | 6 +-
.../CodeBrowserClipboardProvider.java | 5 +-
.../core/codebrowser/CodeViewerProvider.java | 6 +-
.../services/FunctionComparisonService.java | 20 +-
.../field/MnemonicFieldMouseHandler.java | 12 +-
.../ProgramLocationTranslator.java | 31 +-
...el.java => ListingCodeComparisonView.java} | 24 +-
.../ListingComparisonActionContext.java | 12 +-
.../listing/ListingDiffActionManager.java | 6 +-
...r.java => ListingDisplaySynchronizer.java} | 28 +-
.../listing/ListingDisplayToggleAction.java | 6 +-
.../panel/CodeComparisonActionContext.java | 25 +-
...isonPanel.java => CodeComparisonView.java} | 29 +-
...a => CodeComparisonViewActionContext.java} | 14 +-
.../panel/CodeComparisonViewState.java | 87 +++
.../codecompare/panel/ComparisonData.java | 3 +-
.../panel/FunctionComparisonPanel.java | 288 +++++----
.../panel/FunctionComparisonState.java | 90 +++
.../ghidra/program/util/ProgramSelection.java | 1 +
.../src/main/java/ghidra/test/TestEnv.java | 10 +-
.../framework/options/SaveStateTest.java | 17 +-
Ghidra/Features/CodeCompare/build.gradle | 5 +-
.../AbstractMatchedCalleeTokensAction.java | 14 +-
.../AbstractMatchedTokensAction.java | 10 +-
...EmptySignatureFromMatchedTokensAction.java | 11 +-
...eeFunctionNameFromMatchedTokensAction.java | 11 +-
...eWithDatatypesFromMatchedTokensAction.java | 11 +-
...tyVariableTypeFromMatchedTokensAction.java | 10 +-
...pplyGlobalNameFromMatchedTokensAction.java | 15 +-
...ApplyLocalNameFromMatchedTokensAction.java | 15 +-
...lyVariableTypeFromMatchedTokensAction.java | 15 +-
.../codecompare/decompile/CDisplay.java | 10 +-
.../CompareFuncsFromMatchedTokensAction.java | 2 +-
.../DecompilerCodeComparisonOptions.java | 9 +-
...java => DecompilerCodeComparisonView.java} | 21 +-
.../DecompilerDiffViewFindAction.java | 8 +-
.../DetermineDecompilerDifferencesTask.java | 8 +-
.../DualDecompilerActionContext.java | 33 +-
.../DualDecompilerFieldPanelCoordinator.java | 35 --
...a => DualDecompilerScrollCoordinator.java} | 26 +-
.../functiongraph/FgComparisonContext.java | 54 ++
.../codecompare/functiongraph/FgDisplay.java | 535 ++++++++++++++++
.../functiongraph/FgDisplaySynchronizer.java | 57 ++
.../FunctionGraphCodeComparisonView.java | 508 +++++++++++++++
.../actions/AbstractFgAction.java | 43 ++
.../actions/FgChooseFormatAction.java | 69 +++
.../actions/FgRelayoutAction.java | 79 +++
.../actions/FgResetGraphAction.java | 62 ++
.../actions/FgTogglePopupsAction.java | 71 +++
.../actions/FgToggleSatelliteAction.java | 71 +++
.../plugin/FunctionComparisonPlugin.java | 34 +-
.../plugin/FunctionComparisonProvider.java | 68 +-
.../plugin/MultiFunctionComparisonPanel.java | 12 +-
.../decompile/AbstractDualDecompilerTest.java | 22 +-
.../decompile/DualDecompilerActionTest.java | 34 +-
.../plugin/CompareFunctionsProviderTest.java | 12 +-
.../core/functiongraph/DefaultFgEnv.java | 113 ++++
.../core/functiongraph/FGActionManager.java | 222 +++----
.../functiongraph/FGClipboardProvider.java | 8 +-
.../plugin/core/functiongraph/FGProvider.java | 160 +++--
.../functiongraph/FunctionGraphPlugin.java | 12 +-
.../IndependentColorProvider.java | 8 +-
.../SetFormatDialogComponentProvider.java | 6 +-
.../functiongraph/ToolBasedColorProvider.java | 22 +-
.../graph/FunctionGraphFactory.java | 6 +-
.../vertex/ListingGraphComponentPanel.java | 2 +-
.../SetVertexMostRecentColorAction.java | 1 -
.../mvc/DefaultFGControllerListener.java | 79 +++
.../core/functiongraph/mvc/FGController.java | 261 +++++---
.../mvc/FGControllerListener.java | 56 ++
.../plugin/core/functiongraph/mvc/FgEnv.java | 93 +++
.../AbstractFunctionGraphTest.java | 24 +-
.../FunctionGraphGroupVertices1Test.java | 2 +-
.../FunctionGraphGroupVertices2Test.java | 3 +-
.../FunctionGraphPlugin1Test.java | 16 +-
.../FunctionGraphPlugin2Test.java | 10 +-
.../functioncalls/plugin/FcgProvider.java | 3 +-
.../VTDualListingDragNDropHandler.java | 26 +-
.../vt/gui/duallisting/VTListingContext.java | 23 +-
.../gui/duallisting/VTListingNavigator.java | 6 +-
.../feature/vt/gui/plugin/VTPlugin.java | 40 +-
.../VTFunctionAssociationProvider.java | 42 +-
.../markuptable/VTMarkupItemContext.java | 25 +-
.../VTMarkupItemsTableProvider.java | 116 ++--
.../VersionTrackingPluginScreenShots.java | 4 +-
.../VTImpliedMatchCorrelatorTest.java | 10 +-
.../java/ghidra/feature/vt/gui/VTTestEnv.java | 28 +-
.../docking/menu/MultiStateDockingAction.java | 17 +-
.../widgets/dialogs/ObjectChooserDialog.java | 8 +-
....java => FieldPanelScrollCoordinator.java} | 62 +-
.../LayoutLockedFieldPanelCoordinator.java | 197 ------
...youtLockedFieldPanelScrollCoordinator.java | 220 +++++++
...ineLockedFieldPanelScrollCoordinator.java} | 24 +-
.../docking/widgets/table/GFilterTable.java | 8 +-
.../widgets/table/GTableFilterPanel.java | 10 +
.../docking/widgets/table/GTableWidget.java | 4 +
.../featurette/VgSatelliteFeaturette.java | 2 +-
.../graph/viewer/GraphPerspectiveInfo.java | 6 +-
.../ghidra/graph/viewer/VisualGraphView.java | 17 +-
.../graph/viewer/VisualGraphViewUpdater.java | 2 +-
.../graph/viewer/GraphComponentTest.java | 21 +-
.../graph/viewer/VisualGraphViewTest.java | 6 +-
.../ghidra/app/util/SymbolPathParser.java | 7 +-
.../HashedFunctionAddressCorrelation.java | 89 ++-
.../program/util/LabelFieldLocation.java | 18 +-
.../FunctionComparisonScreenShots.java | 6 +-
.../FunctionGraphPluginScreenShots.java | 7 +-
.../CompareFunctionsDecompilerViewTest.java | 23 +-
...CompareFunctionsFunctionGraphViewTest.java | 585 ++++++++++++++++++
116 files changed, 4228 insertions(+), 1350 deletions(-)
rename Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/{BSimDBConnectTaskCoordinator.java => BSimDBConnectTaskManager.java} (91%)
rename Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/{ListingCodeComparisonPanel.java => ListingCodeComparisonView.java} (96%)
rename Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/{ListingCoordinator.java => ListingDisplaySynchronizer.java} (86%)
rename Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/{CodeComparisonPanel.java => CodeComparisonView.java} (93%)
rename Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/{CodeComparisonPanelActionContext.java => CodeComparisonViewActionContext.java} (70%)
create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonViewState.java
create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/FunctionComparisonState.java
rename Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/{DecompilerCodeComparisonPanel.java => DecompilerCodeComparisonView.java} (95%)
delete mode 100644 Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DualDecompilerFieldPanelCoordinator.java
rename Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/{CodeDiffFieldPanelCoordinator.java => DualDecompilerScrollCoordinator.java} (93%)
mode change 100755 => 100644
create mode 100644 Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/FgComparisonContext.java
create mode 100644 Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/FgDisplay.java
create mode 100644 Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/FgDisplaySynchronizer.java
create mode 100644 Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/FunctionGraphCodeComparisonView.java
create mode 100644 Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/AbstractFgAction.java
create mode 100644 Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/FgChooseFormatAction.java
create mode 100644 Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/FgRelayoutAction.java
create mode 100644 Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/FgResetGraphAction.java
create mode 100644 Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/FgTogglePopupsAction.java
create mode 100644 Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/FgToggleSatelliteAction.java
create mode 100644 Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/DefaultFgEnv.java
create mode 100644 Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/DefaultFGControllerListener.java
create mode 100644 Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGControllerListener.java
create mode 100644 Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FgEnv.java
rename Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/{FieldPanelCoordinator.java => FieldPanelScrollCoordinator.java} (73%)
delete mode 100644 Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LayoutLockedFieldPanelCoordinator.java
create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LayoutLockedFieldPanelScrollCoordinator.java
rename Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/{LineLockedFieldPanelCoordinator.java => LineLockedFieldPanelScrollCoordinator.java} (92%)
create mode 100644 Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/compare/CompareFunctionsFunctionGraphViewTest.java
diff --git a/Ghidra/Extensions/sample/src/main/java/ghidra/examples/graph/SampleGraphProvider.java b/Ghidra/Extensions/sample/src/main/java/ghidra/examples/graph/SampleGraphProvider.java
index 520feae901..5ba8c43ca1 100644
--- a/Ghidra/Extensions/sample/src/main/java/ghidra/examples/graph/SampleGraphProvider.java
+++ b/Ghidra/Extensions/sample/src/main/java/ghidra/examples/graph/SampleGraphProvider.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.
@@ -30,8 +30,7 @@ import org.apache.commons.lang3.StringUtils;
import docking.ActionContext;
import docking.ComponentProvider;
-import docking.action.ToggleDockingAction;
-import docking.action.ToolBarData;
+import docking.action.*;
import docking.menu.ActionState;
import docking.menu.MultiStateDockingAction;
import docking.widgets.*;
@@ -345,7 +344,8 @@ public class SampleGraphProvider extends ComponentProviderAdapter {
private void addLayoutAction() {
MultiStateDockingAction
++ + + +Compare Matching Callees
+This action is available on matched tokens corresponding to function calls. It will open @@ -512,8 +514,31 @@
This toggles whether or not constants must be exactly the same value to be a match in the Decompiler Diff View.
++ + +The Function Graph Diff View shows a pair of Function Graphs side by + side. +
+
+ +Function Graph Code Comparison Actions
+ +Show Function Graphs Side-by-Side
+++ +This toggles the Function Graph panels between a vertical split and a horizontal split.
+
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/ExternalAddConflictPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/ExternalAddConflictPanel.java index 189b6260b6..10def3a5cc 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/ExternalAddConflictPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/listing/ExternalAddConflictPanel.java @@ -25,7 +25,7 @@ import docking.widgets.EmptyBorderButton; import docking.widgets.TitledPanel; import docking.widgets.button.GRadioButton; import docking.widgets.fieldpanel.FieldPanel; -import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; +import docking.widgets.fieldpanel.internal.FieldPanelScrollCoordinator; import docking.widgets.label.GIconLabel; import generic.theme.GIcon; import ghidra.GhidraOptions; @@ -214,7 +214,7 @@ class ExternalAddConflictPanel extends JPanel implements CodeFormatService { latestPanel.setProgram(latestProgram); myPanel.setProgram(myProgram); - new FieldPanelCoordinator( + new FieldPanelScrollCoordinator( new FieldPanel[] { latestPanel.getFieldPanel(), myPanel.getFieldPanel() }); buttonGroup = new ButtonGroup(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/tool/ListingMergePanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/tool/ListingMergePanel.java index 2af6ed829a..a04e238cfa 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/merge/tool/ListingMergePanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/merge/tool/ListingMergePanel.java @@ -27,7 +27,7 @@ import docking.widgets.EmptyBorderButton; import docking.widgets.TitledPanel; import docking.widgets.checkbox.GCheckBox; import docking.widgets.fieldpanel.FieldPanel; -import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; +import docking.widgets.fieldpanel.internal.FieldPanelScrollCoordinator; import docking.widgets.fieldpanel.support.BackgroundColorModel; import generic.theme.GIcon; import ghidra.GhidraOptions; @@ -66,7 +66,7 @@ public class ListingMergePanel extends JPanel private JComponent bottomComp; protected TitledPanel[] titlePanels; private ListingPanel[] listingPanels; - private FieldPanelCoordinator coordinator; + private FieldPanelScrollCoordinator coordinator; private FormatManager formatMgr; private MultiListingLayoutModel multiModel; private Program[] programs = new Program[4]; @@ -120,7 +120,7 @@ public class ListingMergePanel extends JPanel } backgroundColorModel.addChangeListener(backgroundChangeListener); - coordinator = new FieldPanelCoordinator(fieldPanels); + coordinator = new FieldPanelScrollCoordinator(fieldPanels); titlePanels[RESULT].addTitleComponent(new ShowHeaderButton()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clipboard/CodeBrowserClipboardProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clipboard/CodeBrowserClipboardProvider.java index cdc2085986..316339e361 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clipboard/CodeBrowserClipboardProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/clipboard/CodeBrowserClipboardProvider.java @@ -130,9 +130,9 @@ public class CodeBrowserClipboardProvider extends ByteCopier private String stringContent; private boolean includeQuotesForStringData; - public CodeBrowserClipboardProvider(PluginTool tool, ComponentProvider codeViewerProvider) { + public CodeBrowserClipboardProvider(PluginTool tool, ComponentProvider componentProvider) { this.tool = tool; - this.componentProvider = codeViewerProvider; + this.componentProvider = componentProvider; PAINT_CONTEXT.setTextCopying(true); @@ -140,7 +140,6 @@ public class CodeBrowserClipboardProvider extends ByteCopier includeQuotesForStringData = !options.getBoolean(ClipboardPlugin.REMOVE_QUOTES_OPTION, false); options.addOptionsChangeListener(this); - } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java index ccbd7ba7b1..1189d21ea9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/CodeViewerProvider.java @@ -36,7 +36,7 @@ import docking.dnd.*; import docking.widgets.EventTrigger; import docking.widgets.fieldpanel.FieldPanel; import docking.widgets.fieldpanel.HoverHandler; -import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; +import docking.widgets.fieldpanel.internal.FieldPanelScrollCoordinator; import docking.widgets.fieldpanel.support.*; import docking.widgets.tab.GTabPanel; import generic.theme.GIcon; @@ -105,7 +105,7 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter private ListingPanel otherPanel; private CoordinatedListingPanelListener coordinatedListingPanelListener; private FormatManager formatMgr; - private FieldPanelCoordinator coordinator; + private FieldPanelScrollCoordinator coordinator; private ProgramSelectionListener liveProgramSelectionListener = (selection, trigger) -> { liveSelection = selection; updateSubTitle(); @@ -753,7 +753,7 @@ public class CodeViewerProvider extends NavigatableComponentProviderAdapter ListingModel otherAlignedModel = multiModel.getAlignedModel(1); listingPanel.setListingModel(myAlignedModel); lp.setListingModel(otherAlignedModel); - coordinator = new FieldPanelCoordinator( + coordinator = new FieldPanelScrollCoordinator( new FieldPanel[] { listingPanel.getFieldPanel(), lp.getFieldPanel() }); addHoverServices(otherPanel); HoverHandler hoverHandler = listingPanel.getFieldPanel().getHoverHandler(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonService.java index ae1b319f2f..b292da0eff 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/FunctionComparisonService.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. @@ -19,6 +19,7 @@ import java.util.Collection; import ghidra.features.base.codecompare.model.FunctionComparisonModel; import ghidra.features.base.codecompare.model.MatchedFunctionComparisonModel; +import ghidra.features.base.codecompare.panel.FunctionComparisonPanel; import ghidra.program.model.listing.Function; import utility.function.Callback; @@ -84,6 +85,17 @@ public interface FunctionComparisonService { * @param closeListener an optional callback if the client wants to be notified when the * associated function comparison windows is closed. */ - public void createCustomComparison(FunctionComparisonModel model, - Callback closeListener); + public void createCustomComparison(FunctionComparisonModel model, Callback closeListener); + + /** + * Creates a new comparison view that the caller can install into their UI. This is in contrast + * with {@link #createCustomComparison(FunctionComparisonModel, Callback)}, which will install + * the new comparison into an existing UI. + *+ * Note: clients are responsible for calling {@link FunctionComparisonPanel#dispose()} when done + * using the panel. + * + * @return the new panel + */ + public FunctionComparisonPanel createComparisonViewer(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/MnemonicFieldMouseHandler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/MnemonicFieldMouseHandler.java index c29958c661..96630f6f24 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/MnemonicFieldMouseHandler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/MnemonicFieldMouseHandler.java @@ -20,7 +20,6 @@ import java.util.ArrayList; import java.util.List; import ghidra.app.nav.Navigatable; -import ghidra.app.nav.NavigationUtils; import ghidra.app.plugin.core.navigation.locationreferences.ReferenceUtils; import ghidra.app.services.GoToService; import ghidra.app.services.ProgramManager; @@ -54,10 +53,11 @@ public class MnemonicFieldMouseHandler implements FieldMouseHandlerExtension { Program program = programManager.getCurrentProgram(); Listing listing = program.getListing(); CodeUnit codeUnit = listing.getCodeUnitAt(location.getAddress()); - return checkMemReferences(codeUnit, serviceProvider); + return checkMemReferences(codeUnit, sourceNavigatable, serviceProvider); } - private boolean checkMemReferences(CodeUnit codeUnit, ServiceProvider serviceProvider) { + private boolean checkMemReferences(CodeUnit codeUnit, Navigatable navigatable, + ServiceProvider serviceProvider) { if (codeUnit == null) { return false; @@ -77,8 +77,8 @@ public class MnemonicFieldMouseHandler implements FieldMouseHandlerExtension { TableService service = serviceProvider.getService(TableService.class); if (service != null) { - Navigatable nav = NavigationUtils.getActiveNavigatable(); - service.showTable("Mnemonic References", "Mnemonic", model, "References", nav); + service.showTable("Mnemonic References", "Mnemonic", model, "References", + navigatable); return true; } } @@ -96,7 +96,7 @@ public class MnemonicFieldMouseHandler implements FieldMouseHandlerExtension { GoToService goToService = serviceProvider.getService(GoToService.class); if (goToService != null) { - return goToService.goTo(loc); + return goToService.goTo(navigatable, loc, navigatable.getProgram()); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ProgramLocationTranslator.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ProgramLocationTranslator.java index 69da99ff3f..7b32026cb2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ProgramLocationTranslator.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ProgramLocationTranslator.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. @@ -83,9 +83,15 @@ public class ProgramLocationTranslator { } } - // Adjust symbol path for labels if it is part of the location. - adjustSymbolPath(saveState, otherSideAddress, address, byteAddress, desiredByteAddress, - otherSideLocation.getProgram(), program); + String[] symbolPathArray = saveState.getStrings("_SYMBOL_PATH", new String[0]); + if (symbolPathArray.length != 0) { + // Adjust symbol path for labels if it is part of the location. + boolean hasSymbol = adjustSymbolPath(saveState, otherSideAddress, address, byteAddress, + desiredByteAddress, otherSideLocation.getProgram(), program); + if (!hasSymbol) { + return new ProgramLocation(program, desiredByteAddress); + } + } // ref address can't be used with indicated side so remove it. saveState.remove("_REF_ADDRESS"); @@ -186,32 +192,28 @@ public class ProgramLocationTranslator { return ProgramLocation.getLocation(correlator.getProgram(side), saveState); } - private void adjustSymbolPath(SaveState saveState, Address address, Address desiredAddress, + private boolean adjustSymbolPath(SaveState saveState, Address address, Address desiredAddress, Address byteAddress, Address desiredByteAddress, Program program, Program desiredProgram) { - String[] symbolPathArray = saveState.getStrings("_SYMBOL_PATH", new String[0]); saveState.remove("_SYMBOL_PATH"); - if (symbolPathArray.length == 0) { - return; // save state has no labels for program location. - } + Address symbolAddress = (byteAddress != null) ? byteAddress : address; Address desiredSymbolAddress = (desiredByteAddress != null) ? desiredByteAddress : desiredAddress; if (symbolAddress == null || desiredSymbolAddress == null) { - return; // no address match. + return false; // no address match. } Symbol[] symbols = program.getSymbolTable().getSymbols(symbolAddress); if (symbols.length == 0) { - return; // no symbols in program for matching. + return false; // no symbols in program for matching. } Symbol[] desiredSymbols = desiredProgram.getSymbolTable().getSymbols(desiredSymbolAddress); if (desiredSymbols.length == 0) { - return; // no symbols in desiredProgram for matching. + return false; // no symbols in desiredProgram for matching. } int desiredRow = adjustSymbolRow(saveState, symbols, desiredSymbols); - int desiredIndex = getDesiredSymbolIndex(desiredSymbols, desiredRow); // Now get the desired symbol. @@ -219,6 +221,7 @@ public class ProgramLocationTranslator { SymbolPath symbolPath = getSymbolPath(desiredSymbol); // Set symbol path for desiredProgram in the save state. saveState.putStrings("_SYMBOL_PATH", symbolPath.asArray()); + return true; } private int adjustSymbolRow(SaveState saveState, Symbol[] symbols, Symbol[] desiredSymbols) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingCodeComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingCodeComparisonView.java similarity index 96% rename from Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingCodeComparisonPanel.java rename to Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingCodeComparisonView.java index 9f131435f8..090a4fceac 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingCodeComparisonPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingCodeComparisonView.java @@ -37,8 +37,8 @@ import ghidra.app.plugin.core.functioncompare.actions.*; import ghidra.app.util.ListingHighlightProvider; import ghidra.app.util.viewer.format.*; import ghidra.app.util.viewer.listingpanel.*; -import ghidra.features.base.codecompare.panel.CodeComparisonPanel; -import ghidra.features.base.codecompare.panel.CodeComparisonPanelActionContext; +import ghidra.features.base.codecompare.panel.CodeComparisonViewActionContext; +import ghidra.features.base.codecompare.panel.CodeComparisonView; import ghidra.framework.options.*; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.*; @@ -57,12 +57,10 @@ import ghidra.util.task.TaskMonitor; import help.Help; /** - * Panel that displays two listings for comparison. + * UI that displays two listings for comparison. */ - -public class ListingCodeComparisonPanel - extends CodeComparisonPanel implements - FormatModelListener, OptionsChangeListener { +public class ListingCodeComparisonView + extends CodeComparisonView implements FormatModelListener, OptionsChangeListener { public static final String NAME = "Listing View"; private static final String DIFF_NAVIGATE_GROUP = "A2_DiffNavigate"; @@ -87,7 +85,7 @@ public class ListingCodeComparisonPanel private ListingAddressCorrelation addressCorrelator; private ListingDiff listingDiff; - private ListingCoordinator coordinator; + private ListingDisplaySynchronizer coordinator; private boolean listingsLocked; private ListingDiffActionManager diffActionManager; @@ -107,7 +105,7 @@ public class ListingCodeComparisonPanel * @param owner the owner of this panel * @param tool the tool displaying this panel */ - public ListingCodeComparisonPanel(String owner, PluginTool tool) { + public ListingCodeComparisonView(String owner, PluginTool tool) { super(owner, tool); Help.getHelpService().registerHelp(this, new HelpLocation(HELP_TOPIC, "Listing_View")); initializeOptions(); @@ -450,7 +448,7 @@ public class ListingCodeComparisonPanel .description("Show the tool options for the Listing Code Comparison.") .popupMenuPath("Properties") .helpLocation(new HelpLocation(HELP_TOPIC, "Listing_Code_Comparison_Options")) - .validContextWhen(c -> isValidPanelContext(c)) + .validWhen(c -> isValidPanelContext(c)) .enabledWhen(c -> isShowing() && listingDiff.hasCorrelation()) .onAction(c -> showOptionsDialog()) .build(); @@ -497,10 +495,10 @@ public class ListingCodeComparisonPanel } private boolean isValidPanelContext(ActionContext context) { - if (!(context instanceof CodeComparisonPanelActionContext comparisonContext)) { + if (!(context instanceof CodeComparisonViewActionContext comparisonContext)) { return false; } - CodeComparisonPanel comparisonPanel = comparisonContext.getCodeComparisonPanel(); + CodeComparisonView comparisonPanel = comparisonContext.getCodeComparisonView(); return comparisonPanel == this; } @@ -521,7 +519,7 @@ public class ListingCodeComparisonPanel coordinator = null; } if (listingsLocked) { - coordinator = new ListingCoordinator(displays, addressCorrelator); + coordinator = new ListingDisplaySynchronizer(displays, addressCorrelator); coordinator.sync(activeSide); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingComparisonActionContext.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingComparisonActionContext.java index 9633f05a47..405edf8a3f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingComparisonActionContext.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingComparisonActionContext.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. @@ -23,24 +23,24 @@ import ghidra.features.base.codecompare.panel.CodeComparisonActionContext; */ public class ListingComparisonActionContext extends CodeComparisonActionContext { - private ListingCodeComparisonPanel codeComparisonPanel = null; + private ListingCodeComparisonView codeComparisonPanel = null; /** * Constructor for a dual listing's action context. * @param provider the provider that uses this action context. * @param panel the ListingCodeComparisonPanel that generated this context */ - public ListingComparisonActionContext(ComponentProvider provider, ListingCodeComparisonPanel panel) { + public ListingComparisonActionContext(ComponentProvider provider, ListingCodeComparisonView panel) { super(provider, panel, panel.getActiveListingPanel().getFieldPanel()); this.codeComparisonPanel = panel; } /** - * Returns the {@link ListingCodeComparisonPanel} that generated this context + * Returns the {@link ListingCodeComparisonView} that generated this context * @return the listing comparison panel that generated this context */ @Override - public ListingCodeComparisonPanel getCodeComparisonPanel() { + public ListingCodeComparisonView getCodeComparisonView() { return codeComparisonPanel; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingDiffActionManager.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingDiffActionManager.java index 4601d2590b..db1b0682fb 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingDiffActionManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingDiffActionManager.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. @@ -151,7 +151,7 @@ public class ListingDiffActionManager { ToggleIgnoreRegisterNamesAction() { super("Toggle Ignore Register Names", "DualListing"); setDescription(HTMLUtilities.toHTML( - "If selected, difference highlights should\n" + "ignore operand Registers.")); + "If selected, difference highlights should\nignore operand Registers.")); setEnabled(true); setPopupMenuData(new MenuData( new String[] { "Ignore Operand Registers As Differences" }, diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingCoordinator.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingDisplaySynchronizer.java similarity index 86% rename from Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingCoordinator.java rename to Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingDisplaySynchronizer.java index a632cba401..85a4cb8058 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingCoordinator.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingDisplaySynchronizer.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. @@ -20,8 +20,8 @@ import static ghidra.util.datastruct.Duo.Side.*; import java.math.BigInteger; import docking.widgets.fieldpanel.FieldPanel; -import docking.widgets.fieldpanel.internal.LayoutLockedFieldPanelCoordinator; -import docking.widgets.fieldpanel.internal.LineLockedFieldPanelCoordinator; +import docking.widgets.fieldpanel.internal.LayoutLockedFieldPanelScrollCoordinator; +import docking.widgets.fieldpanel.internal.LineLockedFieldPanelScrollCoordinator; import docking.widgets.fieldpanel.support.ViewerPosition; import ghidra.app.util.viewer.listingpanel.ProgramLocationTranslator; import ghidra.app.util.viewer.util.AddressIndexMap; @@ -34,23 +34,23 @@ import ghidra.util.datastruct.Duo.Side; /** * Keeps two listing panels synchronized, both the view and cursor location */ -public class ListingCoordinator { +class ListingDisplaySynchronizer { private Duo
displays; - private Duo lockLineAddresses = new Duo<>(); private ProgramLocationTranslator locationTranslator; - private LineLockedFieldPanelCoordinator viewCoordinator; + private LineLockedFieldPanelScrollCoordinator viewCoordinator; - ListingCoordinator(Duo displays, ListingAddressCorrelation correlator) { + ListingDisplaySynchronizer(Duo displays, + ListingAddressCorrelation correlation) { this.displays = displays; - this.locationTranslator = new ProgramLocationTranslator(correlator); + this.locationTranslator = new ProgramLocationTranslator(correlation); FieldPanel left = displays.get(LEFT).getListingPanel().getFieldPanel(); FieldPanel right = displays.get(RIGHT).getListingPanel().getFieldPanel(); - viewCoordinator = new LayoutLockedFieldPanelCoordinator(left, right); + viewCoordinator = new LayoutLockedFieldPanelScrollCoordinator(left, right); } /** - * notification that the given side change to the given location + * Notification that the given side change to the given location * @param side the side that changed * @param location the location from the given side */ @@ -63,9 +63,7 @@ public class ListingCoordinator { if (otherLocation != null) { updateViewCoordinator(side, location, otherLocation); displays.get(otherSide).goTo(otherLocation); - displays.get(side.otherSide()).updateCursorMarkers(otherLocation); } - } void dispose() { @@ -73,7 +71,7 @@ public class ListingCoordinator { } /** - * synchronized the two listings using the given side as the source + * Synchronize the two listings using the given side as the source * @param side to synchronize from */ void sync(Side side) { @@ -109,7 +107,7 @@ public class ListingCoordinator { if (leftAddress == null || rightAddress == null) { return; } - lockLineAddresses = new Duo<>(leftAddress, rightAddress); + AddressIndexMap leftMap = displays.get(LEFT).getListingPanel().getAddressIndexMap(); AddressIndexMap rightMap = displays.get(RIGHT).getListingPanel().getAddressIndexMap(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingDisplayToggleAction.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingDisplayToggleAction.java index 912d1679ef..39604de76b 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingDisplayToggleAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/listing/ListingDisplayToggleAction.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. @@ -46,7 +46,7 @@ abstract class ListingDisplayToggleAction extends ToggleDockingAction { @Override public boolean isAddToPopup(ActionContext context) { Object contextObject = context.getContextObject(); - if (contextObject instanceof ListingCodeComparisonPanel) { + if (contextObject instanceof ListingCodeComparisonView) { Object sourceObject = context.getSourceObject(); return sourceObject instanceof FieldPanel; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonActionContext.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonActionContext.java index 8e7e77ceb7..29a5170467 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonActionContext.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonActionContext.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. @@ -23,19 +23,20 @@ import ghidra.program.model.listing.Function; import ghidra.util.datastruct.Duo.Side; public abstract class CodeComparisonActionContext extends DefaultActionContext - implements CodeComparisonPanelActionContext { - private CodeComparisonPanel comparisonPanel; + implements CodeComparisonViewActionContext { + private CodeComparisonView comparisonProvider; /** * Constructor * @param provider the ComponentProvider containing the code comparison panel - * @param panel the CodeComparisonPanel that generated this context + * @param comparisonProvider the provider that generated this context * @param component the focusable component for associated with the comparison panel */ - public CodeComparisonActionContext(ComponentProvider provider, CodeComparisonPanel panel, + public CodeComparisonActionContext(ComponentProvider provider, + CodeComparisonView comparisonProvider, Component component) { - super(provider, panel, component); - this.comparisonPanel = panel; + super(provider, comparisonProvider, component); + this.comparisonProvider = comparisonProvider; } /** @@ -44,8 +45,8 @@ public abstract class CodeComparisonActionContext extends DefaultActionContext * @return the function to get information from */ public Function getSourceFunction() { - Side activeSide = comparisonPanel.getActiveSide(); - return comparisonPanel.getFunction(activeSide.otherSide()); + Side activeSide = comparisonProvider.getActiveSide(); + return comparisonProvider.getFunction(activeSide.otherSide()); } /** @@ -54,7 +55,7 @@ public abstract class CodeComparisonActionContext extends DefaultActionContext * @return the function to apply information to */ public Function getTargetFunction() { - Side activeSide = comparisonPanel.getActiveSide(); - return comparisonPanel.getFunction(activeSide); + Side activeSide = comparisonProvider.getActiveSide(); + return comparisonProvider.getFunction(activeSide); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonView.java similarity index 93% rename from Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonPanel.java rename to Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonView.java index 3a0da7cfe8..d70be061bd 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonView.java @@ -31,6 +31,7 @@ import docking.ComponentProvider; import docking.action.*; import docking.widgets.TitledPanel; import generic.theme.GThemeDefaults.Colors.Palette; +import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.AddressSetView; import ghidra.program.model.listing.Function; @@ -41,17 +42,19 @@ import ghidra.util.classfinder.ClassSearcher; import ghidra.util.classfinder.ExtensionPoint; import ghidra.util.datastruct.Duo; import ghidra.util.datastruct.Duo.Side; +import utility.function.Callback; /** - * The CodeComparisonPanel class should be extended by any class that is to be + * The {@link CodeComparisonView} class should be extended by any class that is to be * discovered by the {@link FunctionComparisonPanel} class and included as a * form of comparing two sections of code within the same or different programs * - * NOTE: ALL CodeComparisonPanel CLASSES MUST END IN - *
CodeComparisonPanel
so they are discoverable by the {@link ClassSearcher} + * NOTE: ALL CodeComparisonView CLASSES MUST END IN + *CodeComparisonView
so they are discoverable by the {@link ClassSearcher} */ -public abstract class CodeComparisonPanel extends JPanel +public abstract class CodeComparisonView extends JPanel implements ExtensionPoint { + public static final String HELP_TOPIC = "FunctionComparison"; private static final Color ACTIVE_BORDER_COLOR = Palette.getColor("lightpink"); private static final int MINIMUM_PANEL_WIDTH = 50; @@ -71,6 +74,7 @@ public abstract class CodeComparisonPanel extends JPanel private ToggleOrientationAction toggleOrientationAction; private JComponent northComponent; private boolean showTitles = true; + private Callback orientationChangedCallback = Callback.dummy(); /** * Constructor @@ -78,7 +82,7 @@ public abstract class CodeComparisonPanel extends JPanel * @param owner the name of the owner of this component * @param tool the tool that contains the component */ - protected CodeComparisonPanel(String owner, PluginTool tool) { + protected CodeComparisonView(String owner, PluginTool tool) { this.owner = owner; this.tool = tool; toggleOrientationAction = new ToggleOrientationAction(getName()); @@ -91,6 +95,10 @@ public abstract class CodeComparisonPanel extends JPanel return tool; } + public void setSaveState(SaveState saveState) { + // for subclasses + } + /** * Displays a comparison of two ComparisonData objects * @@ -163,7 +171,7 @@ public abstract class CodeComparisonPanel extends JPanel public abstract void dispose(); /** - * Returns the context object which corresponds to the area of focus within this provider's + * Returns the context object which corresponds to the area of focus within this view's * component. Null is returned when there is no context. * @param componentProvider the provider that includes this code comparison component. * @param event mouse event which corresponds to this request. @@ -296,6 +304,12 @@ public abstract class CodeComparisonPanel extends JPanel : JSplitPane.VERTICAL_SPLIT; splitPane.setOrientation(orientation); splitPane.setDividerLocation(0.5); + + orientationChangedCallback.call(); + } + + public void setOrientationChangedCallback(Callback callback) { + this.orientationChangedCallback = Callback.dummyIfNull(callback); } private void setTitle(TitledPanel titlePanel, String titlePrefix, String title) { @@ -343,7 +357,7 @@ public abstract class CodeComparisonPanel extends JPanel setActiveSide(LEFT); } - private void addMouseAndFocusListeners(Side side) { + protected void addMouseAndFocusListeners(Side side) { JComponent comp = getComparisonComponent(side); comp.addFocusListener(new FocusAdapter() { @Override @@ -374,6 +388,7 @@ public abstract class CodeComparisonPanel extends JPanel } private void addMouseListenerRecursively(Component component, MouseListener listener) { + component.removeMouseListener(listener); component.addMouseListener(listener); if (component instanceof Container container) { for (int i = 0; i < container.getComponentCount(); i++) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonPanelActionContext.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonViewActionContext.java similarity index 70% rename from Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonPanelActionContext.java rename to Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonViewActionContext.java index b923b851fd..8e7b879ef7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonPanelActionContext.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonViewActionContext.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. @@ -16,14 +16,14 @@ package ghidra.features.base.codecompare.panel; /** - * Action context for a CodeComparisonPanel. + * Action context for a {@link CodeComparisonView}. */ -public interface CodeComparisonPanelActionContext { +public interface CodeComparisonViewActionContext { /** - * Gets the CodeComparisonPanel associated with this context. - * @return the code comparison panel. + * Gets the view associated with this context. + * @return the code comparison provider. */ - public abstract CodeComparisonPanel getCodeComparisonPanel(); + public abstract CodeComparisonView getCodeComparisonView(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonViewState.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonViewState.java new file mode 100644 index 0000000000..9c64c5b3f6 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/CodeComparisonViewState.java @@ -0,0 +1,87 @@ +/* ### + * 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.features.base.codecompare.panel; + +import java.util.*; +import java.util.Map.Entry; + +import ghidra.framework.options.SaveState; +import ghidra.framework.plugintool.PluginTool; + +/** + * A state object to save settings each type of comparison view known by the system. This class + * is meant to be used to allow user settings to be applied to each new comparison widget that is + * created. Also, the class allows the tool to save those settings when the tool is saved. + *+ * When a comparison provider updates its save state object, it should call + * {@link PluginTool#setConfigChanged(boolean)} so that tool knows there are settings to be saved. + */ +public class CodeComparisonViewState { + + private static final String FUNCTION_COMPARISON_STATES = "CodeComparisonStates"; + + private Map
, SaveState> states = new HashMap<>(); + + public SaveState getSaveState(Class extends CodeComparisonView> clazz) { + return states.computeIfAbsent(clazz, this::createSaveState); + } + + private SaveState createSaveState(Class extends CodeComparisonView> clazz) { + return new SaveState(); + } + + /** + * Called by the tool to write the panels' saved states into the tools save state + * @param saveState the tool's save state + */ + public void writeConfigState(SaveState saveState) { + Set , SaveState>> entries = states.entrySet(); + SaveState classStates = new SaveState(); + for (Entry , SaveState> entry : entries) { + Class extends CodeComparisonView> clazz = entry.getKey(); + SaveState subState = entry.getValue(); + classStates.putSaveState(clazz.getName(), subState); + } + + saveState.putSaveState(FUNCTION_COMPARISON_STATES, classStates); + } + + /** + * Called by the tool to load saved state for the comparison providers + * @param saveState the tool's state + */ + public void readConfigState(SaveState saveState) { + + SaveState classStates = saveState.getSaveState(FUNCTION_COMPARISON_STATES); + if (classStates == null) { + return; + } + + String[] names = classStates.getNames(); + for (String className : names) { + try { + @SuppressWarnings("unchecked") + Class extends CodeComparisonView> clazz = + (Class extends CodeComparisonView>) Class.forName(className); + SaveState classState = classStates.getSaveState(className); + states.put(clazz, classState); + } + catch (ClassNotFoundException e) { + // ignore + } + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/ComparisonData.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/ComparisonData.java index e5c705e35a..86f82ae484 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/ComparisonData.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/ComparisonData.java @@ -24,7 +24,7 @@ import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramLocation; /** - * ComparisonData is an abstract of items that can be compared in a {@link CodeComparisonPanel}. + * ComparisonData is an abstraction of items that can be compared in a {@link CodeComparisonView}. * Not all comparison panels can handle all types of comparison data. For example, the decompiler * comparison only works when the comparison data is a function. */ @@ -71,6 +71,7 @@ public interface ComparisonData { /** * Returns the initial program location to put the cursor when the panel is first displayed + * @return the location */ public ProgramLocation getInitialLocation(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/FunctionComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/FunctionComparisonPanel.java index b68d53e465..bef7de2281 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/FunctionComparisonPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/FunctionComparisonPanel.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,7 +21,6 @@ import static ghidra.util.datastruct.Duo.Side.*; import java.awt.*; import java.awt.event.MouseEvent; import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.List; @@ -34,10 +33,9 @@ import docking.ComponentProvider; import docking.action.*; import docking.widgets.tabbedpane.DockingTabRenderer; import generic.theme.GIcon; -import ghidra.features.base.codecompare.listing.ListingCodeComparisonPanel; +import ghidra.features.base.codecompare.listing.ListingCodeComparisonView; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.PluginTool; -import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSetView; import ghidra.program.model.listing.*; import ghidra.util.HelpLocation; @@ -48,18 +46,16 @@ import help.Help; import help.HelpService; /** - * A panel for displaying {@link Function functions}, {@link Data data}, or - * {@link AddressSet address sets} side-by-side for comparison purposes + * A panel for displaying {@link Function functions} side-by-side for comparison purposes */ public class FunctionComparisonPanel extends JPanel implements ChangeListener { private static final String ORIENTATION_PROPERTY_NAME = "ORIENTATION"; - private static final String DEFAULT_CODE_COMPARISON_VIEW = ListingCodeComparisonPanel.NAME; + private static final String DEFAULT_CODE_COMPARISON_VIEW = ListingCodeComparisonView.NAME; private static final String COMPARISON_VIEW_DISPLAYED = "COMPARISON_VIEW_DISPLAYED"; private static final String CODE_COMPARISON_LOCK_SCROLLING_TOGETHER = "CODE_COMPARISON_LOCK_SCROLLING_TOGETHER"; - private static final HelpService help = Help.getHelpService(); private static final String HELP_TOPIC = "FunctionComparison"; private static final Icon SYNC_SCROLLING_ICON = @@ -72,23 +68,40 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { private JTabbedPane tabbedPane; private Map tabNameToComponentMap; - private List codeComparisonPanels; + private List codeComparisonViews; private ToggleScrollLockAction toggleScrollLockAction; private boolean syncScrolling = false; private Duo comparisonData = new Duo (); - public FunctionComparisonPanel(PluginTool tool, String owner) { - this.comparisonData = new Duo<>(EMPTY, EMPTY); + private FunctionComparisonState state; - codeComparisonPanels = getCodeComparisonPanels(tool, owner); + /** + * Constructor + * @param tool the tool + * @param owner the owner's name + * @param state the comparison save state + */ + public FunctionComparisonPanel(PluginTool tool, String owner, FunctionComparisonState state) { + this.comparisonData = new Duo<>(EMPTY, EMPTY); + this.state = state; + + state.addUpdateCallback(this::comparisonStateUpdated); + + codeComparisonViews = getCodeComparisonViews(tool, owner); tabNameToComponentMap = new HashMap<>(); createMainPanel(); createActions(owner); setScrollingSyncState(true); + HelpService help = Help.getHelpService(); help.registerHelp(this, new HelpLocation(HELP_TOPIC, "Function Comparison")); } + private void comparisonStateUpdated() { + readPanelState(); + readViewState(); + } + /** * Load the given functions into the views of this panel * @@ -118,9 +131,9 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { public void loadComparisons(ComparisonData left, ComparisonData right) { comparisonData = new Duo<>(left, right); - CodeComparisonPanel activePanel = getActiveComparisonPanel(); - if (activePanel != null) { - activePanel.loadComparisons(left, right); + CodeComparisonView activeView = getActiveComparisonView(); + if (activeView != null) { + activeView.loadComparisons(left, right); } } @@ -173,9 +186,9 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { // Setting the addresses to be displayed to null effectively clears // the display - CodeComparisonPanel activePanel = getActiveComparisonPanel(); - if (activePanel != null) { - activePanel.clearComparisons(); + CodeComparisonView activeView = getActiveComparisonView(); + if (activeView != null) { + activeView.clearComparisons(); } } @@ -190,15 +203,15 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { } /** - * Gets the ListingCodeComparisonPanel being displayed by this panel + * Gets the ListingCodeComparisonView being displayed by this panel * if one exists * * @return the comparison panel or null */ - public ListingCodeComparisonPanel getDualListingPanel() { - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - if (codeComparisonPanel instanceof ListingCodeComparisonPanel listingPanel) { - return listingPanel; + public ListingCodeComparisonView getDualListingView() { + for (CodeComparisonView view : codeComparisonViews) { + if (view instanceof ListingCodeComparisonView listingView) { + return listingView; } } return null; @@ -207,13 +220,14 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { @Override public void stateChanged(ChangeEvent e) { tabChanged(); + writeTabState(); } /** * Set the current tabbed panel to be the component with the given name * * @param name name of view to set as the current tab - * @return true if the named view was found in the provider map + * @return true if the named view was found in the view map */ public boolean setCurrentTabbedComponent(String name) { @@ -255,26 +269,34 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { tabbedPane.removeAll(); setVisible(false); - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - codeComparisonPanel.dispose(); + for (CodeComparisonView view : codeComparisonViews) { + view.dispose(); } } public void programClosed(Program program) { - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - codeComparisonPanel.programClosed(program); + for (CodeComparisonView view : codeComparisonViews) { + view.programClosed(program); } } - public CodeComparisonPanel getCodeComparisonPanelByName(String name) { - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - if (name.equals(codeComparisonPanel.getName())) { - return codeComparisonPanel; + public CodeComparisonView getCodeComparisonView(String name) { + for (CodeComparisonView view : codeComparisonViews) { + if (name.equals(view.getName())) { + return view; } } return null; } + public void selectComparisonView(String name) { + for (CodeComparisonView view : codeComparisonViews) { + if (name.equals(view.getName())) { + tabbedPane.setSelectedComponent(view); + } + } + } + /** * Create the main tabbed panel */ @@ -287,22 +309,21 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { add(tabbedPane, BorderLayout.CENTER); setPreferredSize(new Dimension(200, 300)); - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - tabbedPane.add(codeComparisonPanel.getName(), codeComparisonPanel); - tabNameToComponentMap.put(codeComparisonPanel.getName(), codeComparisonPanel); + for (CodeComparisonView view : codeComparisonViews) { + tabbedPane.add(view.getName(), view); + tabNameToComponentMap.put(view.getName(), view); } } /** - * Invoked when there is a tab change. This loads the active tab with - * the appropriate data to be compared. + * Invoked when there is a tab change. This loads the active tab with the data to be compared. */ private void tabChanged() { - CodeComparisonPanel activePanel = getActiveComparisonPanel(); - if (activePanel == null) { + CodeComparisonView activeView = getActiveComparisonView(); + if (activeView == null) { return; // initializing } - activePanel.loadComparisons(comparisonData.get(LEFT), comparisonData.get(RIGHT)); + activeView.loadComparisons(comparisonData.get(LEFT), comparisonData.get(RIGHT)); } /** @@ -311,70 +332,73 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { * @return the currently selected comparison panel, or null if nothing * selected */ - private CodeComparisonPanel getActiveComparisonPanel() { - return (CodeComparisonPanel) tabbedPane.getSelectedComponent(); + private CodeComparisonView getActiveComparisonView() { + return (CodeComparisonView) tabbedPane.getSelectedComponent(); } - /** - * Sets up the FunctionComparisonPanel and which CodeComparisonPanel is currently - * displayed based on the specified saveState - * - * @param prefix identifier to prepend to any save state names to make them unique - * @param saveState the save state for retrieving information - */ - public void readConfigState(String prefix, SaveState saveState) { + private void readViewState() { + CodeComparisonViewState viewState = state.getViewState(); + codeComparisonViews.forEach(v -> { + Class extends CodeComparisonView> viewClass = v.getClass(); + SaveState saveState = viewState.getSaveState(viewClass); + v.setSaveState(saveState); + }); + } + + private void readPanelState() { + + SaveState panelState = state.getPanelState(); String currentTabView = - saveState.getString(prefix + COMPARISON_VIEW_DISPLAYED, DEFAULT_CODE_COMPARISON_VIEW); + panelState.getString(COMPARISON_VIEW_DISPLAYED, DEFAULT_CODE_COMPARISON_VIEW); setCurrentTabbedComponent(currentTabView); setScrollingSyncState( - saveState.getBoolean(prefix + CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, true)); + panelState.getBoolean(CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, true)); - for (CodeComparisonPanel panel : codeComparisonPanels) { - String key = prefix + panel.getName() + ORIENTATION_PROPERTY_NAME; - panel.setSideBySide(saveState.getBoolean(key, true)); + for (CodeComparisonView view : codeComparisonViews) { + String key = view.getName() + ORIENTATION_PROPERTY_NAME; + view.setSideBySide(panelState.getBoolean(key, true)); } } - /** - * Saves the information to the save state about the FunctionComparisonPanel and - * which CodeComparisonPanel is currently displayed - * - * @param prefix identifier to prepend to any save state names to make them unique - * @param saveState the save state where the information gets written - */ - public void writeConfigState(String prefix, SaveState saveState) { + private void writeTabState() { String currentComponentName = getCurrentComponentName(); - if (currentComponentName != null) { - saveState.putString(prefix + COMPARISON_VIEW_DISPLAYED, getCurrentComponentName()); - } - saveState.putBoolean(prefix + CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, isScrollingSynced()); - - for (CodeComparisonPanel panel : codeComparisonPanels) { - String key = prefix + panel.getName() + ORIENTATION_PROPERTY_NAME; - boolean sideBySide = panel.isSideBySide(); - saveState.putBoolean(key, sideBySide); + if (currentComponentName == null) { + return; } + SaveState panelState = state.getPanelState(); + panelState.putString(COMPARISON_VIEW_DISPLAYED, getCurrentComponentName()); + state.setChanged(); + } + + private void writeScrollState() { + SaveState panelState = state.getPanelState(); + panelState.putBoolean(CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, isScrollingSynced()); + state.setChanged(); + } + + private void writeOrientationState() { + + SaveState panelState = state.getPanelState(); + for (CodeComparisonView view : codeComparisonViews) { + String key = view.getName() + ORIENTATION_PROPERTY_NAME; + boolean sideBySide = view.isSideBySide(); + panelState.putBoolean(key, sideBySide); + } } - /** - * Gets all actions for the FunctionComparisonPanel and all CodeComparisonPanels in this - * FunctionComparisonPanel - * - * @return the code comparison actions - */ public DockingAction[] getCodeComparisonActions() { ArrayList dockingActionList = new ArrayList<>(); - // Get actions for this functionComparisonPanel - DockingAction[] functionComparisonActions = getActions(); - for (DockingAction dockingAction : functionComparisonActions) { - dockingActionList.add(dockingAction); + // Get actions for this panel + DockingAction[] actions = getActions(); + for (DockingAction action : actions) { + dockingActionList.add(action); } - // Get actions for each CodeComparisonPanel - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - dockingActionList.addAll(codeComparisonPanel.getActions()); + // Get actions for each view + for (CodeComparisonView view : codeComparisonViews) { + dockingActionList.addAll(view.getActions()); } return dockingActionList.toArray(new DockingAction[dockingActionList.size()]); @@ -382,7 +406,7 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { /** * Sets the prefixes that are to be prepended to the title displayed for each side of - * each CodeComparisonPanel + * each {@link CodeComparisonView} * * @param leftTitlePrefix the prefix to prepend to the left titles * @param rightTitlePrefix the prefix to prepend to the right titles @@ -390,8 +414,8 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { public void setTitlePrefixes(String leftTitlePrefix, String rightTitlePrefix) { Component[] components = tabbedPane.getComponents(); for (Component component : components) { - if (component instanceof CodeComparisonPanel) { - ((CodeComparisonPanel) component).setTitlePrefixes(leftTitlePrefix, + if (component instanceof CodeComparisonView) { + ((CodeComparisonView) component).setTitlePrefixes(leftTitlePrefix, rightTitlePrefix); } } @@ -405,9 +429,9 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { * @return the action context */ public ActionContext getActionContext(MouseEvent event, ComponentProvider componentProvider) { - CodeComparisonPanel activePanel = getDisplayedPanel(); - if (activePanel != null) { - return activePanel.getActionContext(componentProvider, event); + CodeComparisonView activeProvider = getDisplayedView(); + if (activeProvider != null) { + return activeProvider.getActionContext(componentProvider, event); } return null; } @@ -432,43 +456,46 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { if (isScrollingSynced() == syncScrolling) { return; } + toggleScrollLockAction.setSelected(syncScrolling); toggleScrollLockAction.setToolBarData(new ToolBarData( syncScrolling ? SYNC_SCROLLING_ICON : UNSYNC_SCROLLING_ICON, SCROLLING_GROUP)); // Notify each comparison panel of the scrolling sync state. - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - codeComparisonPanel.setSynchronizedScrolling(syncScrolling); + for (CodeComparisonView view : codeComparisonViews) { + view.setSynchronizedScrolling(syncScrolling); } this.syncScrolling = syncScrolling; + + writeScrollState(); } /** - * Gets the currently displayed CodeComparisonPanel + * Gets the currently displayed {@link CodeComparisonView} * * @return the current panel or null. */ - public CodeComparisonPanel getDisplayedPanel() { + public CodeComparisonView getDisplayedView() { int selectedIndex = tabbedPane.getSelectedIndex(); Component component = tabbedPane.getComponentAt(selectedIndex); - return (CodeComparisonPanel) component; + return (CodeComparisonView) component; } /** - * Updates the enablement for all actions provided by each panel + * Updates the enablement for all actions provided by each view */ public void updateActionEnablement() { - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - codeComparisonPanel.updateActionEnablement(); + for (CodeComparisonView view : codeComparisonViews) { + view.updateActionEnablement(); } } /** - * Get the current code comparison panel being viewed + * Get the current code comparison view being viewed * - * @return null if there is no code comparison panel + * @return null if there is no code comparison view */ - public CodeComparisonPanel getCurrentComponent() { - return (CodeComparisonPanel) tabbedPane.getSelectedComponent(); + public CodeComparisonView getCurrentView() { + return (CodeComparisonView) tabbedPane.getSelectedComponent(); } /** @@ -519,45 +546,48 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { } } - public List getComparisonPanels() { - return codeComparisonPanels; + public List getComparisonView() { + return codeComparisonViews; } /** - * Discovers the CodeComparisonPanels which are extension points + * Discovers the {@link CodeComparisonView}s which are extension points * - * @return the CodeComparisonPanels which are extension points + * @return the views which are extension points */ - private List getCodeComparisonPanels(PluginTool tool, String owner) { - if (codeComparisonPanels == null) { - codeComparisonPanels = createAllPossibleCodeComparisonPanels(tool, owner); - codeComparisonPanels.sort((p1, p2) -> p1.getName().compareTo(p2.getName())); + private List getCodeComparisonViews(PluginTool tool, String owner) { + if (codeComparisonViews == null) { + codeComparisonViews = createAllCodeComparisonViews(tool, owner); + codeComparisonViews.sort((p1, p2) -> p1.getName().compareTo(p2.getName())); } - return codeComparisonPanels; + return codeComparisonViews; } - private List createAllPossibleCodeComparisonPanels(PluginTool tool, + private List createAllCodeComparisonViews(PluginTool tool, String owner) { - List instances = new ArrayList<>(); - - List > classes = - ClassSearcher.getClasses(CodeComparisonPanel.class); - - for (Class extends CodeComparisonPanel> panelClass : classes) { + CodeComparisonViewState viewState = state.getViewState(); + List instances = new ArrayList<>(); + List > classes = + ClassSearcher.getClasses(CodeComparisonView.class); + for (Class extends CodeComparisonView> viewClass : classes) { try { - Constructor extends CodeComparisonPanel> constructor = - panelClass.getConstructor(String.class, PluginTool.class); - CodeComparisonPanel panel = constructor.newInstance(owner, tool); - instances.add(panel); + Constructor extends CodeComparisonView> constructor = + viewClass.getConstructor(String.class, PluginTool.class); + CodeComparisonView view = constructor.newInstance(owner, tool); + + SaveState saveState = viewState.getSaveState(viewClass); + view.setSaveState(saveState); + + view.setOrientationChangedCallback(() -> writeOrientationState()); + + instances.add(view); } - catch (NoSuchMethodException | SecurityException | InstantiationException - | IllegalAccessException | IllegalArgumentException - | InvocationTargetException e) { + catch (Exception e) { Msg.showError(this, null, "Error Creating Extension Point", - "Error creating class " + panelClass.getName() + + "Error creating class " + viewClass.getName() + " when creating extension points for " + - CodeComparisonPanel.class.getName(), + CodeComparisonView.class.getName(), e); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/FunctionComparisonState.java b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/FunctionComparisonState.java new file mode 100644 index 0000000000..cabed0aefc --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/features/base/codecompare/panel/FunctionComparisonState.java @@ -0,0 +1,90 @@ +/* ### + * 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.features.base.codecompare.panel; + +import java.util.*; + +import ghidra.framework.options.SaveState; +import ghidra.framework.plugintool.PluginTool; +import utility.function.Callback; + +/** + * An object to share config state between providers and all views within those providers. + * + * When a comparison provider updates its save state object, it should call + * {@link PluginTool#setConfigChanged(boolean)} so that tool knows there are settings to be saved. + */ +public class FunctionComparisonState { + + private static final String PROVIDER_SAVE_STATE_NAME = "FunctionComparison"; + + private SaveState panelState = new SaveState(); + private CodeComparisonViewState comparisonState = new CodeComparisonViewState(); + + private PluginTool tool; + + private List
updateCallbacks = new ArrayList<>(); + + public FunctionComparisonState(PluginTool tool) { + this.tool = tool; + } + + /** + * Returns the state object for the provider + * @return the state object for the provider + */ + public SaveState getPanelState() { + return panelState; + } + + /** + * Returns the save state object for the views that live inside a provider + * @return the state + */ + public CodeComparisonViewState getViewState() { + return comparisonState; + } + + /** + * Signals to the tool that there are changes to the config state that can be saved. + */ + public void setChanged() { + tool.setConfigChanged(true); + } + + public void writeConfigState(SaveState saveState) { + saveState.putSaveState(PROVIDER_SAVE_STATE_NAME, panelState); + comparisonState.writeConfigState(saveState); + } + + public void readConfigState(SaveState saveState) { + SaveState restoredPanelState = saveState.getSaveState(PROVIDER_SAVE_STATE_NAME); + if (restoredPanelState != null) { + panelState = restoredPanelState; + } + + comparisonState.readConfigState(saveState); + updateCallbacks.forEach(Callback::call); + } + + /** + * Adds a callback to this state that is notified when this state changes. + * @param callback the callback + */ + public void addUpdateCallback(Callback callback) { + updateCallbacks.add(Objects.requireNonNull(callback)); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramSelection.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramSelection.java index f9e74ed4af..271d6e92e3 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramSelection.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ProgramSelection.java @@ -82,6 +82,7 @@ public class ProgramSelection implements AddressSetView { * @param addressFactory NOT USED * @param from the start of the selection * @param to the end of the selection + * @deprecated use {@link #ProgramSelection(Address, Address)} */ @Deprecated(since = "11.2", forRemoval = true) public ProgramSelection(AddressFactory addressFactory, Address from, Address to) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java b/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java index e23d218a9b..d48848b228 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/test/TestEnv.java @@ -55,7 +55,8 @@ import ghidra.program.model.lang.*; import ghidra.program.model.listing.Program; import ghidra.program.util.DefaultLanguageService; import ghidra.program.util.ProgramUtilities; -import ghidra.util.*; +import ghidra.util.Msg; +import ghidra.util.TaskUtilities; import ghidra.util.datastruct.WeakSet; import ghidra.util.exception.*; import ghidra.util.task.*; @@ -75,7 +76,7 @@ public class TestEnv { private static Set instances = new HashSet<>(); private FrontEndTool frontEndTool; - private PluginTool tool; + protected PluginTool tool; private static TestProgramManager programManager = new TestProgramManager(); @@ -962,7 +963,7 @@ public class TestEnv { public Program loadResourceProgramAsBinary(String programName, Language language, CompilerSpec compilerSpec) throws LanguageNotFoundException, IOException, - CancelledException, DuplicateNameException, InvalidNameException, VersionException { + CancelledException, VersionException { File file = AbstractGenericTest.getTestDataFile(programName); if (file == null || !file.exists()) { throw new FileNotFoundException("Can not find test program: " + programName); @@ -971,8 +972,7 @@ public class TestEnv { } public Program loadResourceProgramAsBinary(String programName, Processor processor) - throws CancelledException, DuplicateNameException, InvalidNameException, - VersionException, IOException { + throws CancelledException, VersionException, IOException { Language language = DefaultLanguageService.getLanguageService().getDefaultLanguage(processor); CompilerSpec compilerSpec = language.getDefaultCompilerSpec(); diff --git a/Ghidra/Features/Base/src/test/java/ghidra/framework/options/SaveStateTest.java b/Ghidra/Features/Base/src/test/java/ghidra/framework/options/SaveStateTest.java index 9671f49121..e84aba8569 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/framework/options/SaveStateTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/framework/options/SaveStateTest.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,6 +21,9 @@ import org.jdom.Element; import org.junit.Before; import org.junit.Test; +import ghidra.util.Msg; +import ghidra.util.xml.XmlUtilities; + public class SaveStateTest { private SaveState saveState; @@ -46,6 +49,16 @@ public class SaveStateTest { assertEquals(2, restoreSubState.getNames().length); assertEquals(5, restoreSubState.getInt("a", 0)); assertEquals("bar", restoreSubState.getString("foo", "")); + + SaveState s1 = new SaveState("Parent"); + SaveState c1 = new SaveState("Child1"); + c1.putBoolean("Bool1", false); + c1.putString("String1", "Hey bob"); + s1.putSaveState("MapChildName1", c1); + Element e = s1.saveToXml(); + String s = XmlUtilities.toString(e); + Msg.debug(this, s); + } private SaveState saveAndRestoreToXml() throws Exception { diff --git a/Ghidra/Features/CodeCompare/build.gradle b/Ghidra/Features/CodeCompare/build.gradle index e98116eb04..18366cce84 100755 --- a/Ghidra/Features/CodeCompare/build.gradle +++ b/Ghidra/Features/CodeCompare/build.gradle @@ -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. @@ -23,4 +23,5 @@ eclipse.project.name = 'Features CodeCompare' dependencies { api project(":Decompiler") + api project(":FunctionGraph") } diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedCalleeTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedCalleeTokensAction.java index 76f4dd0754..98b0e1110d 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedCalleeTokensAction.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedCalleeTokensAction.java @@ -26,7 +26,7 @@ import ghidra.util.Msg; /** * Subclass of {@link AbstractMatchedTokensAction} for actions in a - * {@link DecompilerCodeComparisonPanel} that are available only when the matched tokens are + * {@link DecompilerCodeComparisonView} that are available only when the matched tokens are * function calls */ public abstract class AbstractMatchedCalleeTokensAction extends AbstractMatchedTokensAction { @@ -37,12 +37,12 @@ public abstract class AbstractMatchedCalleeTokensAction extends AbstractMatchedT * * @param actionName name of action * @param owner owner of action - * @param diffPanel diff panel containing action + * @param comparisonProvider diff comparison provider containing action * @param disableOnReadOnly if true, action will be disabled for read-only programs */ public AbstractMatchedCalleeTokensAction(String actionName, String owner, - DecompilerCodeComparisonPanel diffPanel, boolean disableOnReadOnly) { - super(actionName, owner, diffPanel, disableOnReadOnly); + DecompilerCodeComparisonView comparisonProvider, boolean disableOnReadOnly) { + super(actionName, owner, comparisonProvider, disableOnReadOnly); } @Override @@ -69,15 +69,15 @@ public abstract class AbstractMatchedCalleeTokensAction extends AbstractMatchedT @Override public void dualDecompilerActionPerformed(DualDecompilerActionContext context) { - DecompilerCodeComparisonPanel decompPanel = context.getCodeComparisonPanel(); + DecompilerCodeComparisonView provider = context.getCodeComparisonView(); TokenPair currentPair = context.getTokenPair(); ClangFuncNameToken leftFuncToken = (ClangFuncNameToken) currentPair.leftToken(); ClangFuncNameToken rightFuncToken = (ClangFuncNameToken) currentPair.rightToken(); - Function leftFunction = getFuncFromToken(leftFuncToken, decompPanel.getProgram(LEFT)); - Function rightFunction = getFuncFromToken(rightFuncToken, decompPanel.getProgram(RIGHT)); + Function leftFunction = getFuncFromToken(leftFuncToken, provider.getProgram(LEFT)); + Function rightFunction = getFuncFromToken(rightFuncToken, provider.getProgram(RIGHT)); if (leftFunction == null || rightFunction == null) { return; } diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedTokensAction.java index 0788c7366b..20dfb01583 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedTokensAction.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/AbstractMatchedTokensAction.java @@ -19,13 +19,13 @@ import docking.ActionContext; import docking.action.DockingAction; /** - * This is a base class for actions in a {@link DecompilerCodeComparisonPanel} + * This is a base class for actions in a {@link DecompilerCodeComparisonView} */ public abstract class AbstractMatchedTokensAction extends DockingAction { protected static final String MENU_PARENT = "Apply From Other Function"; protected static final String HELP_TOPIC = "FunctionComparison"; - protected DecompilerCodeComparisonPanel diffPanel; + protected DecompilerCodeComparisonView comparisonProvider; protected boolean disableOnReadOnly; /** @@ -33,13 +33,13 @@ public abstract class AbstractMatchedTokensAction extends DockingAction { * * @param actionName name of action * @param owner owner of action - * @param diffPanel diff panel containing action + * @param comparisonProvider diff panel containing action * @param disableOnReadOnly if true, action will be disabled for read-only programs */ public AbstractMatchedTokensAction(String actionName, String owner, - DecompilerCodeComparisonPanel diffPanel, boolean disableOnReadOnly) { + DecompilerCodeComparisonView comparisonProvider, boolean disableOnReadOnly) { super(actionName, owner); - this.diffPanel = diffPanel; + this.comparisonProvider = comparisonProvider; this.disableOnReadOnly = disableOnReadOnly; } diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeEmptySignatureFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeEmptySignatureFromMatchedTokensAction.java index cd96648da4..5b7aad4fdd 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeEmptySignatureFromMatchedTokensAction.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeEmptySignatureFromMatchedTokensAction.java @@ -33,14 +33,9 @@ public class ApplyCalleeEmptySignatureFromMatchedTokensAction private PluginTool tool; public static final String ACTION_NAME = "Function Comparison Apply Callee Signature"; - /** - * Construtor - * @param diffPanel diff panel - * @param tool tool - */ public ApplyCalleeEmptySignatureFromMatchedTokensAction( - DecompilerCodeComparisonPanel diffPanel, PluginTool tool) { - super(ACTION_NAME, tool.getName(), diffPanel, true); + DecompilerCodeComparisonView comparisonProvider, PluginTool tool) { + super(ACTION_NAME, tool.getName(), comparisonProvider, true); this.tool = tool; MenuData menuData = @@ -53,7 +48,7 @@ public class ApplyCalleeEmptySignatureFromMatchedTokensAction @Override protected void doCalleeActionPerformed(Function leftFunction, Function rightFunction) { - Side activeSide = diffPanel.getActiveSide(); + Side activeSide = comparisonProvider.getActiveSide(); Function activeFunction = activeSide == Side.LEFT ? leftFunction : rightFunction; Function otherFunction = activeSide == Side.LEFT ? rightFunction : leftFunction; diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeFunctionNameFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeFunctionNameFromMatchedTokensAction.java index 77daf33b5c..bfc7553e36 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeFunctionNameFromMatchedTokensAction.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeFunctionNameFromMatchedTokensAction.java @@ -33,14 +33,9 @@ public class ApplyCalleeFunctionNameFromMatchedTokensAction private PluginTool tool; public static final String ACTION_NAME = "Function Comparison Apply Callee Name"; - /** - * Construtor - * @param diffPanel diff panel - * @param tool tool - */ public ApplyCalleeFunctionNameFromMatchedTokensAction( - DecompilerCodeComparisonPanel diffPanel, PluginTool tool) { - super(ACTION_NAME, tool.getName(), diffPanel, true); + DecompilerCodeComparisonView comparisonProvider, PluginTool tool) { + super(ACTION_NAME, tool.getName(), comparisonProvider, true); this.tool = tool; MenuData menuData = @@ -52,7 +47,7 @@ public class ApplyCalleeFunctionNameFromMatchedTokensAction @Override protected void doCalleeActionPerformed(Function leftFunction, Function rightFunction) { - Side activeSide = diffPanel.getActiveSide(); + Side activeSide = comparisonProvider.getActiveSide(); Function activeFunction = activeSide == Side.LEFT ? leftFunction : rightFunction; Function otherFunction = activeSide == Side.LEFT ? rightFunction : leftFunction; diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction.java index c2903ca6c9..39bf51d5e9 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction.java @@ -33,14 +33,9 @@ public class ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction public static final String ACTION_NAME = "Function Comparison Apply Callee Signature And Datatypes"; - /** - * Construtor - * @param diffPanel diff panel - * @param tool tool - */ public ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction( - DecompilerCodeComparisonPanel diffPanel, PluginTool tool) { - super(ACTION_NAME, tool.getName(), diffPanel, true); + DecompilerCodeComparisonView comparisonProvider, PluginTool tool) { + super(ACTION_NAME, tool.getName(), comparisonProvider, true); this.tool = tool; MenuData menuData = @@ -53,7 +48,7 @@ public class ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction @Override protected void doCalleeActionPerformed(Function leftFunction, Function rightFunction) { - Side activeSide = diffPanel.getActiveSide(); + Side activeSide = comparisonProvider.getActiveSide(); Function activeFunction = activeSide == Side.LEFT ? leftFunction : rightFunction; Function otherFunction = activeSide == Side.LEFT ? rightFunction : leftFunction; diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyEmptyVariableTypeFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyEmptyVariableTypeFromMatchedTokensAction.java index f7abeadcee..e8ea7b400f 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyEmptyVariableTypeFromMatchedTokensAction.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyEmptyVariableTypeFromMatchedTokensAction.java @@ -35,9 +35,9 @@ public class ApplyEmptyVariableTypeFromMatchedTokensAction extends AbstractMatch public static final String ACTION_NAME = "Function Comparison Apply Variable Skeleton Type"; private static final String MENU_GROUP = "A1_ApplyVariable"; - public ApplyEmptyVariableTypeFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel, - PluginTool tool) { - super(ACTION_NAME, tool.getName(), diffPanel, true); + public ApplyEmptyVariableTypeFromMatchedTokensAction( + DecompilerCodeComparisonView comparisonProvider, PluginTool tool) { + super(ACTION_NAME, tool.getName(), comparisonProvider, true); this.tool = tool; MenuData menuData = @@ -73,7 +73,7 @@ public class ApplyEmptyVariableTypeFromMatchedTokensAction extends AbstractMatch protected void dualDecompilerActionPerformed(DualDecompilerActionContext context) { TokenPair currentPair = context.getTokenPair(); - Side activeSide = diffPanel.getActiveSide(); + Side activeSide = comparisonProvider.getActiveSide(); ClangVariableToken activeToken = activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken() @@ -87,7 +87,7 @@ public class ApplyEmptyVariableTypeFromMatchedTokensAction extends AbstractMatch HighSymbol otherHighSymbol = otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide())); - Function activeFunction = context.getCodeComparisonPanel().getFunction(activeSide); + Function activeFunction = context.getCodeComparisonView().getFunction(activeSide); Program activeProgram = activeFunction.getProgram(); DataType dt = otherHighSymbol.getDataType(); diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyGlobalNameFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyGlobalNameFromMatchedTokensAction.java index 7aa54371b1..17e7b654c9 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyGlobalNameFromMatchedTokensAction.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyGlobalNameFromMatchedTokensAction.java @@ -36,14 +36,9 @@ public class ApplyGlobalNameFromMatchedTokensAction extends AbstractMatchedToken public static final String ACTION_NAME = "Function Comparison Apply Global Variable Name"; private static final String MENU_GROUP = "A1_ApplyVariable"; - /** - * Construtor - * @param diffPanel diff panel - * @param tool tool - */ - public ApplyGlobalNameFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel, - PluginTool tool) { - super(ACTION_NAME, tool.getName(), diffPanel, true); + public ApplyGlobalNameFromMatchedTokensAction( + DecompilerCodeComparisonView comparisonProvider, PluginTool tool) { + super(ACTION_NAME, tool.getName(), comparisonProvider, true); this.tool = tool; MenuData menuData = @@ -83,7 +78,7 @@ public class ApplyGlobalNameFromMatchedTokensAction extends AbstractMatchedToken public void dualDecompilerActionPerformed(DualDecompilerActionContext context) { TokenPair currentPair = context.getTokenPair(); - Side activeSide = diffPanel.getActiveSide(); + Side activeSide = comparisonProvider.getActiveSide(); ClangVariableToken activeToken = activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken() : (ClangVariableToken) currentPair.rightToken(); @@ -96,7 +91,7 @@ public class ApplyGlobalNameFromMatchedTokensAction extends AbstractMatchedToken HighSymbol otherHighSymbol = otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide())); - Program activeProgram = context.getCodeComparisonPanel().getProgram(activeSide); + Program activeProgram = context.getCodeComparisonView().getProgram(activeSide); Symbol activeSymbol = null; if (activeHighSymbol instanceof HighCodeSymbol activeCodeSymbol) { diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyLocalNameFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyLocalNameFromMatchedTokensAction.java index 9397bfc4be..edcd65e161 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyLocalNameFromMatchedTokensAction.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyLocalNameFromMatchedTokensAction.java @@ -36,14 +36,9 @@ public class ApplyLocalNameFromMatchedTokensAction extends AbstractMatchedTokens public static final String ACTION_NAME = "Function Comparison Apply Local Variable Name"; private static final String MENU_GROUP = "A1_ApplyVariable"; - /** - * Construtor - * @param diffPanel diff panel - * @param tool tool - */ - public ApplyLocalNameFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel, - PluginTool tool) { - super(ACTION_NAME, tool.getName(), diffPanel, true); + public ApplyLocalNameFromMatchedTokensAction( + DecompilerCodeComparisonView comparisonProvider, PluginTool tool) { + super(ACTION_NAME, tool.getName(), comparisonProvider, true); this.tool = tool; MenuData menuData = @@ -82,7 +77,7 @@ public class ApplyLocalNameFromMatchedTokensAction extends AbstractMatchedTokens public void dualDecompilerActionPerformed(DualDecompilerActionContext context) { TokenPair currentPair = context.getTokenPair(); - Side activeSide = diffPanel.getActiveSide(); + Side activeSide = comparisonProvider.getActiveSide(); ClangVariableToken activeToken = activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken() @@ -96,7 +91,7 @@ public class ApplyLocalNameFromMatchedTokensAction extends AbstractMatchedTokens HighSymbol otherHighSymbol = otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide())); - Function activeFunction = context.getCodeComparisonPanel().getFunction(activeSide); + Function activeFunction = context.getCodeComparisonView().getFunction(activeSide); Program activeProgram = activeFunction.getProgram(); try { diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyVariableTypeFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyVariableTypeFromMatchedTokensAction.java index 4adeed1944..a279664b8d 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyVariableTypeFromMatchedTokensAction.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/ApplyVariableTypeFromMatchedTokensAction.java @@ -37,14 +37,9 @@ public class ApplyVariableTypeFromMatchedTokensAction extends AbstractMatchedTok public static final String ACTION_NAME = "Function Comparison Apply Variable Type"; private static final String MENU_GROUP = "A1_ApplyVariable"; - /** - * Construtor - * @param diffPanel diff panel - * @param tool tool - */ - public ApplyVariableTypeFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel, - PluginTool tool) { - super(ACTION_NAME, tool.getName(), diffPanel, true); + public ApplyVariableTypeFromMatchedTokensAction( + DecompilerCodeComparisonView comparisonProvider, PluginTool tool) { + super(ACTION_NAME, tool.getName(), comparisonProvider, true); this.tool = tool; MenuData menuData = @@ -80,7 +75,7 @@ public class ApplyVariableTypeFromMatchedTokensAction extends AbstractMatchedTok public void dualDecompilerActionPerformed(DualDecompilerActionContext context) { TokenPair currentPair = context.getTokenPair(); - Side activeSide = diffPanel.getActiveSide(); + Side activeSide = comparisonProvider.getActiveSide(); ClangVariableToken activeToken = activeSide == Side.LEFT ? (ClangVariableToken) currentPair.leftToken() @@ -94,7 +89,7 @@ public class ApplyVariableTypeFromMatchedTokensAction extends AbstractMatchedTok HighSymbol otherHighSymbol = otherToken.getHighSymbol(context.getHighFunction(activeSide.otherSide())); - Function activeFunction = context.getCodeComparisonPanel().getFunction(activeSide); + Function activeFunction = context.getCodeComparisonView().getFunction(activeSide); Program activeProgram = activeFunction.getProgram(); DataType dt = otherHighSymbol.getDataType(); diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/CDisplay.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/CDisplay.java index cfcc9c481f..7159608ef1 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/CDisplay.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/CDisplay.java @@ -39,7 +39,6 @@ import ghidra.program.util.ProgramLocation; public class CDisplay { private final static String OPTIONS_TITLE = "Decompiler"; - private ServiceProvider serviceProvider; private DecompilerController controller; private DecompileOptions decompileOptions; private FieldLocation lastCursorPosition; @@ -53,7 +52,6 @@ public class CDisplay { DecompileResultsListener decompileListener, Consumer locationConsumer) { - this.serviceProvider = serviceProvider; highlightController = new DiffClangHighlightController(comparisonOptions); decompileOptions = new DecompileOptions(); @@ -138,10 +136,6 @@ public class CDisplay { controller.dispose(); } - public DecompilerController getController() { - return controller; - } - public void refresh() { saveCursorPosition(); DecompileData data = getDecompileData(); @@ -176,8 +170,8 @@ public class CDisplay { } ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS); ToolOptions options = tool.getOptions(OPTIONS_TITLE); - Program program = function == null ? null : function.getProgram(); - decompileOptions.grabFromToolAndProgram(fieldOptions, options, program); + Program p = function == null ? null : function.getProgram(); + decompileOptions.grabFromToolAndProgram(fieldOptions, options, p); } DiffClangHighlightController getHighlightController() { diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/CompareFuncsFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/CompareFuncsFromMatchedTokensAction.java index fb2d999a12..3dbb93613d 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/CompareFuncsFromMatchedTokensAction.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/CompareFuncsFromMatchedTokensAction.java @@ -35,7 +35,7 @@ public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedCalleeTo * @param diffPanel diff Panel * @param tool tool */ - public CompareFuncsFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel, + public CompareFuncsFromMatchedTokensAction(DecompilerCodeComparisonView diffPanel, PluginTool tool) { super(ACTION_NAME, tool.getName(), diffPanel, false); this.tool = tool; diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerCodeComparisonOptions.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerCodeComparisonOptions.java index 19bdc71ee1..d7d3266f2e 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerCodeComparisonOptions.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerCodeComparisonOptions.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. @@ -151,4 +151,9 @@ public class DecompilerCodeComparisonOptions implements OptionsChangeListener { optionsChangedCallback.call(); } + public void dispose(PluginTool tool) { + ToolOptions options = + tool.getOptions(DecompilerCodeComparisonOptions.OPTIONS_CATEGORY_NAME); + options.removeOptionsChangeListener(this); + } } diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerCodeComparisonPanel.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerCodeComparisonView.java similarity index 95% rename from Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerCodeComparisonPanel.java rename to Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerCodeComparisonView.java index 1734223942..6848537d54 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerCodeComparisonPanel.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerCodeComparisonView.java @@ -32,7 +32,7 @@ import docking.options.OptionsService; import generic.theme.GIcon; import ghidra.app.decompiler.component.DecompileData; import ghidra.app.decompiler.component.DecompilerPanel; -import ghidra.features.base.codecompare.panel.CodeComparisonPanel; +import ghidra.features.base.codecompare.panel.CodeComparisonView; import ghidra.features.codecompare.graphanalysis.TokenBin; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.listing.Function; @@ -49,10 +49,9 @@ import resources.Icons; import resources.MultiIcon; /** - * Panel that displays two decompilers for comparison + * UI that displays two decompilers for comparison */ -public class DecompilerCodeComparisonPanel - extends CodeComparisonPanel { +public class DecompilerCodeComparisonView extends CodeComparisonView { public static final String NAME = "Decompiler View"; @@ -61,7 +60,7 @@ public class DecompilerCodeComparisonPanel private Duo cDisplays = new Duo<>(); private DecompilerCodeComparisonOptions comparisonOptions; - private CodeDiffFieldPanelCoordinator coordinator; + private DualDecompilerScrollCoordinator coordinator; private DecompileDataDiff decompileDataDiff; private ToggleExactConstantMatching toggleExactConstantMatchingAction; @@ -74,7 +73,7 @@ public class DecompilerCodeComparisonPanel * @param owner the owner of this panel * @param tool the tool displaying this panel */ - public DecompilerCodeComparisonPanel(String owner, PluginTool tool) { + public DecompilerCodeComparisonView(String owner, PluginTool tool) { super(owner, tool); comparisonOptions = new DecompilerCodeComparisonOptions(tool, () -> repaint()); @@ -119,7 +118,7 @@ public class DecompilerCodeComparisonPanel public void dispose() { setSynchronizedScrolling(false); // disposes any exiting coordinator cDisplays.each(CDisplay::dispose); - comparisonOptions = null; + comparisonOptions.dispose(tool); } /** @@ -189,6 +188,7 @@ public class DecompilerCodeComparisonPanel actions.add(new ApplyCalleeFunctionNameFromMatchedTokensAction(this, tool)); actions.add(new ApplyCalleeEmptySignatureFromMatchedTokensAction(this, tool)); actions.add(new ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction(this, tool)); + } private void decompileDataSet(Side side, DecompileData dcompileData) { @@ -272,8 +272,9 @@ public class DecompilerCodeComparisonPanel } } - private CodeDiffFieldPanelCoordinator createCoordinator() { - CodeDiffFieldPanelCoordinator panelCoordinator = new CodeDiffFieldPanelCoordinator(this); + private DualDecompilerScrollCoordinator createCoordinator() { + DualDecompilerScrollCoordinator panelCoordinator = + new DualDecompilerScrollCoordinator(this); if (decompileDataDiff != null) { TaskBuilder.withRunnable(monitor -> { try { @@ -371,7 +372,7 @@ public class DecompilerCodeComparisonPanel this.setToolBarData(new ToolBarData(NO_EXACT_CONSTANT_MATCHING_ICON, "toggles")); setDescription(HTMLUtilities.toHTML("Toggle whether or not constants must\n" + - "be exactly the same value to be a match\n" + "in the Decomiler Diff View.")); + "be exactly the same value to be a match\nin the Decomiler Diff View.")); setSelected(false); setEnabled(true); } diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerDiffViewFindAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerDiffViewFindAction.java index 6a3630a8dd..b721cdb47d 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerDiffViewFindAction.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DecompilerDiffViewFindAction.java @@ -67,11 +67,11 @@ public class DecompilerDiffViewFindAction extends DockingAction { @Override public void actionPerformed(ActionContext context) { DualDecompilerActionContext dualContext = (DualDecompilerActionContext) context; - DecompilerCodeComparisonPanel decompilerCompPanel = - dualContext.getCodeComparisonPanel(); + DecompilerCodeComparisonView provider = + dualContext.getCodeComparisonView(); - Side focusedSide = decompilerCompPanel.getActiveSide(); - DecompilerPanel focusedPanel = decompilerCompPanel.getDecompilerPanel(focusedSide); + Side focusedSide = provider.getActiveSide(); + DecompilerPanel focusedPanel = provider.getDecompilerPanel(focusedSide); FindDialog dialog = findDialogs.get(focusedSide); if (dialog == null) { dialog = createFindDialog(focusedPanel, focusedSide); diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DetermineDecompilerDifferencesTask.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DetermineDecompilerDifferencesTask.java index f243d1774b..48de42de95 100755 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DetermineDecompilerDifferencesTask.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DetermineDecompilerDifferencesTask.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. @@ -33,12 +33,12 @@ public class DetermineDecompilerDifferencesTask extends Task { private DecompileDataDiff decompileDataDiff; - private CodeDiffFieldPanelCoordinator decompilerFieldPanelCoordinator; + private DualDecompilerScrollCoordinator decompilerFieldPanelCoordinator; public DetermineDecompilerDifferencesTask(DecompileDataDiff decompileDataDiff, boolean matchConstantsExactly, DiffClangHighlightController leftHighlightController, DiffClangHighlightController rightHighlightController, - CodeDiffFieldPanelCoordinator decompilerFieldPanelCoordinator) { + DualDecompilerScrollCoordinator decompilerFieldPanelCoordinator) { super("Mapping C Tokens Between Functions", true, true, true); this.decompileDataDiff = decompileDataDiff; diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DualDecompilerActionContext.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DualDecompilerActionContext.java index 675d0b15b1..2a0626dfe1 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DualDecompilerActionContext.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DualDecompilerActionContext.java @@ -18,7 +18,6 @@ package ghidra.features.codecompare.decompile; import static ghidra.util.datastruct.Duo.Side.*; import java.awt.Component; -import java.util.Iterator; import java.util.List; import docking.ComponentProvider; @@ -38,26 +37,26 @@ import ghidra.util.datastruct.Duo.Side; public class DualDecompilerActionContext extends CodeComparisonActionContext implements RestrictedAddressSetContext { - private DecompilerCodeComparisonPanel decompilerComparisonPanel = null; + private DecompilerCodeComparisonView comparisonProvider = null; private TokenPair tokenPair; private boolean overrideReadOnly = false; /** * Creates an action context for a dual decompiler panel. * @param provider the provider for this context - * @param panel the DecompilerComparisonPanel + * @param comparisonProvider the DecompilerComparisonPanel * @param source the source of the action */ public DualDecompilerActionContext(ComponentProvider provider, - DecompilerCodeComparisonPanel panel, Component source) { - super(provider, panel, source); - decompilerComparisonPanel = panel; + DecompilerCodeComparisonView comparisonProvider, Component source) { + super(provider, comparisonProvider, source); + this.comparisonProvider = comparisonProvider; tokenPair = computeTokenPair(); } private TokenPair computeTokenPair() { DecompilerPanel focusedPanel = - decompilerComparisonPanel.getActiveDisplay().getDecompilerPanel(); + comparisonProvider.getActiveDisplay().getDecompilerPanel(); if (!(focusedPanel.getCurrentLocation() instanceof DecompilerLocation focusedLocation)) { return null; @@ -67,7 +66,7 @@ public class DualDecompilerActionContext extends CodeComparisonActionContext if (focusedToken == null) { return null; } - List tokenBin = decompilerComparisonPanel.getHighBins(); + List tokenBin = comparisonProvider.getHighBins(); if (tokenBin == null) { return null; } @@ -80,13 +79,9 @@ public class DualDecompilerActionContext extends CodeComparisonActionContext return null; } - //loop over the tokens in the matching bin and return the first one in the same - //class as focusedToken - Iterator tokenIter = matchedBin.iterator(); - while (tokenIter.hasNext()) { - ClangToken currentMatch = tokenIter.next(); + for (ClangToken currentMatch : matchedBin) { if (currentMatch.getClass().equals(focusedToken.getClass())) { - return decompilerComparisonPanel.getActiveSide() == LEFT + return comparisonProvider.getActiveSide() == LEFT ? new TokenPair(focusedToken, currentMatch) : new TokenPair(currentMatch, focusedToken); } @@ -95,12 +90,12 @@ public class DualDecompilerActionContext extends CodeComparisonActionContext } /** - * Returns the {@link DecompilerCodeComparisonPanel} that generated this context + * Returns the {@link DecompilerCodeComparisonView} that generated this context * @return the decompiler comparison panel that generated this context */ @Override - public DecompilerCodeComparisonPanel getCodeComparisonPanel() { - return decompilerComparisonPanel; + public DecompilerCodeComparisonView getCodeComparisonView() { + return comparisonProvider; } /** @@ -111,7 +106,7 @@ public class DualDecompilerActionContext extends CodeComparisonActionContext * context */ public HighFunction getHighFunction(Side side) { - return decompilerComparisonPanel.getDecompilerPanel(side).getController().getHighFunction(); + return comparisonProvider.getDecompilerPanel(side).getController().getHighFunction(); } /** @@ -144,7 +139,7 @@ public class DualDecompilerActionContext extends CodeComparisonActionContext } Program activeProgram = - decompilerComparisonPanel.getProgram(decompilerComparisonPanel.getActiveSide()); + comparisonProvider.getProgram(comparisonProvider.getActiveSide()); if (activeProgram == null) { return true; diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DualDecompilerFieldPanelCoordinator.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DualDecompilerFieldPanelCoordinator.java deleted file mode 100644 index 3c569b6dee..0000000000 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DualDecompilerFieldPanelCoordinator.java +++ /dev/null @@ -1,35 +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.features.codecompare.decompile; - -import static ghidra.util.datastruct.Duo.Side.*; - -import docking.widgets.fieldpanel.FieldPanel; -import docking.widgets.fieldpanel.internal.LineLockedFieldPanelCoordinator; -import ghidra.program.util.ProgramLocation; - -abstract public class DualDecompilerFieldPanelCoordinator extends LineLockedFieldPanelCoordinator { - - public DualDecompilerFieldPanelCoordinator( - DecompilerCodeComparisonPanel dualDecompilerPanel) { - super(new FieldPanel[] { dualDecompilerPanel.getDecompilerPanel(LEFT).getFieldPanel(), - dualDecompilerPanel.getDecompilerPanel(RIGHT).getFieldPanel() }); - } - - abstract public void leftLocationChanged(ProgramLocation leftLocation); - - abstract public void rightLocationChanged(ProgramLocation rightLocation); -} diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/CodeDiffFieldPanelCoordinator.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DualDecompilerScrollCoordinator.java old mode 100755 new mode 100644 similarity index 93% rename from Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/CodeDiffFieldPanelCoordinator.java rename to Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DualDecompilerScrollCoordinator.java index 85db586fb8..a7c6d53e9a --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/CodeDiffFieldPanelCoordinator.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/decompile/DualDecompilerScrollCoordinator.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. @@ -24,6 +24,8 @@ import java.util.List; import org.apache.commons.collections4.BidiMap; import org.apache.commons.collections4.bidimap.DualHashBidiMap; +import docking.widgets.fieldpanel.FieldPanel; +import docking.widgets.fieldpanel.internal.LineLockedFieldPanelScrollCoordinator; import docking.widgets.fieldpanel.support.ViewerPosition; import ghidra.app.decompiler.*; import ghidra.app.decompiler.component.DecompilerPanel; @@ -33,11 +35,7 @@ import ghidra.program.util.ProgramLocation; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; -/** - * Class to coordinate the scrolling of two decompiler panels as well as cursor location - * highlighting due to cursor location changes. - */ -public class CodeDiffFieldPanelCoordinator extends DualDecompilerFieldPanelCoordinator { +public class DualDecompilerScrollCoordinator extends LineLockedFieldPanelScrollCoordinator { private BidiMap leftToRightLineNumberPairing; private List leftLines = new ArrayList (); @@ -51,12 +49,13 @@ public class CodeDiffFieldPanelCoordinator extends DualDecompilerFieldPanelCoord /** * Constructor - * @param dualDecompilerPanel decomp comparison panel + * @param comparisonProvider decomp comparison provider */ - public CodeDiffFieldPanelCoordinator(DecompilerCodeComparisonPanel dualDecompilerPanel) { - super(dualDecompilerPanel); - this.leftDecompilerPanel = dualDecompilerPanel.getDecompilerPanel(LEFT); - this.rightDecompilerPanel = dualDecompilerPanel.getDecompilerPanel(RIGHT); + public DualDecompilerScrollCoordinator(DecompilerCodeComparisonView comparisonProvider) { + super(new FieldPanel[] { comparisonProvider.getDecompilerPanel(LEFT).getFieldPanel(), + comparisonProvider.getDecompilerPanel(RIGHT).getFieldPanel() }); + this.leftDecompilerPanel = comparisonProvider.getDecompilerPanel(LEFT); + this.rightDecompilerPanel = comparisonProvider.getDecompilerPanel(RIGHT); leftToRightLineNumberPairing = new DualHashBidiMap<>(); } @@ -89,7 +88,6 @@ public class CodeDiffFieldPanelCoordinator extends DualDecompilerFieldPanelCoord } } - @Override public void leftLocationChanged(ProgramLocation leftLocation) { DecompilerLocation leftDecompilerLocation = (DecompilerLocation) leftLocation; @@ -107,7 +105,6 @@ public class CodeDiffFieldPanelCoordinator extends DualDecompilerFieldPanelCoord panelViewChanged(leftDecompilerPanel); } - @Override public void rightLocationChanged(ProgramLocation rightLocation) { DecompilerLocation rightDecompilerLocation = (DecompilerLocation) rightLocation; @@ -301,5 +298,4 @@ public class CodeDiffFieldPanelCoordinator extends DualDecompilerFieldPanelCoord return true; } - } diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/FgComparisonContext.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/FgComparisonContext.java new file mode 100644 index 0000000000..aed6703070 --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/FgComparisonContext.java @@ -0,0 +1,54 @@ +/* ### + * 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.features.codecompare.functiongraph; + +import java.awt.Component; + +import docking.ComponentProvider; +import ghidra.features.base.codecompare.panel.CodeComparisonActionContext; +import ghidra.features.base.codecompare.panel.CodeComparisonView; + +/** + * Action context for a dual Function Graph panel. + */ +public class FgComparisonContext extends CodeComparisonActionContext { + + private FunctionGraphCodeComparisonView fgProvider; + private FgDisplay display; + private boolean isLeft; + + public FgComparisonContext(ComponentProvider provider, + FunctionGraphCodeComparisonView fgPanel, + FgDisplay display, Component component, boolean isLeft) { + super(provider, fgPanel, component); + this.fgProvider = fgPanel; + this.display = display; + this.isLeft = isLeft; + } + + @Override + public CodeComparisonView getCodeComparisonView() { + return fgProvider; + } + + public FgDisplay getDisplay() { + return display; + } + + public boolean isLeft() { + return isLeft; + } +} diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/FgDisplay.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/FgDisplay.java new file mode 100644 index 0000000000..40b3f69885 --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/FgDisplay.java @@ -0,0 +1,535 @@ +/* ### + * 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.features.codecompare.functiongraph; + +import java.util.*; +import java.util.function.Consumer; + +import javax.swing.Icon; +import javax.swing.JComponent; + +import docking.action.DockingAction; +import docking.tool.ToolConstants; +import ghidra.app.nav.*; +import ghidra.app.plugin.core.colorizer.ColorizingService; +import ghidra.app.plugin.core.functiongraph.*; +import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutOptions; +import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider; +import ghidra.app.plugin.core.functiongraph.graph.layout.flowchart.FlowChartLayoutProvider; +import ghidra.app.plugin.core.functiongraph.mvc.*; +import ghidra.app.services.ClipboardService; +import ghidra.app.util.ListingHighlightProvider; +import ghidra.app.util.viewer.format.FormatManager; +import ghidra.framework.model.DomainObjectChangedEvent; +import ghidra.framework.model.DomainObjectListener; +import ghidra.framework.options.*; +import ghidra.framework.plugintool.PluginTool; +import ghidra.framework.plugintool.util.ServiceListener; +import ghidra.graph.viewer.options.VisualGraphOptions; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; +import ghidra.program.util.ProgramSelection; +import ghidra.util.UniversalIdGenerator; +import ghidra.util.exception.AssertException; +import ghidra.util.task.SwingUpdateManager; + +/** + * This class displays a Function Graph in the left or right side of the Function Comparison view. + */ +public class FgDisplay implements OptionsChangeListener { + + private static final String FUNCTION_GRAPH_NAME = "Function Graph"; + + private PluginTool tool; + private String owner; + private Program program; + private FGController controller; + private FgOptions fgOptions; + private FormatManager userDefinedFormatManager; + private ProgramLocation currentLocation; + + private FgDisplayProgramListener programListener = new FgDisplayProgramListener(); + private FgServiceListener serviceListener = new FgServiceListener(); + + private FGColorProvider colorProvider; + private List layoutProviders; + + private Consumer locationConsumer; + private Consumer graphChangedConsumer; + + // Note: this class should probably be using code block highlights and not the code-level + // highlights already provided by the Listing. + // FgHighlighter highlighter; + + FgDisplay(FunctionGraphCodeComparisonView fgView, + Consumer locationConsumer, Consumer graphChangedConsumer) { + + this.tool = fgView.getTool(); + this.owner = fgView.getOwner(); + this.locationConsumer = locationConsumer; + this.graphChangedConsumer = graphChangedConsumer; + fgOptions = new FgOptions(); + + layoutProviders = loadLayoutProviders(); + colorProvider = new IndependentColorProvider(tool); + + init(); + + FgComparisonEnv env = new FgComparisonEnv(); + FGControllerListener listener = new FgCoparisonControllerListener(); + controller = new FGController(env, listener); + + setDefaultLayout(); + } + + private void setDefaultLayout() { + FGLayoutProvider initialLayout = layoutProviders.get(0); + for (FGLayoutProvider layout : layoutProviders) { + if (layout.getClass().equals(FlowChartLayoutProvider.class)) { + initialLayout = layout; + break; + } + } + controller.changeLayout(initialLayout); + } + + private void init() { + + tool.addServiceListener(serviceListener); + + ColorizingService colorizingService = tool.getService(ColorizingService.class); + if (colorizingService != null) { + colorProvider = new ToolBasedColorProvider(() -> program, colorizingService); + } + } + + private List loadLayoutProviders() { + + // Shared Code Note: This code is mirrored in the FgDisplay for the Code Comparison API + + FGLayoutFinder layoutFinder = new DiscoverableFGLayoutFinder(); + List instances = layoutFinder.findLayouts(); + if (instances.isEmpty()) { + throw new AssertException("Could not find any layout providers. You project may not " + + "be configured properly."); + } + + List layouts = new ArrayList<>(instances); + Collections.sort(layouts, (o1, o2) -> -o1.getPriorityLevel() + o2.getPriorityLevel()); + return layouts; + } + + private void initializeOptions() { + ToolOptions options = tool.getOptions(ToolConstants.GRAPH_OPTIONS); + options.removeOptionsChangeListener(this); + options.addOptionsChangeListener(this); + + // Graph -> Function Graph + Options subOptions = options.getOptions(FUNCTION_GRAPH_NAME); + + fgOptions.registerOptions(subOptions); + fgOptions.loadOptions(subOptions); + + for (FGLayoutProvider layoutProvider : layoutProviders) { + + // Graph -> Function Graph -> Layout Name + String layoutName = layoutProvider.getLayoutName(); + Options layoutToolOptions = subOptions.getOptions(layoutName); + FGLayoutOptions layoutOptions = layoutProvider.createLayoutOptions(layoutToolOptions); + if (layoutOptions == null) { + continue; // many layouts do not have options + } + + layoutOptions.registerOptions(layoutToolOptions); + layoutOptions.loadOptions(layoutToolOptions); + fgOptions.setLayoutOptions(layoutName, layoutOptions); + } + } + + public FGController getController() { + return controller; + } + + public String getOwner() { + return owner; + } + + public JComponent getComponent() { + return controller.getViewComponent(); + } + + public void dispose() { + if (program != null) { + program.removeListener(programListener); + program = null; + } + programListener.dispose(); + controller.cleanup(); + } + + @Override + public void optionsChanged(ToolOptions options, String optionName, Object oldValue, + Object newValue) { + + // Graph -> Function Graph + Options subOptions = options.getOptions(FUNCTION_GRAPH_NAME); + fgOptions.loadOptions(subOptions); + + controller.optionsChanged(); + + if (fgOptions.optionChangeRequiresRelayout(optionName)) { + controller.refresh(true); + } + else if (VisualGraphOptions.VIEW_RESTORE_OPTIONS_KEY.equals(optionName)) { + controller.clearViewSettings(); + } + else { + controller.refreshDisplayWithoutRebuilding(); + } + } + + public void showFunction(Function function) { + updateProgram(function); + + if (function == null) { + controller.setStatusMessage("No Function"); + return; + } + if (function.isExternal()) { + String name = function.getName(true); + controller.setStatusMessage("\"" + name + "\" is an external function."); + return; + } + + Address entry = function.getEntryPoint(); + currentLocation = new ProgramLocation(program, entry); + controller.display(program, currentLocation); + } + + public void setLocation(ProgramLocation location) { + controller.setLocation(location); + } + + public ProgramLocation getLocation() { + return controller.getLocation(); + } + + private void updateProgram(Function function) { + Program newProgram = function == null ? null : function.getProgram(); + if (program == newProgram) { + return; + } + if (program != null) { + program.removeListener(programListener); + } + + program = newProgram; + + if (program != null) { + program.addListener(programListener); + initializeOptions(); + } + + } + + public boolean isBusy() { + return controller.isBusy(); + } + + private void refresh() { + controller.refresh(true); + } + + private class FgComparisonEnv implements FgEnv { + + private Navigatable navigatable = new DummyNavigatable(); + + @Override + public PluginTool getTool() { + return tool; + } + + @Override + public Program getProgram() { + return program; + } + + @Override + public FunctionGraphOptions getOptions() { + return fgOptions; + } + + @Override + public List getLayoutProviders() { + return layoutProviders; + } + + @Override + public void addLocalAction(DockingAction action) { + // stub + } + + @Override + public FGColorProvider getColorProvider() { + return colorProvider; + } + + @Override + public FormatManager getUserDefinedFormat() { + return userDefinedFormatManager; + } + + @Override + public void setUserDefinedFormat(FormatManager format) { + userDefinedFormatManager = format; + } + + @Override + public Navigatable getNavigatable() { + return navigatable; + } + + @Override + public ProgramLocation getToolLocation() { + // this isn't really the tool's location, but maybe this is fine for this display + return currentLocation; + } + + @Override + public ProgramLocation getGraphLocation() { + return currentLocation; + } + + @Override + public void setSelection(ProgramSelection selection) { + controller.setSelection(selection); + } + } + + private class FgCoparisonControllerListener implements FGControllerListener { + + @Override + public void dataChanged() { + graphChangedConsumer.accept(FgDisplay.this); + } + + @Override + public void userChangedLocation(ProgramLocation location, boolean vertexChanged) { + currentLocation = location; + locationConsumer.accept(location); + } + + @Override + public void userChangedSelection(ProgramSelection selection) { + // stub + } + + @Override + public void userSelectedText(String s) { + // stub + } + + @Override + public void userNavigated(ProgramLocation location) { + // stub + } + } + + private class DummyNavigatable implements Navigatable { + + private long id; + + DummyNavigatable() { + id = UniversalIdGenerator.nextID().getValue(); + } + + @Override + public long getInstanceID() { + return id; + } + + @Override + public boolean goTo(Program p, ProgramLocation pl) { + return false; + } + + @Override + public ProgramLocation getLocation() { + return null; + } + + @Override + public Program getProgram() { + return program; + } + + @Override + public LocationMemento getMemento() { + return new FgMemento(); // dummy + } + + @Override + public void setMemento(LocationMemento memento) { + // stub + } + + @Override + public Icon getNavigatableIcon() { + return null; + } + + @Override + public boolean isConnected() { + return false; + } + + @Override + public boolean supportsMarkers() { + return false; + } + + @Override + public void requestFocus() { + // stub + } + + @Override + public boolean isVisible() { + return true; + } + + @Override + public void setSelection(ProgramSelection selection) { + // stub + } + + @Override + public void setHighlight(ProgramSelection highlight) { + // stub + } + + @Override + public ProgramSelection getSelection() { + return null; + } + + @Override + public ProgramSelection getHighlight() { + return null; + } + + @Override + public String getTextSelection() { + return null; + } + + @Override + public void addNavigatableListener(NavigatableRemovalListener listener) { + // stub + } + + @Override + public void removeNavigatableListener(NavigatableRemovalListener listener) { + // stub + } + + @Override + public boolean isDisposed() { + return false; + } + + @Override + public boolean supportsHighlight() { + return false; + } + + @Override + public void setHighlightProvider(ListingHighlightProvider highlightProvider, + Program program) { + // stub + } + + @Override + public void removeHighlightProvider(ListingHighlightProvider highlightProvider, + Program p) { + // stub + } + + } + + private class FgMemento extends LocationMemento { + FgMemento() { + super((Program) null, null); + } + } + + private class FgDisplayProgramListener implements DomainObjectListener { + + private SwingUpdateManager updater = new SwingUpdateManager(500, 5000, () -> refresh()); + + @Override + public void domainObjectChanged(DomainObjectChangedEvent ev) { + updater.update(); + } + + public void dispose() { + updater.dispose(); + } + } + + private class FgServiceListener implements ServiceListener { + + @Override + public void serviceAdded(Class> interfaceClass, Object service) { + if (interfaceClass == ClipboardService.class) { + // if we decide to support copy/paste in this viewer, then the FGClipboardProvider + // needs to be taken out of the FGProvider and made independent. We would also need + // to refactor the ClipboardPlugin to not need a provider, instead adding a way to + // get a component, context and to add/remove actions. + } + else if (interfaceClass == ColorizingService.class) { + colorProvider = + new ToolBasedColorProvider(() -> program, (ColorizingService) service); + controller.refresh(true); + } + } + + @Override + public void serviceRemoved(Class> interfaceClass, Object service) { + if (interfaceClass == ColorizingService.class) { + colorProvider = new IndependentColorProvider(tool); + controller.refresh(true); + } + } + } + + private class FgOptions extends FunctionGraphOptions { + + @Override + public void setUseAnimation(boolean useAnimation) { + // don't allow this to be changed; animations seem like overkill for comparisons + } + + @Override + public void loadOptions(Options options) { + super.loadOptions(options); + + useAnimation = false; + } + } +} diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/FgDisplaySynchronizer.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/FgDisplaySynchronizer.java new file mode 100644 index 0000000000..68dbc2c135 --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/FgDisplaySynchronizer.java @@ -0,0 +1,57 @@ +/* ### + * 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.features.codecompare.functiongraph; + +import ghidra.app.util.viewer.listingpanel.ProgramLocationTranslator; +import ghidra.program.util.ListingAddressCorrelation; +import ghidra.program.util.ProgramLocation; +import ghidra.util.datastruct.Duo; +import ghidra.util.datastruct.Duo.Side; + +/** + * A class to synchronize locations between the left and right Function Graph comparison panels. + */ +class FgDisplaySynchronizer { + + private Duo displays; + private ProgramLocationTranslator locationTranslator; + + FgDisplaySynchronizer(Duo displays, ListingAddressCorrelation correlation) { + this.displays = displays; + this.locationTranslator = new ProgramLocationTranslator(correlation); + } + + void setLocation(Side side, ProgramLocation location) { + // Only set other side's cursor if we are coordinating right now. + Side otherSide = side.otherSide(); + ProgramLocation otherLocation = locationTranslator.getProgramLocation(otherSide, location); + if (otherLocation != null) { + displays.get(otherSide).setLocation(otherLocation); + } + } + + void sync(Side side) { + ProgramLocation programLocation = displays.get(side).getLocation(); + if (programLocation != null) { + setLocation(side, programLocation); + } + } + + void dispose() { + // this object should probably have a dispose() method + // locationTranslator.dispose(); + } +} diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/FunctionGraphCodeComparisonView.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/FunctionGraphCodeComparisonView.java new file mode 100644 index 0000000000..579a054ab5 --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/FunctionGraphCodeComparisonView.java @@ -0,0 +1,508 @@ +/* ### + * 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.features.codecompare.functiongraph; + +import static ghidra.util.datastruct.Duo.Side.*; + +import java.awt.Component; +import java.awt.event.MouseEvent; +import java.util.*; +import java.util.function.Consumer; + +import javax.swing.JComponent; + +import docking.ActionContext; +import docking.ComponentProvider; +import docking.action.DockingAction; +import ghidra.GhidraOptions; +import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider; +import ghidra.app.plugin.core.functiongraph.mvc.*; +import ghidra.app.util.viewer.format.FormatManager; +import ghidra.features.base.codecompare.listing.LinearAddressCorrelation; +import ghidra.features.base.codecompare.panel.CodeComparisonView; +import ghidra.features.codecompare.functiongraph.actions.*; +import ghidra.framework.options.SaveState; +import ghidra.framework.options.ToolOptions; +import ghidra.framework.plugintool.PluginTool; +import ghidra.graph.viewer.GraphSatelliteListener; +import ghidra.program.model.correlate.HashedFunctionAddressCorrelation; +import ghidra.program.model.listing.Function; +import ghidra.program.model.mem.MemoryAccessException; +import ghidra.program.util.ListingAddressCorrelation; +import ghidra.program.util.ProgramLocation; +import ghidra.util.HelpLocation; +import ghidra.util.datastruct.Duo; +import ghidra.util.datastruct.Duo.Side; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; +import help.Help; + +/** + * Provides a {@link CodeComparisonView} for displaying function graphs. + * + * Known Issues: + *
+ *
+ */ +public class FunctionGraphCodeComparisonView extends CodeComparisonView { + + public static final String NAME = "Function Graph View"; + + private static final String FORMAT_KEY = "FIELD_FORMAT"; + private static final String SHOW_POPUPS_KEY = "SHOW_POPUPS"; + private static final String SHOW_SATELLITE_KEY = "SHOW_SATELLITE"; + + private static final String LAYOUT_NAME = "LAYOUT_NAME"; + private static final String COMPLEX_LAYOUT_NAME = "COMPLEX_LAYOUT_NAME"; + private static final String LAYOUT_CLASS_NAME = "LAYOUT_CLASS_NAME"; + + private FgDisplaySynchronizer coordinator; + + private Duo- The options used by this API are the same as those for the Function Graph plugin. We + * may find that some of the options should not apply to this API. If true, then we would + * have to create a new options entry in the tool and a different options object for this + * API to use. + *
+ *- Each open panel will potentially update the state that is saved in the tool. This can + * lead to confusion when multiple open windows have different settings, as it is not + * clear which window's settings will get saved. + *
+ *- The views do not support copying, which is consistent with the other function comparison + * views. + *
+ *displays = new Duo<>(); + private Duo functions = new Duo<>(); + + private ListingAddressCorrelation addressCorrelator; + private boolean displaysLocked; + + private SaveState defaultSaveState; + private SaveState saveState; + private List actions = new ArrayList<>(); + private FgTogglePopupsAction showPopupsAction; + private FgToggleSatelliteAction showSatelliteAction; + + public FunctionGraphCodeComparisonView(String owner, PluginTool tool) { + super(owner, tool); + + Help.getHelpService() + .registerHelp(this, new HelpLocation(HELP_TOPIC, "FunctionGraph_Diff_View")); + + displays = buildDisplays(); + createActions(); + installSatelliteListeners(); + buildDefaultSaveState(); + + buildPanel(); + setSynchronizedScrolling(true); + } + + public String getOwner() { + return owner; + } + + public Duo getDisplays() { + return displays; + } + + /** + * Called by actions to signal that the user changed something worth saving. + */ + public void stateChanged() { + saveShowPopups(saveState); + saveShowSatellite(saveState); + saveLayout(saveState); + saveCustomFormat(saveState); + + if (!hasStateChanges()) { + // This implies the user has made changes, but those changes match the default settings. + // Clear the save state so no changes get written to the tool. + saveState.clear(); + } + + tool.setConfigChanged(true); + } + + private void buildDefaultSaveState() { + SaveState ss = new SaveState(); + saveShowPopups(ss); + saveLayout(ss); + saveCustomFormat(ss); + defaultSaveState = ss; + } + + private void saveShowPopups(SaveState ss) { + ss.putBoolean(SHOW_POPUPS_KEY, showPopupsAction.isSelected()); + } + + private void saveShowSatellite(SaveState ss) { + ss.putBoolean(SHOW_SATELLITE_KEY, showSatelliteAction.isSelected()); + } + + private void loadShowPopups(SaveState ss) { + FgDisplay leftDisplay = displays.get(LEFT); + FGController leftController = leftDisplay.getController(); + boolean currentShowPopups = leftController.arePopupsVisible(); + boolean savedShowPopups = ss.getBoolean(SHOW_POPUPS_KEY, currentShowPopups); + if (currentShowPopups == savedShowPopups) { + return; + } + + FgDisplay rightDisplay = displays.get(Side.RIGHT); + FGController rightController = rightDisplay.getController(); + + leftController.setPopupsVisible(savedShowPopups); + rightController.setPopupsVisible(savedShowPopups); + showPopupsAction.setSelected(savedShowPopups); + } + + private void loadShowSatellite(SaveState ss) { + FgDisplay leftDisplay = displays.get(LEFT); + FGController leftController = leftDisplay.getController(); + boolean currentShowSatellite = leftController.isSatelliteVisible(); + boolean savedShowSatellite = ss.getBoolean(SHOW_SATELLITE_KEY, currentShowSatellite); + if (currentShowSatellite == savedShowSatellite) { + return; + } + + FgDisplay rightDisplay = displays.get(Side.RIGHT); + FGController rightController = rightDisplay.getController(); + + leftController.setSatelliteVisible(savedShowSatellite); + rightController.setSatelliteVisible(savedShowSatellite); + showSatelliteAction.setSelected(savedShowSatellite); + } + + private void saveLayout(SaveState ss) { + FgDisplay display = displays.get(LEFT); + FGController controller = display.getController(); + FGLayoutProvider layout = controller.getLayoutProvider(); + + SaveState layoutState = new SaveState(COMPLEX_LAYOUT_NAME); + String layoutName = layout.getLayoutName(); + layoutState.putString(LAYOUT_NAME, layoutName); + layoutState.putString(LAYOUT_CLASS_NAME, layout.getClass().getName()); + ss.putSaveState(COMPLEX_LAYOUT_NAME, layoutState); + } + + private void loadLayout(SaveState ss) { + + SaveState layoutState = saveState.getSaveState(COMPLEX_LAYOUT_NAME); + if (layoutState == null) { + return; + } + + FgDisplay leftDisplay = displays.get(LEFT); + FGController leftController = leftDisplay.getController(); + FGLayoutProvider layout = leftController.getLayoutProvider(); + + String savedLayoutName = layoutState.getString(LAYOUT_NAME, layout.getLayoutName()); + if (layout.getLayoutName().equals(savedLayoutName)) { + return; // already set + } + + FgDisplay rightDisplay = displays.get(Side.RIGHT); + FGController rightController = rightDisplay.getController(); + + FgEnv env = leftController.getEnv(); + List layoutProviders = new ArrayList<>(env.getLayoutProviders()); + for (FGLayoutProvider layoutProvider : layoutProviders) { + String providerName = layoutProvider.getLayoutName(); + if (providerName.equals(savedLayoutName)) { + leftController.changeLayout(layoutProvider); + rightController.changeLayout(layoutProvider); + break; + } + } + } + + private void saveCustomFormat(SaveState ss) { + FgDisplay display = displays.get(LEFT); + FGController controller = display.getController(); + FormatManager format = controller.getMinimalFormatManager(); + SaveState formatState = new SaveState(); + format.saveState(formatState); + ss.putSaveState(FORMAT_KEY, formatState); + } + + private void loadCustomFormat(SaveState ss) { + + SaveState formatState = ss.getSaveState(FORMAT_KEY); + if (formatState == null) { + return; + } + + ToolOptions displayOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_DISPLAY); + ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS); + + FgDisplay leftDisplay = displays.get(LEFT); + FGController leftController = leftDisplay.getController(); + FormatManager format = leftController.getMinimalFormatManager(); + SaveState testState = new SaveState(); + format.saveState(testState); + + if (equals(testState, formatState)) { + return; + } + + FormatManager formatManager = new FormatManager(displayOptions, fieldOptions); + formatManager.readState(formatState); + leftController.updateMinimalFormatManager(formatManager); + + FgDisplay rightDisplay = displays.get(Side.RIGHT); + FGController rightController = rightDisplay.getController(); + rightController.updateMinimalFormatManager(formatManager); + } + + @Override + public void setSaveState(SaveState ss) { + this.saveState = ss; + + if (!hasStateChanges()) { + return; // the given state matches the default state; nothing to do + } + + loadShowPopups(ss); + loadShowSatellite(ss); + loadLayout(ss); + loadCustomFormat(ss); + } + + private boolean hasStateChanges() { + if (!saveState.isEmpty()) { + return !equals(saveState, defaultSaveState); + } + return false; + } + + private boolean equals(SaveState state1, SaveState state2) { + String s1 = state1.toString(); + String s2 = state2.toString(); + return Objects.equals(s1, s2); + } + + private void createActions() { + + // Note: many of these actions are similar to what is in the main Function Graph. The way + // this class is coded, the actions do not share keybindings. This is something we may wish + // to change in the future by making the key binding type sharable. + + // Both displays have the same actions they get from the Function Graph API. We will add + // them to the comparison provider. We only need to add one set of actions, since they are + // the same for both providers. + FgDisplay left = displays.get(LEFT); + actions.add(new FgResetGraphAction(left)); + + FgDisplay right = displays.get(RIGHT); + actions.add(new FgResetGraphAction(right)); + + showPopupsAction = new FgTogglePopupsAction(this); + FGController controller = left.getController(); + boolean showPopups = controller.arePopupsVisible(); + showPopupsAction.setSelected(showPopups); + + showSatelliteAction = new FgToggleSatelliteAction(this); + boolean showSatellite = controller.isSatelliteVisible(); + showSatelliteAction.setSelected(showSatellite); + + actions.add(showSatelliteAction); + actions.add(showPopupsAction); + actions.add(new FgRelayoutAction(this)); + actions.add(new FgChooseFormatAction(this)); + } + + private void installSatelliteListeners() { + + FgDisplay left = displays.get(LEFT); + FgDisplay right = displays.get(RIGHT); + FGController leftController = left.getController(); + FGController rightController = right.getController(); + + GraphSatelliteListener listener = new GraphSatelliteListener() { + + @Override + public void satelliteVisibilityChanged(boolean docked, boolean visible) { + if (visible) { + leftController.setSatelliteVisible(true); + rightController.setSatelliteVisible(true); + } + showSatelliteAction.setSelected(visible); + stateChanged(); + } + }; + + FGView lv = leftController.getView(); + FGView rv = rightController.getView(); + lv.addSatelliteListener(listener); + rv.addSatelliteListener(listener); + } + + @Override + public List getActions() { + List superActions = super.getActions(); + superActions.addAll(0, actions); + return actions; + } + + @Override + protected void comparisonDataChanged() { + + maybeLoadFunction(LEFT, comparisonData.get(LEFT).getFunction()); + maybeLoadFunction(RIGHT, comparisonData.get(RIGHT).getFunction()); + + addressCorrelator = createCorrelator(); + // updateProgramViews(); + updateCoordinator(); + //initializeCursorMarkers(); + updateActionEnablement(); + validate(); + } + + private ListingAddressCorrelation createCorrelator() { + Function f1 = getFunction(LEFT); + Function f2 = getFunction(RIGHT); + if (f1 != null && f2 != null) { + try { + return new HashedFunctionAddressCorrelation(f1, f2, TaskMonitor.DUMMY); + } + catch (CancelledException | MemoryAccessException e) { + // fall back to linear address correlation + } + } + if (comparisonData.get(LEFT).isEmpty() || comparisonData.get(RIGHT).isEmpty()) { + return null; + } + return new LinearAddressCorrelation(comparisonData); + } + + private void updateCoordinator() { + if (coordinator != null) { + coordinator.dispose(); + coordinator = null; + } + if (displaysLocked) { + coordinator = new FgDisplaySynchronizer(displays, addressCorrelator); + coordinator.sync(activeSide); + } + } + + private void maybeLoadFunction(Side side, Function function) { + // we keep a local copy of the function so we know if it is already decompiled + if (functions.get(side) == function) { + return; + } + + // Clear the scroll info and highlight info to prevent unnecessary highlighting, etc. + loadFunction(side, null); + loadFunction(side, function); + + } + + private void loadFunction(Side side, Function function) { + if (functions.get(side) != function) { + functions = functions.with(side, function); + displays.get(side).showFunction(function); + } + } + + private Duo buildDisplays() { + + // The function graph's display is not ready until a graph is loaded. It also gets rebuilt + // each time the graph is reloaded. To correctly install listeners, we need to update them + // as the graph is rebuilt. + Consumer graphChangedCallback = display -> { + Side side = getSide(display); + addMouseAndFocusListeners(side); + }; + + FgDisplay leftDisplay = + new FgDisplay(this, l -> locationChanged(LEFT, l), graphChangedCallback); + FgDisplay rightDisplay = + new FgDisplay(this, l -> locationChanged(RIGHT, l), graphChangedCallback); + + return new Duo<>(leftDisplay, rightDisplay); + } + + private Side getSide(FgDisplay display) { + FgDisplay leftDisplay = displays.get(LEFT); + if (display == leftDisplay) { + return LEFT; + } + return RIGHT; + } + + private void locationChanged(Side side, ProgramLocation location) { + if (coordinator != null) { + coordinator.setLocation(side, location); + } + } + + @Override + public String getName() { + return NAME; + } + + @Override + public void dispose() { + setSynchronizedScrolling(false); // disposes any exiting coordinator + displays.each(FgDisplay::dispose); + } + + /** + * Gets the display from the active side. + * @return the active display + */ + public FgDisplay getActiveDisplay() { + return displays.get(activeSide); + } + + @Override + public ActionContext getActionContext(ComponentProvider provider, MouseEvent event) { + FgDisplay display = getActiveDisplay(); + Component component = event != null ? event.getComponent() + : display.getComponent(); + boolean isLeft = activeSide == LEFT; + return new FgComparisonContext(provider, this, display, component, isLeft); + } + + @Override + public void updateActionEnablement() { + // stub + } + + @Override + public void setSynchronizedScrolling(boolean synchronize) { + if (coordinator != null) { + coordinator.dispose(); + coordinator = null; + } + + displaysLocked = synchronize; + if (displaysLocked) { + coordinator = new FgDisplaySynchronizer(displays, addressCorrelator); + coordinator.sync(activeSide); + } + } + + @Override + public JComponent getComparisonComponent(Side side) { + return displays.get(side).getComponent(); + } + + public boolean isBusy() { + return displays.get(LEFT).isBusy() || displays.get(RIGHT).isBusy(); + } +} diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/AbstractFgAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/AbstractFgAction.java new file mode 100644 index 0000000000..ceec37821b --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/AbstractFgAction.java @@ -0,0 +1,43 @@ +/* ### + * 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.features.codecompare.functiongraph.actions; + +import docking.ActionContext; +import docking.action.DockingAction; +import ghidra.features.codecompare.functiongraph.FgComparisonContext; +import ghidra.features.codecompare.functiongraph.FgDisplay; + +public abstract class AbstractFgAction extends DockingAction { + + private FgDisplay display; + + protected AbstractFgAction(FgDisplay display, String name) { + super(name, display.getOwner()); + this.display = display; + } + + protected boolean isMyDisplay(ActionContext context) { + if (!(context instanceof FgComparisonContext fgContext)) { + return false; + } + return fgContext.getDisplay() == display; + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return isMyDisplay(context); + } +} diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/FgChooseFormatAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/FgChooseFormatAction.java new file mode 100644 index 0000000000..82a0dd22de --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/FgChooseFormatAction.java @@ -0,0 +1,69 @@ +/* ### + * 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.features.codecompare.functiongraph.actions; + +import docking.ActionContext; +import docking.action.DockingAction; +import docking.action.MenuData; +import ghidra.app.plugin.core.functiongraph.mvc.FGController; +import ghidra.app.util.viewer.format.FormatManager; +import ghidra.features.codecompare.functiongraph.*; +import ghidra.util.HelpLocation; +import ghidra.util.datastruct.Duo; +import ghidra.util.datastruct.Duo.Side; + +public class FgChooseFormatAction extends DockingAction { + + private FunctionGraphCodeComparisonView fgProvider; + + public FgChooseFormatAction(FunctionGraphCodeComparisonView fgProvider) { + super("Edit Code Block Fields", fgProvider.getOwner()); + this.fgProvider = fgProvider; + + setPopupMenuData(new MenuData(new String[] { "Edit Fields" })); + + setHelpLocation( + new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Format")); + } + + @Override + public void actionPerformed(ActionContext context) { + + Duo displays = fgProvider.getDisplays(); + FgDisplay leftDisplay = displays.get(Side.LEFT); + FGController leftController = leftDisplay.getController(); + leftController.showFormatChooser(); + + FormatManager leftFormatManager = leftController.getMinimalFormatManager(); + FgDisplay rightDisplay = displays.get(Side.RIGHT); + FGController rightController = rightDisplay.getController(); + rightController.updateMinimalFormatManager(leftFormatManager); + + fgProvider.stateChanged(); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + if (!(context instanceof FgComparisonContext)) { + return false; + } + + Duo displays = fgProvider.getDisplays(); + FgDisplay leftDisplay = displays.get(Side.LEFT); + FGController controller = leftDisplay.getController(); + return controller.hasResults(); + } +} diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/FgRelayoutAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/FgRelayoutAction.java new file mode 100644 index 0000000000..91f5c3968f --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/FgRelayoutAction.java @@ -0,0 +1,79 @@ +/* ### + * 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.features.codecompare.functiongraph.actions; + +import java.util.ArrayList; +import java.util.List; + +import docking.ActionContext; +import docking.action.*; +import docking.widgets.dialogs.ObjectChooserDialog; +import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider; +import ghidra.app.plugin.core.functiongraph.mvc.FGController; +import ghidra.app.plugin.core.functiongraph.mvc.FgEnv; +import ghidra.features.codecompare.functiongraph.*; +import ghidra.framework.plugintool.PluginTool; +import ghidra.util.HelpLocation; +import ghidra.util.datastruct.Duo; +import ghidra.util.datastruct.Duo.Side; + +public class FgRelayoutAction extends DockingAction { + + private FunctionGraphCodeComparisonView fgProvider; + + public FgRelayoutAction(FunctionGraphCodeComparisonView fgProvider) { + super("Relayout Graph", fgProvider.getOwner(), KeyBindingType.SHARED); + this.fgProvider = fgProvider; + + setPopupMenuData(new MenuData(new String[] { "Relayout Graph" })); + + setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout")); + } + + @Override + public void actionPerformed(ActionContext context) { + + Duo displays = fgProvider.getDisplays(); + FgDisplay leftDisplay = displays.get(Side.LEFT); + FGController leftController = leftDisplay.getController(); + FgEnv env = leftController.getEnv(); + List layoutProviders = new ArrayList<>(env.getLayoutProviders()); + ObjectChooserDialog dialog = + new ObjectChooserDialog<>("Choose Layout", FGLayoutProvider.class, layoutProviders, + "getLayoutName"); + FGLayoutProvider currentLayout = leftController.getLayoutProvider(); + dialog.setSelectedObject(currentLayout); + PluginTool tool = env.getTool(); + tool.showDialog(dialog); + + FGLayoutProvider layoutProvider = dialog.getSelectedObject(); + if (layoutProvider == null) { + return; // cancelled + } + leftController.changeLayout(layoutProvider); + + FgDisplay rightDisplay = displays.get(Side.RIGHT); + FGController rightController = rightDisplay.getController(); + rightController.changeLayout(layoutProvider); + + fgProvider.stateChanged(); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return context instanceof FgComparisonContext; + } +} diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/FgResetGraphAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/FgResetGraphAction.java new file mode 100644 index 0000000000..ce4921fe41 --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/FgResetGraphAction.java @@ -0,0 +1,62 @@ +/* ### + * 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.features.codecompare.functiongraph.actions; + +import javax.swing.JComponent; + +import docking.ActionContext; +import docking.action.MenuData; +import docking.widgets.OptionDialog; +import ghidra.app.plugin.core.functiongraph.mvc.FGController; +import ghidra.features.codecompare.functiongraph.FgDisplay; +import ghidra.util.HelpLocation; + +public class FgResetGraphAction extends AbstractFgAction { + + private FgDisplay display; + + public FgResetGraphAction(FgDisplay display) { + super(display, "Reset Graph"); + this.display = display; + + setPopupMenuData(new MenuData(new String[] { "Reset Graph" })); + + setHelpLocation( + new HelpLocation("FunctionGraphPlugin", "Function_Graph_Reload_Graph")); + } + + @Override + public void actionPerformed(ActionContext context) { + FGController controller = display.getController(); + JComponent component = controller.getViewComponent(); + int choice = OptionDialog.showYesNoDialog(component, "Reset Graph?", + "Erase all vertex position and grouping information?"); + if (choice != OptionDialog.YES_OPTION) { + return; + } + + controller.resetGraph(); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + if (!super.isEnabledForContext(context)) { + return false; + } + FGController controller = display.getController(); + return controller.hasResults(); + } +} diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/FgTogglePopupsAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/FgTogglePopupsAction.java new file mode 100644 index 0000000000..39525399ac --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/FgTogglePopupsAction.java @@ -0,0 +1,71 @@ +/* ### + * 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.features.codecompare.functiongraph.actions; + +import docking.ActionContext; +import docking.action.MenuData; +import docking.action.ToggleDockingAction; +import ghidra.app.plugin.core.functiongraph.mvc.FGController; +import ghidra.features.codecompare.functiongraph.*; +import ghidra.util.HelpLocation; +import ghidra.util.datastruct.Duo; +import ghidra.util.datastruct.Duo.Side; + +/** + * An action to toggle popup enablement for the Function Graph comparison views. + */ +public class FgTogglePopupsAction extends ToggleDockingAction { + + private FunctionGraphCodeComparisonView fgProvider; + + public FgTogglePopupsAction(FunctionGraphCodeComparisonView fgProvider) { + super("Display Popup Windows", fgProvider.getOwner()); + this.fgProvider = fgProvider; + + setPopupMenuData(new MenuData(new String[] { "Display Popup Windows" })); + + setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Popups")); + } + + @Override + public void actionPerformed(ActionContext context) { + + Duo displays = fgProvider.getDisplays(); + FgDisplay leftDisplay = displays.get(Side.LEFT); + + FGController controller = leftDisplay.getController(); + boolean visible = isSelected(); + controller.setPopupsVisible(visible); + + FgDisplay rightDisplay = displays.get(Side.RIGHT); + FGController rightController = rightDisplay.getController(); + rightController.setPopupsVisible(visible); + + fgProvider.stateChanged(); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + if (!(context instanceof FgComparisonContext)) { + return false; + } + + Duo displays = fgProvider.getDisplays(); + FgDisplay leftDisplay = displays.get(Side.LEFT); + FGController controller = leftDisplay.getController(); + return controller.hasResults(); + } +} diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/FgToggleSatelliteAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/FgToggleSatelliteAction.java new file mode 100644 index 0000000000..a82df30218 --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/functiongraph/actions/FgToggleSatelliteAction.java @@ -0,0 +1,71 @@ +/* ### + * 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.features.codecompare.functiongraph.actions; + +import docking.ActionContext; +import docking.action.MenuData; +import docking.action.ToggleDockingAction; +import ghidra.app.plugin.core.functiongraph.mvc.FGController; +import ghidra.features.codecompare.functiongraph.*; +import ghidra.util.HelpLocation; +import ghidra.util.datastruct.Duo; +import ghidra.util.datastruct.Duo.Side; + +/** + * An action to toggle satellite enablement for the Function Graph comparison views. + */ +public class FgToggleSatelliteAction extends ToggleDockingAction { + + private FunctionGraphCodeComparisonView fgProvider; + + public FgToggleSatelliteAction(FunctionGraphCodeComparisonView fgProvider) { + super("Display Satellite View", fgProvider.getOwner()); + this.fgProvider = fgProvider; + + setPopupMenuData(new MenuData(new String[] { "Display Satellite" })); + + setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Satellite_View")); + } + + @Override + public void actionPerformed(ActionContext context) { + + Duo displays = fgProvider.getDisplays(); + FgDisplay leftDisplay = displays.get(Side.LEFT); + + FGController leftController = leftDisplay.getController(); + boolean visible = isSelected(); + leftController.setSatelliteVisible(visible); + + FgDisplay rightDisplay = displays.get(Side.RIGHT); + FGController rightController = rightDisplay.getController(); + rightController.setSatelliteVisible(visible); + + fgProvider.stateChanged(); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + if (!(context instanceof FgComparisonContext)) { + return false; + } + + Duo displays = fgProvider.getDisplays(); + FgDisplay leftDisplay = displays.get(Side.LEFT); + FGController controller = leftDisplay.getController(); + return controller.hasResults(); + } +} 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 98959a5ca6..1edba91a15 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 @@ -28,7 +28,10 @@ import ghidra.app.plugin.ProgramPlugin; import ghidra.app.services.FunctionComparisonService; import ghidra.features.base.codecompare.model.AnyToAnyFunctionComparisonModel; import ghidra.features.base.codecompare.model.FunctionComparisonModel; +import ghidra.features.base.codecompare.panel.FunctionComparisonPanel; +import ghidra.features.base.codecompare.panel.FunctionComparisonState; import ghidra.framework.model.*; +import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.util.PluginStatus; @@ -66,8 +69,12 @@ public class FunctionComparisonPlugin extends ProgramPlugin private Set providers = new HashSet<>(); private FunctionComparisonProvider lastActiveProvider; + // There is one state shared between all providers and CodeComparison views + private FunctionComparisonState comparisonState; + public FunctionComparisonPlugin(PluginTool tool) { super(tool); + comparisonState = new FunctionComparisonState(tool); createActions(); } @@ -87,14 +94,16 @@ public class FunctionComparisonPlugin extends ProgramPlugin foreEachProvider(p -> p.programClosed(program)); } - /** - * Overridden to listen for two event types: - * Object Restored: In the event of a redo/undo that affects a function - * being shown in the comparison provider, this will allow tell the provider - * to reload - *Object Removed: If a function is deleted, this will tell the provider - * to purge it from the view - */ + @Override + public void writeConfigState(SaveState saveState) { + comparisonState.writeConfigState(saveState); + } + + @Override + public void readConfigState(SaveState saveState) { + comparisonState.readConfigState(saveState); + } + @Override public void domainObjectChanged(DomainObjectChangedEvent ev) { for (int i = 0; i < ev.numRecords(); ++i) { @@ -210,7 +219,7 @@ public class FunctionComparisonPlugin extends ProgramPlugin private FunctionComparisonProvider createProvider(FunctionComparisonModel model, Callback closeListener) { FunctionComparisonProvider provider = - new FunctionComparisonProvider(this, model, closeListener); + new FunctionComparisonProvider(this, model, closeListener, comparisonState); providers.add(provider); return provider; @@ -218,7 +227,8 @@ public class FunctionComparisonPlugin extends ProgramPlugin //================================================================================================== // Service Methods -//================================================================================================== +//================================================================================================== + @Override public void createComparison(Collectionfunctions) { if (functions.isEmpty()) { @@ -254,4 +264,8 @@ public class FunctionComparisonPlugin extends ProgramPlugin Swing.runLater(() -> createProvider(model, closeListener)); } + @Override + public FunctionComparisonPanel createComparisonViewer() { + return new FunctionComparisonPanel(tool, name, comparisonState); + } } 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 c40d04cf17..fd90ed84fb 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 @@ -35,11 +35,9 @@ import ghidra.app.plugin.core.functionwindow.FunctionRowObject; import ghidra.app.plugin.core.functionwindow.FunctionTableModel; import ghidra.app.services.*; import ghidra.app.util.viewer.listingpanel.ListingPanel; -import ghidra.features.base.codecompare.listing.ListingCodeComparisonPanel; +import ghidra.features.base.codecompare.listing.ListingCodeComparisonView; import ghidra.features.base.codecompare.model.*; -import ghidra.features.base.codecompare.panel.CodeComparisonPanel; -import ghidra.features.base.codecompare.panel.FunctionComparisonPanel; -import ghidra.framework.options.SaveState; +import ghidra.features.base.codecompare.panel.*; import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; @@ -50,9 +48,9 @@ import util.CollectionUtils; import utility.function.Callback; /** - * Dockable provider that displays function comparisons Clients create/modify - * these comparisons using the {@link FunctionComparisonService}, which in turn - * creates instances of this provider as-needed. + * Dockable provider that displays function comparisons. Clients create/modify these comparisons + * using the {@link FunctionComparisonService}, which in turn creates instances of this provider + * as-needed. */ public class FunctionComparisonProvider extends ComponentProviderAdapter implements PopupActionProvider, FunctionComparisonModelListener { @@ -80,13 +78,13 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter private ToggleDockingAction navigateToAction; public FunctionComparisonProvider(FunctionComparisonPlugin plugin, - FunctionComparisonModel model, Callback closeListener) { + FunctionComparisonModel model, Callback closeListener, FunctionComparisonState state) { super(plugin.getTool(), "Function Comparison Provider", plugin.getName()); this.plugin = plugin; this.model = model; this.closeListener = Callback.dummyIfNull(closeListener); - functionComparisonPanel = new MultiFunctionComparisonPanel(this, tool, model); + functionComparisonPanel = new MultiFunctionComparisonPanel(this, tool, model, state); model.addFunctionComparisonModelListener(this); setTabText(functionComparisonPanel.getDescription()); @@ -120,8 +118,8 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter @Override public ActionContext getActionContext(MouseEvent event) { - CodeComparisonPanel currentComponent = - functionComparisonPanel.getCurrentComponent(); + CodeComparisonView currentComponent = + functionComparisonPanel.getCurrentView(); return currentComponent.getActionContext(this, event); } @@ -153,10 +151,10 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter @Override public List getPopupActions(Tool t, ActionContext context) { if (context.getComponentProvider() == this) { - ListingCodeComparisonPanel dualListingPanel = - functionComparisonPanel.getDualListingPanel(); - if (dualListingPanel != null) { - ListingPanel leftPanel = dualListingPanel.getListingPanel(LEFT); + ListingCodeComparisonView dualListingView = + functionComparisonPanel.getDualListingView(); + if (dualListingView != null) { + ListingPanel leftPanel = dualListingView.getListingPanel(LEFT); return leftPanel.getHeaderActions(getOwner()); } } @@ -213,29 +211,8 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter * @param program the program that was restored (undo/redo) */ public void programRestored(Program program) { - CodeComparisonPanel comparePanel = - functionComparisonPanel.getCurrentComponent(); - comparePanel.programRestored(program); - } - - /** - * Restores the function comparison providers components to the indicated - * saved configuration state - * - * @param saveState the configuration state to restore - */ - public void readConfigState(SaveState saveState) { - functionComparisonPanel.readConfigState(getName(), saveState); - } - - /** - * Saves the current configuration state of the components that compose - * the function comparison provider - * - * @param saveState the new configuration state - */ - public void writeConfigState(SaveState saveState) { - functionComparisonPanel.writeConfigState(getName(), saveState); + CodeComparisonView view = functionComparisonPanel.getCurrentView(); + view.programRestored(program); } @Override @@ -291,8 +268,8 @@ 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" + + .description(HTMLUtilities.toWrappedHTML("Toggle On to navigate the " + + "tool to the selected function 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) @@ -368,8 +345,7 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter } /** - * Gets actions specific to the code comparison panel and adds them to this - * provider + * Gets actions specific to the code comparison panel and adds them to this provider */ private void addSpecificCodeComparisonActions() { DockingAction[] actions = functionComparisonPanel.getCodeComparisonActions(); @@ -378,8 +354,12 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter } } - public CodeComparisonPanel getCodeComparisonPanelByName(String name) { - return functionComparisonPanel.getCodeComparisonPanelByName(name); + public CodeComparisonView getCodeComparisonView(String name) { + return functionComparisonPanel.getCodeComparisonView(name); + } + + public void selectComparisonView(String name) { + functionComparisonPanel.selectComparisonView(name); } private void dispose() { diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/plugin/MultiFunctionComparisonPanel.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/plugin/MultiFunctionComparisonPanel.java index 8d743e1677..7636853abe 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/plugin/MultiFunctionComparisonPanel.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/features/codecompare/plugin/MultiFunctionComparisonPanel.java @@ -26,8 +26,7 @@ import javax.swing.*; import docking.widgets.list.GComboBoxCellRenderer; import ghidra.features.base.codecompare.model.FunctionComparisonModel; import ghidra.features.base.codecompare.model.FunctionComparisonModelListener; -import ghidra.features.base.codecompare.panel.CodeComparisonPanel; -import ghidra.features.base.codecompare.panel.FunctionComparisonPanel; +import ghidra.features.base.codecompare.panel.*; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Function; @@ -60,16 +59,17 @@ public class MultiFunctionComparisonPanel extends FunctionComparisonPanel * @param provider the comparison provider associated with this panel * @param tool the active plugin tool * @param model the comparison data model + * @param state the comparison save state */ public MultiFunctionComparisonPanel(FunctionComparisonProvider provider, PluginTool tool, - FunctionComparisonModel model) { - super(tool, provider.getName()); + FunctionComparisonModel model, FunctionComparisonState state) { + super(tool, provider.getName(), state); this.model = model; model.addFunctionComparisonModelListener(this); buildComboPanels(); - getComparisonPanels().forEach(p -> p.setShowDataTitles(false)); + getComparisonView().forEach(p -> p.setShowDataTitles(false)); setPreferredSize(new Dimension(1200, 600)); modelDataChanged(); } @@ -94,7 +94,7 @@ public class MultiFunctionComparisonPanel extends FunctionComparisonPanel } Side getActiveSide() { - CodeComparisonPanel currentComponent = getCurrentComponent(); + CodeComparisonView currentComponent = getCurrentView(); return currentComponent.getActiveSide(); } diff --git a/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/decompile/AbstractDualDecompilerTest.java b/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/decompile/AbstractDualDecompilerTest.java index 2a8cb73968..c43eacb0ab 100644 --- a/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/decompile/AbstractDualDecompilerTest.java +++ b/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/decompile/AbstractDualDecompilerTest.java @@ -28,7 +28,7 @@ import ghidra.app.decompiler.component.ClangTextField; import ghidra.app.decompiler.component.DecompilerPanel; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.function.FunctionPlugin; -import ghidra.features.base.codecompare.panel.CodeComparisonPanel; +import ghidra.features.base.codecompare.panel.CodeComparisonView; import ghidra.features.codecompare.plugin.FunctionComparisonPlugin; import ghidra.features.codecompare.plugin.FunctionComparisonProvider; import ghidra.program.model.listing.Function; @@ -61,10 +61,10 @@ public abstract class AbstractDualDecompilerTest extends AbstractGhidraHeadedInt return waitForComponentProvider(FunctionComparisonProvider.class); } - protected DecompilerCodeComparisonPanel findDecompilerPanel( + protected DecompilerCodeComparisonView findDecompilerPanel( FunctionComparisonProvider provider) { - for (CodeComparisonPanel panel : provider.getComponent().getComparisonPanels()) { - if (panel instanceof DecompilerCodeComparisonPanel decompPanel) { + for (CodeComparisonView p : provider.getComponent().getComparisonView()) { + if (p instanceof DecompilerCodeComparisonView decompPanel) { return decompPanel; } } @@ -72,24 +72,26 @@ public abstract class AbstractDualDecompilerTest extends AbstractGhidraHeadedInt return null; } - protected void setActivePanel(FunctionComparisonProvider provider, CodeComparisonPanel panel) { - runSwing(() -> provider.getComponent().setCurrentTabbedComponent(panel.getName())); + protected void setActivePanel(FunctionComparisonProvider provider, + CodeComparisonView comparisonProvider) { + runSwing( + () -> provider.getComponent().setCurrentTabbedComponent(comparisonProvider.getName())); waitForSwing(); } - protected void waitForDecompile(DecompilerCodeComparisonPanel panel) { + protected void waitForDecompile(DecompilerCodeComparisonView panel) { waitForSwing(); waitForCondition(() -> !panel.isBusy()); waitForSwing(); } - protected DecompilerPanel getDecompSide(DecompilerCodeComparisonPanel panel, Side side) { + protected DecompilerPanel getDecompSide(DecompilerCodeComparisonView panel, Side side) { CDisplay sideDisplay = side == Side.LEFT ? panel.getLeftPanel() : panel.getRightPanel(); return sideDisplay.getDecompilerPanel(); } // 1-indexed lines - protected ClangToken setDecompLocation(DecompilerCodeComparisonPanel comparePanel, Side side, + protected ClangToken setDecompLocation(DecompilerCodeComparisonView comparePanel, Side side, int line, int charPos) { DecompilerPanel panel = getDecompSide(comparePanel, side); FieldPanel fp = panel.getFieldPanel(); @@ -107,7 +109,7 @@ public abstract class AbstractDualDecompilerTest extends AbstractGhidraHeadedInt } // Get the token under the cursor at the given side - protected ClangToken getCurrentToken(DecompilerCodeComparisonPanel comparePanel, Side side) { + protected ClangToken getCurrentToken(DecompilerCodeComparisonView comparePanel, Side side) { DecompilerPanel panel = getDecompSide(comparePanel, side); FieldLocation loc = panel.getCursorPosition(); int lineNumber = loc.getIndex().intValue(); diff --git a/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/decompile/DualDecompilerActionTest.java b/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/decompile/DualDecompilerActionTest.java index 93cf92cca3..bb11cda724 100644 --- a/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/decompile/DualDecompilerActionTest.java +++ b/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/decompile/DualDecompilerActionTest.java @@ -169,7 +169,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest { int col; ClangToken currentToken; - DecompilerCodeComparisonPanel uncorrelatedPanel = + DecompilerCodeComparisonView uncorrelatedPanel = preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedCplusDemangle); DockingActionIf localNameTransferAction = getLocalAction(provider, actionName); assertNotNull(localNameTransferAction); @@ -182,7 +182,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest { assertEquals("demangler", currentToken.getText()); assertNotEnabled(localNameTransferAction, getProviderContext()); - DecompilerCodeComparisonPanel correlatedPanel = + DecompilerCodeComparisonView correlatedPanel = preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedMain); // Recreated provider, need to get new handle on action localNameTransferAction = getLocalAction(provider, actionName); @@ -225,7 +225,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest { int col; ClangToken currentToken; - DecompilerCodeComparisonPanel uncorrelatedPanel = + DecompilerCodeComparisonView uncorrelatedPanel = preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedCplusDemangle); DockingActionIf globalNameTransferAction = getLocalAction(provider, actionName); assertNotNull(globalNameTransferAction); @@ -238,7 +238,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest { assertEquals("program_name", currentToken.getText()); assertNotEnabled(globalNameTransferAction, getProviderContext()); - DecompilerCodeComparisonPanel correlatedPanel = + DecompilerCodeComparisonView correlatedPanel = preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedMain); // Recreated provider, need to get new handle on action globalNameTransferAction = getLocalAction(provider, actionName); @@ -280,7 +280,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest { int col; ClangToken currentToken; - DecompilerCodeComparisonPanel uncorrelatedPanel = + DecompilerCodeComparisonView uncorrelatedPanel = preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedCplusDemangle); DockingActionIf typeTransferAction = getLocalAction(provider, actionName); assertNotNull(typeTransferAction); @@ -301,7 +301,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest { assertEquals("program_name", currentToken.getText()); assertNotEnabled(typeTransferAction, getProviderContext()); - DecompilerCodeComparisonPanel correlatedPanel = + DecompilerCodeComparisonView correlatedPanel = preparePanel(funcDemangler24DebugMain, funcDemangler24StrippedMain); // Recreated provider, need to get new handle on action typeTransferAction = getLocalAction(provider, actionName); @@ -436,13 +436,13 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest { */ @Test - public void testFullStructTypeTransferAction() throws RuntimeException { + public void testFullStructTypeTransferAction() { final String actionName = ApplyVariableTypeFromMatchedTokensAction.ACTION_NAME; int line; int col; ClangToken currentToken; - DecompilerCodeComparisonPanel correlatedPanel = + DecompilerCodeComparisonView correlatedPanel = preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle); DockingActionIf typeTransferAction = getLocalAction(provider, actionName); assertNotNull(typeTransferAction); @@ -484,7 +484,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest { int col; ClangToken currentToken; - DecompilerCodeComparisonPanel correlatedPanel = + DecompilerCodeComparisonView correlatedPanel = preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle); DockingActionIf typeTransferAction = getLocalAction(provider, actionName); assertNotNull(typeTransferAction); @@ -524,7 +524,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest { int col; ClangToken currentToken; - DecompilerCodeComparisonPanel uncorrelatedPanel = + DecompilerCodeComparisonView uncorrelatedPanel = preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedMain); DockingActionIf calleeNameTransferAction = getLocalAction(provider, actionName); assertNotNull(calleeNameTransferAction); @@ -537,7 +537,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest { assertEquals("xstrdup", currentToken.getText()); assertNotEnabled(calleeNameTransferAction, getProviderContext()); - DecompilerCodeComparisonPanel correlatedPanel = + DecompilerCodeComparisonView correlatedPanel = preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle); // Recreated provider, need to get new handle on action calleeNameTransferAction = getLocalAction(provider, actionName); @@ -582,7 +582,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest { int col; ClangToken currentToken; - DecompilerCodeComparisonPanel uncorrelatedPanel = + DecompilerCodeComparisonView uncorrelatedPanel = preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedMain); DockingActionIf calleeFullSignatureTransferAction = getLocalAction(provider, actionName); assertNotNull(calleeFullSignatureTransferAction); @@ -595,7 +595,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest { assertEquals("xstrdup", currentToken.getText()); assertNotEnabled(calleeFullSignatureTransferAction, getProviderContext()); - DecompilerCodeComparisonPanel correlatedPanel = + DecompilerCodeComparisonView correlatedPanel = preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle); // Recreated provider, need to get new handle on action calleeFullSignatureTransferAction = getLocalAction(provider, actionName); @@ -669,7 +669,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest { int col; ClangToken currentToken; - DecompilerCodeComparisonPanel uncorrelatedPanel = + DecompilerCodeComparisonView uncorrelatedPanel = preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedMain); DockingActionIf calleeFullSignatureTransferAction = getLocalAction(provider, actionName); assertNotNull(calleeFullSignatureTransferAction); @@ -682,7 +682,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest { assertEquals("xstrdup", currentToken.getText()); assertNotEnabled(calleeFullSignatureTransferAction, getProviderContext()); - DecompilerCodeComparisonPanel correlatedPanel = + DecompilerCodeComparisonView correlatedPanel = preparePanel(funcDemangler24DebugCplusDemangle, funcDemangler24StrippedCplusDemangle); // Recreated provider, need to get new handle on action calleeFullSignatureTransferAction = getLocalAction(provider, actionName); @@ -747,7 +747,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest { // Setup and focus to a decompiler comparison between the two selected functions. Wait for // the decompilation to complete so that subsequent calls to navigation, etc. work correctly - private DecompilerCodeComparisonPanel preparePanel(Function leftFunc, Function rightFunc) { + private DecompilerCodeComparisonView preparePanel(Function leftFunc, Function rightFunc) { if (provider != null) { // Always want to clear out existing comparison closeProvider(provider); @@ -755,7 +755,7 @@ public class DualDecompilerActionTest extends AbstractDualDecompilerTest { provider = compareFunctions(Set.of(leftFunc, rightFunc)); - DecompilerCodeComparisonPanel decompPanel = findDecompilerPanel(provider); + DecompilerCodeComparisonView decompPanel = findDecompilerPanel(provider); waitForDecompile(decompPanel); decompPanel.setSynchronizedScrolling(true); setActivePanel(provider, decompPanel); diff --git a/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/plugin/CompareFunctionsProviderTest.java b/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/plugin/CompareFunctionsProviderTest.java index 5b163a32da..2bf372db1d 100644 --- a/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/plugin/CompareFunctionsProviderTest.java +++ b/Ghidra/Features/CodeCompare/src/test/java/ghidra/features/codecompare/plugin/CompareFunctionsProviderTest.java @@ -35,7 +35,7 @@ import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.function.FunctionPlugin; import ghidra.features.base.codecompare.model.FunctionComparisonModel; import ghidra.features.base.codecompare.model.MatchedFunctionComparisonModel; -import ghidra.features.base.codecompare.panel.CodeComparisonPanel; +import ghidra.features.base.codecompare.panel.CodeComparisonView; import ghidra.features.base.codecompare.panel.FunctionComparisonPanel; import ghidra.program.database.ProgramBuilder; import ghidra.program.model.address.Address; @@ -137,7 +137,7 @@ public class CompareFunctionsProviderTest extends AbstractGhidraHeadedIntegratio DockingActionIf previousAction = getAction(plugin, "Compare Previous Function"); // since we are clicking the listing panel, bring that to the front first - setActivePanel(provider, provider.getComponent().getDualListingPanel()); + setActivePanel(provider, provider.getComponent().getDualListingView()); // left panel has focus, so nextAction should be enabled and previous should be disabled ActionContext context = provider.getActionContext(null); @@ -145,7 +145,7 @@ public class CompareFunctionsProviderTest extends AbstractGhidraHeadedIntegratio assertNotEnabled(previousAction, context); JPanel rightPanel = - provider.getComponent().getDualListingPanel().getListingPanel(RIGHT).getFieldPanel(); + provider.getComponent().getDualListingView().getListingPanel(RIGHT).getFieldPanel(); clickMouse(rightPanel, 1, 30, 30, 1, 0); waitForSwing(); provider.getComponent().updateActionEnablement(); @@ -291,8 +291,10 @@ public class CompareFunctionsProviderTest extends AbstractGhidraHeadedIntegratio assertFalse(runSwing(() -> action.isEnabledForContext(context))); } - private void setActivePanel(FunctionComparisonProvider provider, CodeComparisonPanel panel) { - runSwing(() -> provider.getComponent().setCurrentTabbedComponent(panel.getName())); + private void setActivePanel(FunctionComparisonProvider provider, + CodeComparisonView comparisonProvider) { + runSwing( + () -> provider.getComponent().setCurrentTabbedComponent(comparisonProvider.getName())); waitForSwing(); } diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/DefaultFgEnv.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/DefaultFgEnv.java new file mode 100644 index 0000000000..008075ee36 --- /dev/null +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/DefaultFgEnv.java @@ -0,0 +1,113 @@ +/* ### + * 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.functiongraph; + +import java.util.List; +import java.util.Objects; + +import docking.action.DockingAction; +import ghidra.app.nav.Navigatable; +import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider; +import ghidra.app.plugin.core.functiongraph.mvc.*; +import ghidra.app.util.viewer.format.FormatManager; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; +import ghidra.program.util.ProgramSelection; + +public class DefaultFgEnv implements FgEnv { + + private FGProvider provider; + private FunctionGraphPlugin plugin; + + public DefaultFgEnv(FGProvider provider, FunctionGraphPlugin plugin) { + this.provider = Objects.requireNonNull(provider); + this.plugin = Objects.requireNonNull(plugin); + } + + @Override + public PluginTool getTool() { + return plugin.getTool(); + } + + @Override + public Program getProgram() { + return provider.getProgram(); + } + + @Override + public FunctionGraphOptions getOptions() { + return plugin.getFunctionGraphOptions(); + } + + @Override + public FGColorProvider getColorProvider() { + return plugin.getColorProvider(); + } + + @Override + public List getLayoutProviders() { + return plugin.getLayoutProviders(); + } + + @Override + public void addLocalAction(DockingAction action) { + provider.addLocalAction(action); + } + + @Override + public FormatManager getUserDefinedFormat() { + return plugin.getUserDefinedFormat(); + } + + @Override + public void setUserDefinedFormat(FormatManager format) { + plugin.setUserDefinedFormat(format); + } + + @Override + public Navigatable getNavigatable() { + return provider; + } + + @Override + public ProgramLocation getToolLocation() { + return plugin.getProgramLocation(); + } + + @Override + public void setSelection(ProgramSelection selection) { + // + // The connected provider will synchronize the tool selection with its selection. + // Non-connected providers will ignore selection updates, since users have made a snapshot + // that should no longer respond to selection changes from the tool. We still want actions + // that manipulate selection the graph to work for snapshots. To do this, we can call the + // controller directly (which is what the connected provider does). + // + if (provider.isConnected()) { + provider.setSelection(selection); + } + else { + FGController controller = provider.getController(); + controller.setSelection(selection); + } + } + + @Override + public ProgramLocation getGraphLocation() { + return provider.getLocation(); + } +} diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java index a6ec812c3a..f120cf2018 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGActionManager.java @@ -19,15 +19,13 @@ import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.util.*; -import javax.swing.Icon; -import javax.swing.KeyStroke; +import javax.swing.*; import docking.ActionContext; import docking.DockingUtils; import docking.action.*; import docking.menu.ActionState; import docking.menu.MultiStateDockingAction; -import docking.options.OptionsService; import docking.widgets.EventTrigger; import docking.widgets.OptionDialog; import edu.uci.ics.jung.graph.Graph; @@ -39,19 +37,18 @@ import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph; import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider; import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex; import ghidra.app.plugin.core.functiongraph.graph.vertex.GroupedFunctionGraphVertex; -import ghidra.app.plugin.core.functiongraph.mvc.FGController; -import ghidra.app.plugin.core.functiongraph.mvc.FGData; +import ghidra.app.plugin.core.functiongraph.mvc.*; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSetView; import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; import ghidra.util.HelpLocation; -import ghidra.util.SystemUtilities; -class FGActionManager { +public class FGActionManager { private static final String EDGE_HOVER_HIGHLIGHT = "EDGE_HOVER_HIGHLIGHT"; private static final String EDGE_SELECTION_HIGHLIGHT = "EDGE_SELECTION_HIGHLIGHT"; @@ -71,9 +68,8 @@ class FGActionManager { //@formatter:off private PluginTool tool; - private FunctionGraphPlugin plugin; + private String owner; private FGController controller; - private FGProvider provider; private ToggleDockingAction togglePopups; @@ -82,20 +78,29 @@ class FGActionManager { private MultiStateDockingAction layoutAction; - FGActionManager(FunctionGraphPlugin plugin, FGController controller, FGProvider provider) { - this.plugin = plugin; - this.tool = plugin.getTool(); + public FGActionManager(FGController controller, String owner) { this.controller = controller; - this.provider = provider; + this.owner = owner; + FgEnv env = controller.getEnv(); + this.tool = env.getTool(); + createActions(); } + private JComponent getCenterOverComponent() { + return controller.getViewComponent(); + } + + private void addLocalAction(DockingAction action) { + FgEnv env = controller.getEnv(); + env.addLocalAction(action); + } + private void createActions() { String toolBarGroup1 = "groupA"; String layoutGroup = "groupB"; - String toolbarEdgeGroup = "groupC"; - String toolbarEndGroup = "zzzend"; + String toolbarEdgeGroup = "groupC"; // this is a dependent, hard-coded value pulled from the plugin that creates highlight actions String popupSelectionGroup = "Highlight"; @@ -106,14 +111,13 @@ class FGActionManager { String popupMutateGroup1 = "zamutate.1"; String popupMutateGroup2 = "zamutate.2"; String popupDisplayGroup = "zdisplay"; - String popuEndPopupGroup = "zzzoom"; - String popupVeryLastGroup = "zzzzzz"; + String popuEndPopupGroup = "zzzoom"; int vertexGroupingSubgroupOffset = 1; int groupingSubgroupOffset = 1; // sub-sort of the grouping menu - DockingAction chooseFormatsAction = - new DockingAction("Edit Code Block Fields", plugin.getName()) { + DockingAction chooseFormatsAction = + new DockingAction("Edit Code Block Fields", owner) { @Override public void actionPerformed(ActionContext context) { showFormatChooser(); @@ -132,7 +136,7 @@ class FGActionManager { new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Format")); DockingAction homeAction = - new DockingAction("Go To Function Entry Point", plugin.getName()) { + new DockingAction("Go To Function Entry Point", owner) { @Override public void actionPerformed(ActionContext context) { goHome(); @@ -148,10 +152,10 @@ class FGActionManager { homeAction.setHelpLocation( new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Home")); - DockingAction resetGraphAction = new DockingAction("Reset Graph", plugin.getName()) { + DockingAction resetGraphAction = new DockingAction("Reset Graph", owner) { @Override public void actionPerformed(ActionContext context) { - int choice = OptionDialog.showYesNoDialog(provider.getComponent(), "Reset Graph?", + int choice = OptionDialog.showYesNoDialog(getCenterOverComponent(), "Reset Graph?", "Erase all vertex position and grouping information?"); if (choice != OptionDialog.YES_OPTION) { return; @@ -172,7 +176,7 @@ class FGActionManager { resetGraphAction.setHelpLocation( new HelpLocation("FunctionGraphPlugin", "Function_Graph_Reload_Graph")); - provider.addLocalAction(resetGraphAction); + addLocalAction(resetGraphAction); addLayoutAction(layoutGroup); addVertexHoverModeAction(toolbarEdgeGroup); @@ -181,7 +185,7 @@ class FGActionManager { // // Display transforming actions // - DockingAction zoomOutAction = new DockingAction("Zoom Out", plugin.getName()) { + DockingAction zoomOutAction = new DockingAction("Zoom Out", owner) { @Override public void actionPerformed(ActionContext context) { controller.zoomOutGraph(); @@ -201,7 +205,7 @@ class FGActionManager { KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, DockingUtils.CONTROL_KEY_MODIFIER_MASK))); zoomOutAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Zoom")); - DockingAction zoomInAction = new DockingAction("Zoom In", plugin.getName()) { + DockingAction zoomInAction = new DockingAction("Zoom In", owner) { @Override public void actionPerformed(ActionContext context) { controller.zoomInGraph(); @@ -220,7 +224,7 @@ class FGActionManager { KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, DockingUtils.CONTROL_KEY_MODIFIER_MASK))); zoomInAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Zoom")); - DockingAction zoomToWindowAction = new DockingAction("Zoom to Window", plugin.getName()) { + DockingAction zoomToWindowAction = new DockingAction("Zoom to Window", owner) { @Override public void actionPerformed(ActionContext context) { controller.zoomToWindow(); @@ -238,7 +242,7 @@ class FGActionManager { new MenuData(new String[] { "Zoom to Window" }, popuEndPopupGroup)); zoomToWindowAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Zoom")); - DockingAction zoomToVertexAction = new DockingAction("Zoom to Vertex", plugin.getName()) { + DockingAction zoomToVertexAction = new DockingAction("Zoom to Vertex", owner) { @Override public void actionPerformed(ActionContext context) { FunctionGraphVertexLocationContextIf vertexContext = @@ -264,7 +268,7 @@ class FGActionManager { new MenuData(new String[] { "Zoom to Vertex" }, popuEndPopupGroup)); zoomToVertexAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Zoom")); - togglePopups = new ToggleDockingAction("Display Popup Windows", plugin.getName()) { + togglePopups = new ToggleDockingAction("Display Popup Windows", owner) { @Override public void actionPerformed(ActionContext context) { controller.setPopupsVisible(isSelected()); @@ -290,7 +294,7 @@ class FGActionManager { // // Vertex Actions // - DockingAction editLabelAction = new DockingAction("Edit Vertex Label", plugin.getName()) { + DockingAction editLabelAction = new DockingAction("Edit Vertex Label", owner) { @Override public void actionPerformed(ActionContext context) { FunctionGraphValidGraphActionContextIf graphContext = @@ -298,7 +302,7 @@ class FGActionManager { // size guaranteed to be 1 FGVertex vertex = graphContext.getSelectedVertices().iterator().next(); - vertex.editLabel(provider.getComponent()); + vertex.editLabel(getCenterOverComponent()); } @Override @@ -334,7 +338,7 @@ class FGActionManager { editLabelAction .setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Vertex_Action_Label")); - DockingAction fullViewAction = new DockingAction("Vertex View Mode", plugin.getName()) { + DockingAction fullViewAction = new DockingAction("Vertex View Mode", owner) { @Override public void actionPerformed(ActionContext context) { FunctionGraphValidGraphActionContextIf graphContext = @@ -384,7 +388,7 @@ class FGActionManager { fullViewAction.setHelpLocation( new HelpLocation("FunctionGraphPlugin", "Vertex_Action_Full_View")); - DockingAction xrefsAction = new DockingAction("Jump to a XRef", plugin.getName()) { + DockingAction xrefsAction = new DockingAction("Jump to a XRef", owner) { @Override public void actionPerformed(ActionContext context) { controller.showXRefsDialog(); @@ -416,7 +420,7 @@ class FGActionManager { // Group Actions // DockingAction groupSelectedVertices = - new DockingAction("Group Selected Vertices", plugin.getName()) { + new DockingAction("Group Selected Vertices", owner) { @Override public void actionPerformed(ActionContext context) { controller.groupSelectedVertices(); @@ -455,7 +459,7 @@ class FGActionManager { new HelpLocation("FunctionGraphPlugin", "Vertex_Grouping_Group_Selected_Popup")); DockingAction addSelectedVerticesToGroup = - new DockingAction("Group Selected Vertices", plugin.getName()) { + new DockingAction("Group Selected Vertices", owner) { @Override public void actionPerformed(ActionContext context) { addToGroup(context); @@ -514,7 +518,7 @@ class FGActionManager { "Vertex_Grouping_Add_Selected_Vertices_To_Group")); DockingAction ungroupSelectedVertices = - new DockingAction("Ungroup Selected Vertices", plugin.getName()) { + new DockingAction("Ungroup Selected Vertices", owner) { @Override public void actionPerformed(ActionContext context) { FunctionGraphValidGraphActionContextIf graphContext = @@ -556,7 +560,7 @@ class FGActionManager { ungroupSelectedVertices.setHelpLocation( new HelpLocation("FunctionGraphPlugin", "Vertex_Grouping_Ungroup_Selected_Popup")); - DockingAction removeFromGroup = new DockingAction("Remove From Group", plugin.getName()) { + DockingAction removeFromGroup = new DockingAction("Remove From Group", owner) { @Override public void actionPerformed(ActionContext context) { FunctionGraphValidGraphActionContextIf graphContext = @@ -603,11 +607,11 @@ class FGActionManager { new HelpLocation("FunctionGraphPlugin", "Vertex_Grouping_Remove_From_Group")); DockingAction ungroupAllVertices = - new DockingAction("Ungroup All Vertices", plugin.getName()) { + new DockingAction("Ungroup All Vertices", owner) { @Override public void actionPerformed(ActionContext context) { - int choice = OptionDialog.showYesNoDialog(provider.getComponent(), + int choice = OptionDialog.showYesNoDialog(getCenterOverComponent(), "Ungroup All Vertices?", "Ungroup all grouped vertices?"); if (choice != OptionDialog.YES_OPTION) { return; @@ -645,55 +649,13 @@ class FGActionManager { ungroupAllVertices.setHelpLocation( new HelpLocation("FunctionGraphPlugin", "Vertex_Grouping_Ungroup_All_Popup")); - // - // Miscellaneous Actions - // - DockingAction cloneAction = new DockingAction("Function Graph Clone", plugin.getName()) { - @Override - public void actionPerformed(ActionContext context) { - provider.cloneWindow(); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - return controller.getGraphedFunction() != null; - } - }; - Icon image = new GIcon("icon.plugin.functiongraph.action.viewer.clone"); - cloneAction.setToolBarData(new ToolBarData(image, toolbarEndGroup)); - cloneAction.setDescription( - "Create a snapshot (disconnected) copy of this Function Graph window"); - cloneAction.setHelpLocation(new HelpLocation("Snapshots", "Snapshots_Start")); - cloneAction.setHelpLocation( - new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Snapshot")); - cloneAction.setKeyBindingData(new KeyBindingData(KeyEvent.VK_T, - InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); - - DockingAction optionsAction = - new DockingAction("Function Graph Options", plugin.getName()) { - - @Override - public void actionPerformed(ActionContext context) { - OptionsService service = tool.getService(OptionsService.class); - service.showOptionsDialog(FunctionGraphPlugin.OPTIONS_NAME_PATH, - "Function Graph"); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - return true; - } - }; - optionsAction.setPopupMenuData( - new MenuData(new String[] { "Properties" }, null, popupVeryLastGroup)); - optionsAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Options")); // // Selection Actions // String selectionMenuName = "Program Selection"; DockingAction selectHoveredEdgesAction = - new DockingAction("Make Selection From Hovered Edges", plugin.getName()) { + new DockingAction("Make Selection From Hovered Edges", owner) { @Override public void actionPerformed(ActionContext context) { @@ -727,7 +689,7 @@ class FGActionManager { .setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Path_Selection")); DockingAction selectFocusedEdgesAction = - new DockingAction("Make Selection From Focused Edges", plugin.getName()) { + new DockingAction("Make Selection From Focused Edges", owner) { @Override public void actionPerformed(ActionContext context) { @@ -761,7 +723,7 @@ class FGActionManager { .setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Path_Selection")); DockingAction clearCurrentSelectionAction = - new DockingAction("Clear Current Selection", plugin.getName()) { + new DockingAction("Clear Current Selection", owner) { @Override public void actionPerformed(ActionContext context) { @@ -775,7 +737,7 @@ class FGActionManager { @Override public boolean isEnabledForContext(ActionContext context) { - ProgramSelection selection = provider.getCurrentProgramSelection(); + ProgramSelection selection = controller.getSelection(); return selection != null && !selection.isEmpty(); } }; @@ -785,7 +747,7 @@ class FGActionManager { .setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Path_Selection")); DockingAction selectAllAction = - new DockingAction("Select All Code Units", plugin.getName()) { + new DockingAction("Select All Code Units", owner) { @Override public void actionPerformed(ActionContext context) { @@ -828,31 +790,28 @@ class FGActionManager { selectAllAction .setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Code_Unit_Selection")); - provider.addLocalAction(chooseFormatsAction); - provider.addLocalAction(homeAction); - provider.addLocalAction(zoomInAction); - provider.addLocalAction(zoomOutAction); - provider.addLocalAction(zoomToVertexAction); - provider.addLocalAction(zoomToWindowAction); + addLocalAction(chooseFormatsAction); + addLocalAction(homeAction); + addLocalAction(zoomInAction); + addLocalAction(zoomOutAction); + addLocalAction(zoomToVertexAction); + addLocalAction(zoomToWindowAction); - provider.addLocalAction(editLabelAction); - provider.addLocalAction(fullViewAction); - provider.addLocalAction(xrefsAction); + addLocalAction(editLabelAction); + addLocalAction(fullViewAction); + addLocalAction(xrefsAction); - provider.addLocalAction(groupSelectedVertices); - provider.addLocalAction(addSelectedVerticesToGroup); - provider.addLocalAction(removeFromGroup); - provider.addLocalAction(ungroupSelectedVertices); - provider.addLocalAction(ungroupAllVertices); - provider.addLocalAction(togglePopups); + addLocalAction(groupSelectedVertices); + addLocalAction(addSelectedVerticesToGroup); + addLocalAction(removeFromGroup); + addLocalAction(ungroupSelectedVertices); + addLocalAction(ungroupAllVertices); + addLocalAction(togglePopups); - provider.addLocalAction(cloneAction); - provider.addLocalAction(optionsAction); - - provider.addLocalAction(selectAllAction); - provider.addLocalAction(selectHoveredEdgesAction); - provider.addLocalAction(selectFocusedEdgesAction); - provider.addLocalAction(clearCurrentSelectionAction); + addLocalAction(selectAllAction); + addLocalAction(selectHoveredEdgesAction); + addLocalAction(selectFocusedEdgesAction); + addLocalAction(clearCurrentSelectionAction); // this does two things: 1) allows us to subgroup the pull-right menu and 2) it matches // the organization of the highlight and selection actions from the main listing @@ -865,7 +824,8 @@ class FGActionManager { HelpLocation layoutHelpLocation = new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Layout"); - layoutAction = new MultiStateDockingAction<>("Relayout Graph", plugin.getName()) { + layoutAction = new MultiStateDockingAction<>("Relayout Graph", owner, + KeyBindingType.SHARED) { @Override public void actionPerformed(ActionContext context) { @@ -896,7 +856,7 @@ class FGActionManager { layoutAction.addActionState(actionState); } - provider.addLocalAction(layoutAction); + addLocalAction(layoutAction); } private void changeLayout(FGLayoutProvider layout) { @@ -904,7 +864,8 @@ class FGActionManager { } private List > loadActionStatesForLayoutProviders() { - List layoutInstances = plugin.getLayoutProviders(); + FgEnv env = controller.getEnv(); + List layoutInstances = env.getLayoutProviders(); return createActionStates(layoutInstances); } @@ -988,7 +949,7 @@ class FGActionManager { offState.setHelpLocation(pathHelpLocation); vertexHoverModeAction = - new MultiStateDockingAction<>("Block Hover Mode", plugin.getName()) { + new MultiStateDockingAction<>("Block Hover Mode", owner) { @Override public void actionStateChanged(ActionState newActionState, @@ -1013,7 +974,7 @@ class FGActionManager { vertexHoverModeAction.setCurrentActionState(pathsForwardScopedFlow); - provider.addLocalAction(vertexHoverModeAction); + addLocalAction(vertexHoverModeAction); } @@ -1060,7 +1021,7 @@ class FGActionManager { offState.setHelpLocation(pathHelpLocation); vertexFocusModeAction = - new MultiStateDockingAction<>("Block Focus Mode", plugin.getName()) { + new MultiStateDockingAction<>("Block Focus Mode", owner) { @Override public void actionStateChanged(ActionState newActionState, @@ -1085,7 +1046,7 @@ class FGActionManager { vertexFocusModeAction.setCurrentActionState(allCyclesState); - provider.addLocalAction(vertexFocusModeAction); + addLocalAction(vertexFocusModeAction); } private void clearGraphSelection() { @@ -1093,12 +1054,14 @@ class FGActionManager { FGData functionGraphData = controller.getFunctionGraphData(); Function function = functionGraphData.getFunction(); AddressSetView functionBody = function.getBody(); - AddressSet subtraction = provider.getCurrentProgramSelection().subtract(functionBody); - + ProgramSelection selection = controller.getSelection(); + + AddressSet subtraction = selection.subtract(functionBody); ProgramSelection programSelectionWithoutGraphBody = new ProgramSelection(subtraction); - plugin.getTool() - .firePluginEvent(new ProgramSelectionPluginEvent("Spoof!", - programSelectionWithoutGraphBody, provider.getCurrentProgram())); + FgEnv env = controller.getEnv(); + Program program = env.getProgram(); + tool.firePluginEvent(new ProgramSelectionPluginEvent("Spoof!", + programSelectionWithoutGraphBody, program)); } private Set getAllVertices() { @@ -1146,15 +1109,10 @@ class FGActionManager { private void goHome() { Function function = controller.getGraphedFunction(); - ProgramLocation homeLocation = - new ProgramLocation(provider.getCurrentProgram(), function.getEntryPoint()); - if (SystemUtilities.isEqual(provider.getCurrentLocation(), homeLocation)) { - // already at the right location, just make sure we are on the screen and selected - provider.displayLocation(homeLocation); - } - else { - provider.internalGoTo(homeLocation, provider.getCurrentProgram()); - } + FgEnv env = controller.getEnv(); + Program program = env.getProgram(); + ProgramLocation homeLocation = new ProgramLocation(program, function.getEntryPoint()); + controller.display(program, homeLocation); } private AddressSet getAddressesForVertices(Collection vertices) { @@ -1167,9 +1125,9 @@ class FGActionManager { private void makeSelectionFromAddresses(AddressSet addresses) { ProgramSelection selection = new ProgramSelection(addresses); - plugin.getTool() - .firePluginEvent(new ProgramSelectionPluginEvent("Spoof!", selection, - provider.getCurrentProgram())); + FgEnv env = controller.getEnv(); + Program program = env.getProgram(); + tool.firePluginEvent(new ProgramSelectionPluginEvent("Spoof!", selection, program)); } private void ungroupVertices(Set groupVertices) { @@ -1181,7 +1139,7 @@ class FGActionManager { String vertexString = size == 1 ? "1 group vertex" : size + " group vertices"; - int choice = OptionDialog.showYesNoDialog(provider.getComponent(), "Ungroup Vertices?", + int choice = OptionDialog.showYesNoDialog(getCenterOverComponent(), "Ungroup Vertices?", "Ungroup " + vertexString + "?"); if (choice != OptionDialog.YES_OPTION) { return; diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGClipboardProvider.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGClipboardProvider.java index 7df039ba46..251d437424 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGClipboardProvider.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGClipboardProvider.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. @@ -41,8 +41,8 @@ public class FGClipboardProvider extends CodeBrowserClipboardProvider { private FGController controller; - FGClipboardProvider(PluginTool tool, FGController controller) { - super(tool, controller.getProvider()); + FGClipboardProvider(PluginTool tool, FGController controller, FGProvider provider) { + super(tool, provider); this.controller = controller; } diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGProvider.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGProvider.java index 2eac2e26fd..03bdd25ab1 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGProvider.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/FGProvider.java @@ -15,19 +15,22 @@ */ package ghidra.app.plugin.core.functiongraph; -import static ghidra.framework.model.DomainObjectEvent.RESTORED; +import static ghidra.framework.model.DomainObjectEvent.*; import static ghidra.program.util.ProgramEvent.*; -import java.awt.event.MouseEvent; +import java.awt.event.*; import java.util.*; import java.util.function.Supplier; import javax.swing.*; import docking.*; +import docking.action.*; +import docking.options.OptionsService; import docking.widgets.fieldpanel.FieldPanel; import edu.uci.ics.jung.graph.Graph; import generic.stl.Pair; +import generic.theme.GIcon; import ghidra.app.context.ListingActionContext; import ghidra.app.nav.*; import ghidra.app.plugin.core.functiongraph.action.*; @@ -103,7 +106,9 @@ public class FGProvider extends VisualGraphComponentProvider refreshAndKeepPerspective()); @@ -132,11 +137,64 @@ public class FGProvider extends VisualGraphComponentProvider setPendingLocationFromUpdateManager()); - clipboardProvider = new FGClipboardProvider(tool, controller); + clipboardProvider = new FGClipboardProvider(tool, controller, this); setDefaultFocusComponent(controller.getViewComponent()); } + private void createActions() { + + actionManager = new FGActionManager(controller, plugin.getName()); + + // Note: these values are coordinated with the FGActionManager + String toolbarEndGroup = "zzzend"; + String popupVeryLastGroup = "zzzzzz"; + + String owner = plugin.getName(); + DockingAction cloneAction = new DockingAction("Function Graph Clone", owner) { + @Override + public void actionPerformed(ActionContext context) { + cloneWindow(); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return controller.getGraphedFunction() != null; + } + }; + Icon image = new GIcon("icon.plugin.functiongraph.action.viewer.clone"); + cloneAction.setToolBarData(new ToolBarData(image, toolbarEndGroup)); + cloneAction.setDescription( + "Create a snapshot (disconnected) copy of this Function Graph window"); + cloneAction.setHelpLocation(new HelpLocation("Snapshots", "Snapshots_Start")); + cloneAction.setHelpLocation( + new HelpLocation("FunctionGraphPlugin", "Function_Graph_Action_Snapshot")); + cloneAction.setKeyBindingData(new KeyBindingData(KeyEvent.VK_T, + InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)); + + DockingAction optionsAction = + new DockingAction("Function Graph Options", owner) { + + @Override + public void actionPerformed(ActionContext context) { + OptionsService service = tool.getService(OptionsService.class); + service.showOptionsDialog(FunctionGraphPlugin.OPTIONS_NAME_PATH, + "Function Graph"); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return true; + } + }; + optionsAction.setPopupMenuData( + new MenuData(new String[] { "Properties" }, null, popupVeryLastGroup)); + optionsAction.setHelpLocation(new HelpLocation("FunctionGraphPlugin", "Options")); + + addLocalAction(cloneAction); + addLocalAction(optionsAction); + } + @Override public boolean isSnapshot() { // we are a snapshot when we are 'disconnected' @@ -150,6 +208,17 @@ public class FGProvider extends VisualGraphComponentProvider + * This will prime the clipboard such that a copy action will copy the given string. + * + * @param string the string to set + */ + public void setClipboardStringContent(String string) { + clipboardProvider.setStringContent(string); + } + FGController getController() { return controller; } @@ -235,24 +304,13 @@ public class FGProvider extends VisualGraphComponentProvider - * This will prime the clipboard such that a copy action will copy the given string. - * - * @param string the string to set - */ - public void setClipboardStringContent(String string) { - clipboardProvider.setStringContent(string); - } - public void saveLocationToHistory() { NavigationHistoryService historyService = tool.getService(NavigationHistoryService.class); historyService.addNewLocation(this); @@ -523,6 +581,16 @@ public class FGProvider extends VisualGraphComponentProvider iterator = ev.iterator(); - while (iterator.hasNext()) { - DomainObjectChangeRecord record = iterator.next(); + for (DomainObjectChangeRecord record : ev) { if (record instanceof ProgramChangeRecord) { ProgramChangeRecord programRecord = (ProgramChangeRecord) record; Address start = programRecord.getStart(); @@ -1133,9 +1167,7 @@ public class FGProvider extends VisualGraphComponentProvider info = - GraphPerspectiveInfo.createInvalidGraphPerspectiveInfo(); - controller.setGraphPerspective(info); + controller.clearViewSettings(); } void addMarkerProviderSupplier(MarginProviderSupplier supplier) { @@ -1148,6 +1180,10 @@ public class FGProvider extends VisualGraphComponentProvider getCurrentProgram(), colorizingService); } } @@ -139,7 +140,8 @@ public class FunctionGraphPlugin extends ProgramPlugin } } else if (interfaceClass == ColorizingService.class) { - colorProvider = new ToolBasedColorProvider(this, (ColorizingService) service); + colorProvider = + new ToolBasedColorProvider(() -> getCurrentProgram(), (ColorizingService) service); connectedProvider.refreshAndKeepPerspective(); } else if (interfaceClass == MarkerService.class) { @@ -172,6 +174,8 @@ public class FunctionGraphPlugin extends ProgramPlugin private List loadLayoutProviders() { + // Shared Code Note: This code is mirrored in the FgDisplay for the Code Comparison API + FGLayoutFinder layoutFinder = new DiscoverableFGLayoutFinder(); List instances = layoutFinder.findLayouts(); if (instances.isEmpty()) { @@ -214,6 +218,8 @@ public class FunctionGraphPlugin extends ProgramPlugin public void optionsChanged(ToolOptions options, String optionName, Object oldValue, Object newValue) { + // Shared Code Note: This code is mirrored in the FgDisplay for the Code Comparison API + // Graph -> Function Graph Options fgOptions = options.getOptions(FUNCTION_GRAPH_NAME); functionGraphOptions.loadOptions(fgOptions); diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/IndependentColorProvider.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/IndependentColorProvider.java index 8e11947bdb..e882425750 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/IndependentColorProvider.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/IndependentColorProvider.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. @@ -30,7 +30,7 @@ import ghidra.app.plugin.core.functiongraph.mvc.FunctionGraphVertexAttributes; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.PluginTool; -class IndependentColorProvider implements FGColorProvider { +public class IndependentColorProvider implements FGColorProvider { private static final String VERTEX_COLORS = "VERTEX_COLORS"; @@ -38,7 +38,7 @@ class IndependentColorProvider implements FGColorProvider { private final PluginTool tool; - IndependentColorProvider(PluginTool tool) { + public IndependentColorProvider(PluginTool tool) { this.tool = tool; } diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/SetFormatDialogComponentProvider.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/SetFormatDialogComponentProvider.java index 0bcd091067..6a2a49690d 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/SetFormatDialogComponentProvider.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/SetFormatDialogComponentProvider.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. @@ -127,7 +127,7 @@ public class SetFormatDialogComponentProvider extends DialogComponentProvider { return null; } - /*testing*/ FieldHeader getFieldHeader() { + public FieldHeader getFieldHeader() { return listingPanel.getFieldHeader(); } //================================================================================================== diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/ToolBasedColorProvider.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/ToolBasedColorProvider.java index f5ba5c8d0b..3c27461394 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/ToolBasedColorProvider.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/ToolBasedColorProvider.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. @@ -17,6 +17,7 @@ package ghidra.app.plugin.core.functiongraph; import java.awt.Color; import java.util.List; +import java.util.function.Supplier; import ghidra.app.plugin.core.colorizer.ColorizingService; import ghidra.app.plugin.core.functiongraph.graph.vertex.FGVertex; @@ -25,13 +26,18 @@ import ghidra.framework.options.SaveState; import ghidra.program.model.address.AddressSetView; import ghidra.program.model.listing.Program; -class ToolBasedColorProvider implements FGColorProvider { +/** + * An implementation of the {@link FGColorProvider} that works using the color services of the tool. + * A different implementation will be used if the tool's color services are not installed. + */ +public class ToolBasedColorProvider implements FGColorProvider { private final ColorizingService service; - private final FunctionGraphPlugin plugin; + private final Supplier programSupplier; - ToolBasedColorProvider(FunctionGraphPlugin plugin, ColorizingService colorizingService) { - this.plugin = plugin; + public ToolBasedColorProvider(Supplier programSupplier, + ColorizingService colorizingService) { + this.programSupplier = programSupplier; this.service = colorizingService; } @@ -42,7 +48,7 @@ class ToolBasedColorProvider implements FGColorProvider { @Override public void setVertexColor(FGVertex vertex, Color color) { - Program program = plugin.getCurrentProgram(); + Program program = programSupplier.get(); int id = program.startTransaction("Set Background Color"); try { service.setBackgroundColor(vertex.getAddresses(), color); @@ -56,7 +62,7 @@ class ToolBasedColorProvider implements FGColorProvider { @Override public void clearVertexColor(FGVertex vertex) { - Program program = plugin.getCurrentProgram(); + Program program = programSupplier.get(); int id = program.startTransaction("Set Background Color"); try { service.clearBackgroundColor(vertex.getAddresses()); diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FunctionGraphFactory.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FunctionGraphFactory.java index 8da82f3670..90ba27424f 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FunctionGraphFactory.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/FunctionGraphFactory.java @@ -222,7 +222,11 @@ public class FunctionGraphFactory { for (FGVertex v : vertices) { monitor.increment(); try { - Swing.runNow(v::getComponent); + Swing.runNow(() -> { + if (!monitor.isCancelled()) { + v.getComponent(); + } + }); } catch (Exception e) { return false; diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/vertex/ListingGraphComponentPanel.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/vertex/ListingGraphComponentPanel.java index 2a3db590de..7c8d7cc079 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/vertex/ListingGraphComponentPanel.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/vertex/ListingGraphComponentPanel.java @@ -744,7 +744,7 @@ public class ListingGraphComponentPanel extends AbstractGraphComponentPanel { @Override protected void showPopup(JComponent comp, Field field, MouseEvent event, Rectangle fieldBounds) { - if (!controller.arePopupsEnabled()) { + if (!controller.arePopupsVisible()) { return; } diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/vertex/SetVertexMostRecentColorAction.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/vertex/SetVertexMostRecentColorAction.java index ed32cd256d..91583f4c47 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/vertex/SetVertexMostRecentColorAction.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/graph/vertex/SetVertexMostRecentColorAction.java @@ -160,7 +160,6 @@ public class SetVertexMostRecentColorAction extends MultiActionDockingAction { actionList.add(createSeparator()); actionList.add(chooseColorAction); actionList.add(clearColorAction); - return actionList; } diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/DefaultFGControllerListener.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/DefaultFGControllerListener.java new file mode 100644 index 0000000000..75946e4216 --- /dev/null +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/DefaultFGControllerListener.java @@ -0,0 +1,79 @@ +/* ### + * 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.functiongraph.mvc; + +import ghidra.app.plugin.core.functiongraph.FGProvider; +import ghidra.app.plugin.core.functiongraph.FunctionGraphPlugin; +import ghidra.program.util.ProgramLocation; +import ghidra.program.util.ProgramSelection; + +public class DefaultFGControllerListener implements FGControllerListener { + + private FGProvider provider; + + public DefaultFGControllerListener(FGProvider provider) { + this.provider = provider; + } + + @Override + public void dataChanged() { + provider.functionGraphDataChanged(); + } + + @Override + public void userChangedLocation(ProgramLocation location, boolean vertexChanged) { + + boolean updateHistory = false; + if (vertexChanged) { + if (shouldSaveVertexChanges()) { + // put the navigation on the history stack if we've changed nodes (this is the + // location we are leaving) + provider.saveLocationToHistory(); + updateHistory = true; + } + } + + provider.graphLocationChanged(location); + + if (updateHistory) { + // put the new location on the history stack now that we've updated the provider + provider.saveLocationToHistory(); + } + } + + private boolean shouldSaveVertexChanges() { + FunctionGraphPlugin plugin = provider.getPlugin(); + FunctionGraphOptions options = plugin.getFunctionGraphOptions(); + return options.getNavigationHistoryChoice() == NavigationHistoryChoices.VERTEX_CHANGES; + } + + @Override + public void userChangedSelection(ProgramSelection selection) { + provider.graphSelectionChanged(selection); + } + + @Override + public void userSelectedText(String s) { + provider.setClipboardStringContent(s); + } + + @Override + public void userNavigated(ProgramLocation location) { + // Tell the provider to navigate to this location. This will work for connected and + // disconnected providers. + provider.internalGoTo(location); + } +} diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGController.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGController.java index 6bf55bd3bb..fc45fa4f96 100644 --- a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGController.java +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGController.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. @@ -23,7 +23,6 @@ import java.util.List; import java.util.function.BiConsumer; import javax.swing.JComponent; -import javax.swing.SwingUtilities; import com.google.common.cache.*; @@ -34,7 +33,8 @@ import docking.widgets.fieldpanel.support.Highlight; import ghidra.GhidraOptions; import ghidra.app.nav.Navigatable; import ghidra.app.plugin.core.codebrowser.ListingMiddleMouseHighlightProvider; -import ghidra.app.plugin.core.functiongraph.*; +import ghidra.app.plugin.core.functiongraph.FGColorProvider; +import ghidra.app.plugin.core.functiongraph.SetFormatDialogComponentProvider; import ghidra.app.plugin.core.functiongraph.graph.FGEdge; import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph; import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider; @@ -63,10 +63,9 @@ import ghidra.util.datastruct.WeakSet; public class FGController implements ProgramLocationListener, ProgramSelectionListener { - private final FunctionGraphPlugin plugin; - private FGProvider provider; - private final FGModel model; - private final FGView view; + private FgEnv env; + private FGModel model; + private FGView view; private FGData functionGraphData = new EmptyFunctionGraphData("Uninitialized Function Graph"); private FunctionGraphViewSettings viewSettings = new NoFunctionGraphViewSettings(); @@ -82,10 +81,11 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi private FormatManager defaultFormatManager; // lazy! private FunctionGraphOptions functionGraphOptions; + private FGControllerListener listener; private FgHighlightProvider sharedHighlightProvider; private StringSelectionListener sharedStringSelectionListener = - string -> provider.setClipboardStringContent(string); + string -> listener.userSelectedText(string); private Cache cache; private BiConsumer fgDataDisposeListener = (data, evicted) -> { @@ -95,14 +95,18 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi private WeakSet marginProviders = WeakDataStructureFactory.createSingleThreadAccessWeakSet(); - public FGController(FGProvider provider, FunctionGraphPlugin plugin) { - this.provider = provider; - this.plugin = plugin; + public FGController(FgEnv env, FGControllerListener controllerListener) { + this.env = env; + this.listener = Objects.requireNonNull(controllerListener); this.cache = buildCache(this::cacheValueRemoved); this.model = new FGModel(this); this.view = new FGView(this, model.getTaskMonitorComponent()); - functionGraphOptions = plugin.getFunctionGraphOptions(); + this.functionGraphOptions = env.getOptions(); + } + + public FgEnv getEnv() { + return env; } private boolean disposeGraphDataIfNotInUse(FGData data) { @@ -124,7 +128,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi } private FormatManager createMinimalFormatManager() { - FormatManager userDefinedFormat = plugin.getUserDefinedFormat(); + FormatManager userDefinedFormat = env.getUserDefinedFormat(); if (userDefinedFormat != null) { return userDefinedFormat; } @@ -132,8 +136,20 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi } private FormatManager createFullFormatManager() { - CodeViewerService codeViewer = plugin.getTool().getService(CodeViewerService.class); - return codeViewer.getFormatManager(); + CodeViewerService codeViewer = + env.getTool().getService(CodeViewerService.class); + + if (codeViewer != null) { + // Prefer the manager from the service, as this is the current state of the Listing. + return codeViewer.getFormatManager(); + } + + // No code viewer service implies we are not in the default tool + PluginTool tool = env.getTool(); + ToolOptions displayOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_DISPLAY); + ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS); + return new FormatManager(displayOptions, fieldOptions); + } public FormatManager getMinimalFormatManager() { @@ -149,6 +165,18 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi minimalFormatManager.addHighlightProvider(highlightProvider); } + public void updateMinimalFormatManager(FormatManager newFormatManager) { + + // ensure the format manager has been created + getMinimalFormatManager(); + + SaveState saveState = new SaveState(); + newFormatManager.saveState(saveState); + minimalFormatManager.readState(saveState); + env.setUserDefinedFormat(minimalFormatManager); + view.repaint(); + } + public FormatManager getFullFormatManager() { if (fullFormatManager == null) { fullFormatManager = createFullFormatManager(); @@ -156,7 +184,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi return fullFormatManager; } - private FormatManager getDefaultFormatManager() { + public FormatManager getDefaultFormatManager() { if (defaultFormatManager == null) { defaultFormatManager = createDefaultFormat(); } @@ -167,22 +195,24 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi if (sharedHighlightProvider != null) { return sharedHighlightProvider; } + + JComponent centerOverComponent = view.getPrimaryGraphViewer(); sharedHighlightProvider = - new FgHighlightProvider(plugin.getTool(), provider.getComponent()); + new FgHighlightProvider(env.getTool(), centerOverComponent); return sharedHighlightProvider; } public void formatChanged() { - setMinimalFormatManager(plugin.getUserDefinedFormat()); + setMinimalFormatManager(env.getUserDefinedFormat()); view.repaint(); } public Navigatable getNavigatable() { - return provider; + return env.getNavigatable(); } private FormatManager createDefaultFormat() { - OptionsService options = plugin.getTool().getService(OptionsService.class); + OptionsService options = env.getTool().getService(OptionsService.class); ToolOptions displayOptions = options.getOptions(GhidraOptions.CATEGORY_BROWSER_DISPLAY); ToolOptions fieldOptions = options.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS); @@ -321,31 +351,13 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi FunctionGraph graph = functionGraphData.getFunctionGraph(); FGVertex newFocusedVertex = graph.getFocusedVertex(); boolean vertexChanged = lastUserNavigatedVertex != newFocusedVertex; - boolean updateHistory = false; - if (vertexChanged) { - if (shouldSaveVertexChanges()) { - // put the navigation on the history stack if we've changed nodes (this is the - // location we are leaving) - provider.saveLocationToHistory(); - updateHistory = true; - } - lastUserNavigatedVertex = newFocusedVertex; - } + lastUserNavigatedVertex = newFocusedVertex; viewSettings.setLocation(loc); - provider.graphLocationChanged(loc); - - if (updateHistory) { - // put the new location on the history stack now that we've updated the provider - provider.saveLocationToHistory(); - } - } - - private boolean shouldSaveVertexChanges() { - return functionGraphOptions - .getNavigationHistoryChoice() == NavigationHistoryChoices.VERTEX_CHANGES; + listener.userChangedLocation(loc, vertexChanged); } + // this is a callback from the vertex's listing panel @Override public void programSelectionChanged(ProgramSelection selection, EventTrigger trigger) { if (trigger != EventTrigger.GUI_ACTION) { @@ -360,7 +372,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi // push the user changes up to the provider viewSettings.setSelection(fullSelection); - provider.graphSelectionChanged(fullSelection); + listener.userChangedSelection(fullSelection); } //================================================================================================== @@ -450,19 +462,12 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi return view.isSatelliteVisible(); } - public boolean isSatelliteDocked() { - return view.isSatelliteDocked(); + public void setSatelliteVisible(boolean visible) { + view.setSatelliteVisible(visible); } - public void satelliteProviderShown() { - // note: always show the primary provider when the satellite is shown - if (provider.isVisible()) { - return; // nothing to do - } - - // We do this later because it is possible during initialization that the provider is - // not 'inTool' because of how XML gets restored. So, just do it later--it's less code. - SwingUtilities.invokeLater(() -> provider.setVisible(true)); + public boolean isSatelliteDocked() { + return view.isSatelliteDocked(); } public void primaryProviderHidden() { @@ -471,11 +476,14 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi public void setPopupsVisible(boolean visible) { view.setPopupsVisible(visible); - provider.setPopupsVisible(visible); + } + + public boolean arePopupsVisible() { + return view.arePopupsVisible(); } public boolean arePopupsEnabled() { - return view.arePopupsEnabled(); + return arePopupsVisible(); } public FGVertex getFocusedVertex() { @@ -486,6 +494,13 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi return view.getFocusedVertex(); } + public FGVertex getEntryPointVertex() { + if (!hasResults()) { + return null; + } + return view.getEntryPointVertex(); + } + public Set getSelectedVertices() { if (!hasResults()) { return null; @@ -504,6 +519,10 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi view.cleanup(); } + public ProgramSelection getSelection() { + return viewSettings.getSelection(); + } + public void setSelection(ProgramSelection selection) { viewSettings.setSelection(selection); } @@ -533,6 +552,12 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi } } + public void clearViewSettings() { + GraphPerspectiveInfo info = + GraphPerspectiveInfo.createInvalidGraphPerspectiveInfo(); + setGraphPerspective(info); + } + public void display(Program program, ProgramLocation location) { if (viewContainsLocation(location)) { // no need to rebuild the graph; just set the location @@ -587,6 +612,40 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi } } + /** + * Tells this provider to refresh, which means to rebuild the graph and relayout the + * vertices. + * @param keepPerspective true to keep the perspective (e.g., zoom level and view position) + */ + public void refresh(boolean keepPerspective) { + + if (functionGraphData.hasResults()) { + // + // We use the graph's data over the 'currentXXX' data, as there is a chance that the + // latter values have been set to new values, while the graph has differing data. In + // that case we have made the decision to prefer the graph's data. + // + Function function = functionGraphData.getFunction(); + Address address = function.getEntryPoint(); + ProgramLocation currentLocation = env.getGraphLocation(); + Address currentAddress = currentLocation.getAddress(); + if (function.getBody().contains(currentAddress)) { + // prefer the current address if it is within the current function (i.e., the + // location hasn't changed out from under the graph due to threading issues) + address = currentAddress; + } + + Program program = function.getProgram(); + ProgramLocation programLocation = new ProgramLocation(program, address); + rebuildDisplay(program, programLocation, keepPerspective); + return; + } + + Program program = env.getProgram(); + ProgramLocation currentLocation = env.getGraphLocation(); + rebuildDisplay(program, currentLocation, keepPerspective); + } + /* * This method differs from the refresh...() methods in that it will trigger a * graph rebuild, clearing any cached graph data in the process. If maintainPerspective @@ -611,7 +670,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi } public void rebuildCurrentDisplay() { - provider.refreshAndKeepPerspective(); + refresh(true); } public void resetGraph() { @@ -629,9 +688,9 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi rebuildDisplay(function.getProgram(), location, false); // we are changing the location above--make sure the external tool knows of it - ProgramLocation externalLocation = plugin.getProgramLocation(); + ProgramLocation externalLocation = env.getToolLocation(); if (!externalLocation.getAddress().equals(location.getAddress())) { - provider.graphLocationChanged(location); + listener.userChangedLocation(location, false); } } @@ -649,7 +708,9 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi } public void refreshDisplayWithoutRebuilding() { - view.refreshDisplayWithoutRebuilding(); + if (functionGraphData.hasResults()) { + view.refreshDisplayWithoutRebuilding(); + } } public void refreshDisplayForAddress(Address address) { @@ -657,7 +718,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi } public Program getProgram() { - return provider.getProgram(); + return env.getProgram(); } public FGData getFunctionGraphData() { @@ -671,6 +732,10 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi return null; } + public boolean isBusy() { + return model.isBusy(); + } + public JComponent getViewComponent() { return view.getViewComponent(); } @@ -680,7 +745,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi view.setLayoutProvider(newLayout); if (previousLayout == null) { - provider.refreshAndResetPerspective(); + refresh(false); return; } @@ -690,7 +755,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi view.relayout(); } else { - provider.refreshAndResetPerspective(); + refresh(false); } } @@ -704,40 +769,37 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi vertex = view.getEntryPointVertex(); } - PluginTool tool = plugin.getTool(); + PluginTool tool = env.getTool(); SetFormatDialogComponentProvider setFormatDialog = new SetFormatDialogComponentProvider(getDefaultFormatManager(), minimalFormatManager, - tool, provider.getProgram(), vertex.getAddresses()); + tool, env.getProgram(), vertex.getAddresses()); tool.showDialog(setFormatDialog); FormatManager newFormatManager = setFormatDialog.getNewFormatManager(); if (newFormatManager == null) { return; } - SaveState saveState = new SaveState(); - newFormatManager.saveState(saveState); - minimalFormatManager.readState(saveState); - plugin.setUserDefinedFormat(minimalFormatManager); - view.repaint(); + updateMinimalFormatManager(newFormatManager); } public void showXRefsDialog() { - PluginTool tool = plugin.getTool(); - Program program = plugin.getCurrentProgram(); + PluginTool tool = env.getTool(); + Program program = env.getProgram(); List references = getXReferencesToGraph(); XRefChooserDialog chooserDialog = new XRefChooserDialog(references, program, tool); - tool.showDialog(chooserDialog, provider); + JComponent centerOverComponent = view.getPrimaryGraphViewer(); + tool.showDialog(chooserDialog, centerOverComponent); Reference reference = chooserDialog.getSelectedReference(); if (reference == null) { return; // the user cancelled } - internalGoTo(new ProgramLocation(program, reference.getFromAddress()), program); + listener.userNavigated(new ProgramLocation(program, reference.getFromAddress())); } private List getXReferencesToGraph() { - Program program = plugin.getCurrentProgram(); + Program program = env.getProgram(); Function function = getGraphedFunction(); ReferenceManager referenceManager = program.getReferenceManager(); @@ -886,7 +948,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi restoreGraphSettingsForNewFunction(); viewSettings = new CurrentFunctionGraphViewSettings(view, viewSettings); - provider.functionGraphDataChanged(); + listener.dataChanged(); } private boolean disposeIfNotInCache(FGData data) { @@ -921,7 +983,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi } private boolean isSnapshot() { - return !provider.isConnected(); + return !getNavigatable().isConnected(); } //================================================================================================== @@ -956,11 +1018,7 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi } public PluginTool getTool() { - return provider.getTool(); - } - - public FGProvider getProvider() { - return provider; + return env.getTool(); } public Point getViewerPointFromVertexPoint(FGVertex vertex, Point point) { @@ -988,22 +1046,22 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi } public Color getMostRecentColor() { - FGColorProvider colorProvider = plugin.getColorProvider(); + FGColorProvider colorProvider = env.getColorProvider(); return colorProvider.getMostRecentColor(); } public List getRecentColors() { - FGColorProvider colorProvider = plugin.getColorProvider(); + FGColorProvider colorProvider = env.getColorProvider(); return colorProvider.getRecentColors(); } public void saveVertexColors(FGVertex vertex, FunctionGraphVertexAttributes settings) { - FGColorProvider colorProvider = plugin.getColorProvider(); + FGColorProvider colorProvider = env.getColorProvider(); colorProvider.saveVertexColors(vertex, settings); } public void restoreVertexColors(FGVertex vertex, FunctionGraphVertexAttributes settings) { - FGColorProvider colorProvider = plugin.getColorProvider(); + FGColorProvider colorProvider = env.getColorProvider(); colorProvider.loadVertexColors(vertex, settings); } @@ -1013,11 +1071,12 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi } public FGColorProvider getColorProvider() { - return plugin.getColorProvider(); + return env.getColorProvider(); } public T getService(Class serviceClass) { - return plugin.getService(serviceClass); + PluginTool tool = env.getTool(); + return tool.getService(serviceClass); } /** @@ -1026,7 +1085,23 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi */ public void synchronizeProgramLocationAfterEdit() { // It is assumed that the provider's location is the correct location. - viewSettings.setLocation(provider.getLocation()); + viewSettings.setLocation(env.getGraphLocation()); + } + + /** + * A simple method to move the cursor to the given location in the currently graphed function. + * If the location is not in the current function, nothing will happen. + * + * @param location the location + */ + public void setLocation(ProgramLocation location) { + if (viewContainsLocation(location)) { + viewSettings.setLocation(location); + } + } + + public ProgramLocation getLocation() { + return viewSettings.getLocation(); } /** @@ -1042,16 +1117,12 @@ public class FGController implements ProgramLocationListener, ProgramSelectionLi handleLocationChangedFromVertex(location); } - public void internalGoTo(ProgramLocation programLocation, Program program) { - provider.internalGoTo(programLocation, program); - } - - private Cache buildCache(RemovalListener listener) { + private Cache buildCache(RemovalListener removalListener) { //@formatter:off return CacheBuilder .newBuilder() .maximumSize(5) - .removalListener(listener) + .removalListener(removalListener) // Note: using soft values means that sometimes our data is reclaimed by the // Garbage Collector. We don't want that, we wish to call dispose() on the data //.softValues() diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGControllerListener.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGControllerListener.java new file mode 100644 index 0000000000..94069ff0c7 --- /dev/null +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FGControllerListener.java @@ -0,0 +1,56 @@ +/* ### + * 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.functiongraph.mvc; + +import ghidra.program.util.ProgramLocation; +import ghidra.program.util.ProgramSelection; + +public interface FGControllerListener { + + /** + * Called when the {@link FGData} for the current viewer has been set on the controller. + */ + public void dataChanged(); + + /** + * A notification for when the user has changed the location by interacting with the Function + * Graph UI. + * @param location the new location + * @param vertexChanged true if a new vertex has been selected + */ + public void userChangedLocation(ProgramLocation location, boolean vertexChanged); + + /** + * A notification for when the user has changed the selection by interacting with the Function + * Graph UI. + * @param selection the new selection + */ + public void userChangedSelection(ProgramSelection selection); + + /** + * A notification for when the user has selected text in a vertex by interacting with the + * Function Graph UI. + * @param s the selected text + */ + public void userSelectedText(String s); + + /** + * Called when the users requests the tool to navigate to a new location, such as when + * double-clicking an xref. + * @param location the location + */ + public void userNavigated(ProgramLocation location); +} diff --git a/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FgEnv.java b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FgEnv.java new file mode 100644 index 0000000000..604ff14b83 --- /dev/null +++ b/Ghidra/Features/FunctionGraph/src/main/java/ghidra/app/plugin/core/functiongraph/mvc/FgEnv.java @@ -0,0 +1,93 @@ +/* ### + * 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.functiongraph.mvc; + +import java.util.List; + +import docking.action.DockingAction; +import ghidra.app.nav.Navigatable; +import ghidra.app.plugin.core.functiongraph.FGColorProvider; +import ghidra.app.plugin.core.functiongraph.FunctionGraphPlugin; +import ghidra.app.plugin.core.functiongraph.graph.FunctionGraph; +import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider; +import ghidra.app.util.viewer.format.FormatManager; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; +import ghidra.program.util.ProgramSelection; + +/** + * A simple class that allows us to re-use parts of the {@link FunctionGraph} API by abstracting + * away the {@link FunctionGraphPlugin}. The env allows the controller to get the state of the + * graph, the state of the tool and to share resources among graphs. + */ +public interface FgEnv { + + public PluginTool getTool(); + + public Program getProgram(); + + public FunctionGraphOptions getOptions(); + + public FGColorProvider getColorProvider(); + + public List getLayoutProviders(); + + /** + * Adds the given action to the provider used by this environment. + * @param action the action + */ + public void addLocalAction(DockingAction action); + + /** + * Returns the graph format manager that can be shared amongst all graphs. + * @return the graph format manager that can be shared amongst all graphs. + * @see #setUserDefinedFormat(FormatManager) + */ + public FormatManager getUserDefinedFormat(); + + /** + * Sets the graph format manager that can be shared amongst all graphs. + * @param format the format manager + * @see #getUserDefinedFormat() + */ + public void setUserDefinedFormat(FormatManager format); + + public Navigatable getNavigatable(); + + /** + * The tool location is the program location shared by all plugins. Disconnected graphs my not + * be using this location. + * @return the location + * @see #getGraphLocation() + */ + public ProgramLocation getToolLocation(); + + /** + * Sets the selection for this function graph environment. If the graph is connected to the + * tool, then the selection will be sent to the tool as well as to the graph. + * @param selection the selection + */ + public void setSelection(ProgramSelection selection); + + /** + * Graph location is the program location inside of the graph, which may differ from that of the + * tool, such as for disconnected graphs. + * @return the location + * @see #getToolLocation() + */ + public ProgramLocation getGraphLocation(); +} diff --git a/Ghidra/Features/FunctionGraph/src/test.slow/java/ghidra/app/plugin/core/functiongraph/AbstractFunctionGraphTest.java b/Ghidra/Features/FunctionGraph/src/test.slow/java/ghidra/app/plugin/core/functiongraph/AbstractFunctionGraphTest.java index 997db3fa94..c8e332badb 100644 --- a/Ghidra/Features/FunctionGraph/src/test.slow/java/ghidra/app/plugin/core/functiongraph/AbstractFunctionGraphTest.java +++ b/Ghidra/Features/FunctionGraph/src/test.slow/java/ghidra/app/plugin/core/functiongraph/AbstractFunctionGraphTest.java @@ -507,6 +507,10 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte return (FGController) TestUtils.getInstanceField("controller", graphProvider); } + protected FGProvider getProvider() { + return graphProvider; + } + protected FGComponent getGraphComponent() { FGController controller = (FGController) TestUtils.getInstanceField("controller", graphProvider); @@ -799,8 +803,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte @SuppressWarnings("unchecked") protected DockingAction getCopyAction() { - FGController controller = getFunctionGraphController(); - FGProvider provider = controller.getProvider(); + FGProvider provider = getProvider(); FGClipboardProvider clipboarProvider = (FGClipboardProvider) getInstanceField("clipboardProvider", provider); @@ -885,11 +888,12 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte } protected boolean isSatelliteVisible() { - return isSatelliteVisible(getFunctionGraphController()); + return isSatelliteVisible(graphProvider); } - protected boolean isSatelliteVisible(FGController controller) { + protected boolean isSatelliteVisible(FGProvider fgProvider) { + FGController controller = fgProvider.getController(); FGView view = controller.getView(); GraphComponent gc = view.getGraphComponent(); if (gc == null) { @@ -898,7 +902,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte // Note: we cannot rely on 'gc.isSatelliteShowing()', as when the application does not // have focus, isShowing() will return false :( - ComponentProvider satellite = controller.getProvider().getSatelliteProvider(); + ComponentProvider satellite = fgProvider.getSatelliteProvider(); boolean satelliteProviderVisible = runSwing(() -> satellite != null && satellite.isVisible()); @@ -1206,7 +1210,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte runSwing(() -> controller.invalidateAllCacheForProgram(program)); } - protected FGController cloneGraph() { + protected FGProvider cloneGraph() { DockingActionIf snapshotAction = AbstractDockingTest.getAction(tool, graphPlugin.getName(), "Function Graph Clone"); @@ -1222,7 +1226,7 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte waitForBusyRunManager(controllerClone); waitForAnimation(controllerClone); - return controllerClone; + return providerClone; } protected void color(final FGVertex v1, final Color color) { @@ -2139,9 +2143,9 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte assertUndockedProviderShowing(provider); } - protected void assertUndockedProviderShowing(ComponentProvider satellite) { - assertNotNull("Undocked provider is not installed when it should be", satellite); - assertTrue("Undocked provider is not showing after being undocked", satellite.isVisible()); + protected void assertUndockedProviderShowing(ComponentProvider provider) { + assertNotNull("Undocked provider is not installed when it should be", provider); + assertTrue("Undocked provider is not showing after being undocked", provider.isVisible()); } protected void assertZoomedIn() { diff --git a/Ghidra/Features/FunctionGraph/src/test.slow/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices1Test.java b/Ghidra/Features/FunctionGraph/src/test.slow/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices1Test.java index 70141e5569..89b01ef108 100644 --- a/Ghidra/Features/FunctionGraph/src/test.slow/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices1Test.java +++ b/Ghidra/Features/FunctionGraph/src/test.slow/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices1Test.java @@ -148,7 +148,7 @@ public class FunctionGraphGroupVertices1Test extends AbstractFunctionGraphTest { // First thing we need to do is close the function graph window. It's opened on // startup by default in this test suite but we want it closed until we clear the // function code bytes. - this.getFunctionGraphController().getProvider().closeComponent(); + this.getProvider().closeComponent(); // Set up some additional plugins we need. try { diff --git a/Ghidra/Features/FunctionGraph/src/test.slow/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices2Test.java b/Ghidra/Features/FunctionGraph/src/test.slow/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices2Test.java index deb3586b5e..c7958c8601 100644 --- a/Ghidra/Features/FunctionGraph/src/test.slow/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices2Test.java +++ b/Ghidra/Features/FunctionGraph/src/test.slow/java/ghidra/app/plugin/core/functiongraph/FunctionGraphGroupVertices2Test.java @@ -97,7 +97,8 @@ public class FunctionGraphGroupVertices2Test extends AbstractFunctionGraphTest { // // Clone the graph // - FGController clonedController = cloneGraph(); + FGProvider clonedProvider = cloneGraph(); + FGController clonedController = clonedProvider.getController(); FGData clonedData = clonedController.getFunctionGraphData(); FunctionGraph clonedFunctionGraph = clonedData.getFunctionGraph(); diff --git a/Ghidra/Features/FunctionGraph/src/test.slow/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin1Test.java b/Ghidra/Features/FunctionGraph/src/test.slow/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin1Test.java index 0ed501eaac..35be292c71 100644 --- a/Ghidra/Features/FunctionGraph/src/test.slow/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin1Test.java +++ b/Ghidra/Features/FunctionGraph/src/test.slow/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin1Test.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. @@ -418,15 +418,14 @@ public class FunctionGraphPlugin1Test extends AbstractFunctionGraphTest { AddressSetView addresses = focusedVertex.getAddresses(); Address address = addresses.getMinAddress(); ProgramSelection selection = - new ProgramSelection(program.getAddressFactory(), address, address.add(8)); + new ProgramSelection(address, address.add(8)); tool.firePluginEvent(new ProgramSelectionPluginEvent("Test", selection, program)); // // Validate and execute the action // DockingAction copyAction = getCopyAction(); - FGController controller = getFunctionGraphController(); - ComponentProvider provider = controller.getProvider(); + ComponentProvider provider = getProvider(); assertTrue(copyAction.isEnabledForContext(provider.getActionContext(null))); performAction(copyAction, provider, false); @@ -478,8 +477,7 @@ public class FunctionGraphPlugin1Test extends AbstractFunctionGraphTest { // // Validate and execute the action // - FGController controller = getFunctionGraphController(); - ComponentProvider provider = controller.getProvider(); + ComponentProvider provider = getProvider(); ActionContext actionContext = provider.getActionContext(null); boolean isEnabled = copyAction.isEnabledForContext(actionContext); debugAction(copyAction, actionContext); @@ -585,14 +583,14 @@ public class FunctionGraphPlugin1Test extends AbstractFunctionGraphTest { @Test public void testGraphNodesCreated() throws Exception { + FGData graphData = getFunctionGraphData(); assertNotNull(graphData); assertTrue("Unexpectedly received an empty FunctionGraphData", graphData.hasResults()); FunctionGraph functionGraph = graphData.getFunctionGraph(); Collection vertices = functionGraph.getVertices(); - BlockModelService blockService = tool.getService(BlockModelService.class); - CodeBlockModel blockModel = blockService.getActiveBlockModel(program); + CodeBlockModel blockModel = new BasicBlockModel(program); FunctionManager functionManager = program.getFunctionManager(); Function function = functionManager.getFunctionContaining(getAddress(startAddressString)); CodeBlockIterator iterator = diff --git a/Ghidra/Features/FunctionGraph/src/test.slow/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin2Test.java b/Ghidra/Features/FunctionGraph/src/test.slow/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin2Test.java index d9376c9dfd..2c8ff0602c 100644 --- a/Ghidra/Features/FunctionGraph/src/test.slow/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin2Test.java +++ b/Ghidra/Features/FunctionGraph/src/test.slow/java/ghidra/app/plugin/core/functiongraph/FunctionGraphPlugin2Test.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. @@ -158,9 +158,9 @@ public class FunctionGraphPlugin2Test extends AbstractFunctionGraphTest { undockSatellite(); - FGController newController = cloneGraph(); - assertUndockedProviderShowing(newController.getProvider()); - isSatelliteVisible(newController); + FGProvider newProvider = cloneGraph(); + assertUndockedProviderShowing(newProvider); + isSatelliteVisible(newProvider); } @SuppressWarnings("unchecked") diff --git a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/FcgProvider.java b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/FcgProvider.java index 6abd443815..6b8ef28c8c 100644 --- a/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/FcgProvider.java +++ b/Ghidra/Features/GraphFunctionCalls/src/main/java/functioncalls/plugin/FcgProvider.java @@ -533,7 +533,8 @@ public class FcgProvider addLocalAction(resetGraphAction); MultiStateDockingAction > layoutAction = - new MultiStateDockingAction<>(RELAYOUT_GRAPH_ACTION_NAME, plugin.getName()) { + new MultiStateDockingAction<>(RELAYOUT_GRAPH_ACTION_NAME, plugin.getName(), + KeyBindingType.SHARED) { @Override public void actionPerformed(ActionContext context) { diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/duallisting/VTDualListingDragNDropHandler.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/duallisting/VTDualListingDragNDropHandler.java index dbeeb83e46..b15cdfdcd6 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/duallisting/VTDualListingDragNDropHandler.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/duallisting/VTDualListingDragNDropHandler.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. @@ -29,7 +29,7 @@ import ghidra.feature.vt.api.main.*; import ghidra.feature.vt.api.markuptype.VTMarkupType; import ghidra.feature.vt.gui.plugin.VTController; import ghidra.feature.vt.gui.task.ApplyMarkupAtDestinationAddressTask; -import ghidra.features.base.codecompare.listing.ListingCodeComparisonPanel; +import ghidra.features.base.codecompare.listing.ListingCodeComparisonView; import ghidra.program.model.address.Address; import ghidra.program.util.ProgramLocation; import ghidra.util.Msg; @@ -41,7 +41,7 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable { private Duo listingPanels; private VTController controller; - ListingCodeComparisonPanel dualListingPanel; + ListingCodeComparisonView dualListingProvider; // Drag-N-Drop private DragSource dragSource; @@ -53,11 +53,11 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable { private DataFlavor[] acceptableFlavors; // data flavors that are valid. public VTDualListingDragNDropHandler(VTController controller, - ListingCodeComparisonPanel dualListingPanel) { + ListingCodeComparisonView dualListingProvider) { this.controller = controller; - this.dualListingPanel = dualListingPanel; - ListingPanel leftPanel = dualListingPanel.getListingPanel(LEFT); - ListingPanel rightPanel = dualListingPanel.getListingPanel(RIGHT); + this.dualListingProvider = dualListingProvider; + ListingPanel leftPanel = dualListingProvider.getListingPanel(LEFT); + ListingPanel rightPanel = dualListingProvider.getListingPanel(RIGHT); listingPanels = new Duo<>(leftPanel, rightPanel); setUpDragDrop(); } @@ -109,7 +109,7 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable { ProgramLocation programLocation = listingPanels.get(LEFT).getProgramLocation(p); VTMarkupItem markupItem = controller.getCurrentMarkupForLocation(programLocation, - dualListingPanel.getProgram(LEFT)); + dualListingProvider.getProgram(LEFT)); if (markupItem == null) { return false; } @@ -131,7 +131,7 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable { ProgramLocation programLocation = listingPanels.get(LEFT).getProgramLocation(p); VTMarkupItem markupItem = controller.getCurrentMarkupForLocation(programLocation, - dualListingPanel.getProgram(LEFT)); + dualListingProvider.getProgram(LEFT)); if (markupItem == null) { return null; } @@ -151,16 +151,16 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable { ProgramLocation loc = listingPanels.get(RIGHT).getProgramLocation(p); Address newDestinationAddress = - markupType.getAddress(loc, dualListingPanel.getProgram(RIGHT)); + markupType.getAddress(loc, dualListingProvider.getProgram(RIGHT)); if (newDestinationAddress == null) { - Msg.showInfo(getClass(), dualListingPanel, "Invalid Drop Location", + Msg.showInfo(getClass(), dualListingProvider, "Invalid Drop Location", markupType.getDisplayName() + " was not dropped at a valid location."); return; } if ((markupItem.getStatus() == VTMarkupItemStatus.SAME) && (SystemUtilities.isEqual(markupItem.getDestinationAddress(), newDestinationAddress))) { // Dropped at expected address and already the same there. - Msg.showInfo(getClass(), dualListingPanel, "Already The Same", markupType + Msg.showInfo(getClass(), dualListingProvider, "Already The Same", markupType .getDisplayName() + " was dropped at its expected\ndestination where the value is already the same."); return; diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/duallisting/VTListingContext.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/duallisting/VTListingContext.java index db7fe8a7af..76b0a33135 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/duallisting/VTListingContext.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/duallisting/VTListingContext.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. @@ -18,16 +18,16 @@ package ghidra.feature.vt.gui.duallisting; import docking.ComponentProvider; import ghidra.app.context.ListingActionContext; import ghidra.app.nav.Navigatable; -import ghidra.features.base.codecompare.panel.CodeComparisonPanel; -import ghidra.features.base.codecompare.panel.CodeComparisonPanelActionContext; +import ghidra.features.base.codecompare.panel.CodeComparisonView; +import ghidra.features.base.codecompare.panel.CodeComparisonViewActionContext; /** * Action context for a version tracking listing. */ public class VTListingContext extends ListingActionContext - implements CodeComparisonPanelActionContext { + implements CodeComparisonViewActionContext { - private CodeComparisonPanel codeComparisonPanel = null; + private CodeComparisonView codeComparisonView = null; /** * Creates an action context for a VT listing. @@ -40,15 +40,14 @@ public class VTListingContext extends ListingActionContext /** * Sets the CodeComparisonPanel associated with this context. - * @param codeComparisonPanel the code comparison panel. + * @param codeComparisonView the code comparison panel. */ - public void setCodeComparisonPanel( - CodeComparisonPanel codeComparisonPanel) { - this.codeComparisonPanel = codeComparisonPanel; + public void setCodeComparisonPanel(CodeComparisonView codeComparisonView) { + this.codeComparisonView = codeComparisonView; } @Override - public CodeComparisonPanel getCodeComparisonPanel() { - return codeComparisonPanel; + public CodeComparisonView getCodeComparisonView() { + return codeComparisonView; } } diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/duallisting/VTListingNavigator.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/duallisting/VTListingNavigator.java index bf1a9103a6..01f093f53f 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/duallisting/VTListingNavigator.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/duallisting/VTListingNavigator.java @@ -20,7 +20,6 @@ import javax.swing.Icon; import ghidra.app.nav.*; import ghidra.app.util.ListingHighlightProvider; import ghidra.app.util.viewer.listingpanel.ListingPanel; -import ghidra.features.base.codecompare.listing.ListingCodeComparisonPanel; import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; @@ -28,14 +27,11 @@ import ghidra.util.UniversalIdGenerator; public class VTListingNavigator implements Navigatable { - private final ListingCodeComparisonPanel dualListingPanel; private final ListingPanel listingPanel; private long id; - public VTListingNavigator(ListingCodeComparisonPanel dualListingPanel, - ListingPanel listingPanel) { + public VTListingNavigator(ListingPanel listingPanel) { - this.dualListingPanel = dualListingPanel; this.listingPanel = listingPanel; id = UniversalIdGenerator.nextID().getValue(); } diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/plugin/VTPlugin.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/plugin/VTPlugin.java index 58037e7b25..348e501811 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/plugin/VTPlugin.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/plugin/VTPlugin.java @@ -87,9 +87,9 @@ public class VTPlugin extends Plugin { private VTController controller; - // common resources - - // destination-side resources + // plugins we have to add to our tool manually + private Set additionalPluginNames = new HashSet<>(Set.of( + "ghidra.features.codecompare.plugin.FunctionComparisonPlugin")); private VTMatchTableProvider matchesProvider; private VTMarkupItemsTableProvider markupProvider; @@ -99,16 +99,15 @@ public class VTPlugin extends Plugin { public VTPlugin(PluginTool tool) { super(tool); + + tool.setUnconfigurable(); + OWNER = getName(); controller = new VTControllerImpl(this); - matchesProvider = new VTMatchTableProvider(controller); - markupProvider = new VTMarkupItemsTableProvider(controller); - impliedMatchesTable = new VTImpliedMatchesTableProvider(controller); - functionAssociationProvider = new VTFunctionAssociationProvider(controller); + registerServiceProvided(VTController.class, controller); + toolManager = new VTSubToolManager(this); createActions(); - registerServiceProvided(VTController.class, controller); - tool.setUnconfigurable(); DockingActionIf saveAs = getToolAction("Save Tool As"); tool.removeAction(saveAs); @@ -116,11 +115,7 @@ public class VTPlugin extends Plugin { DockingActionIf export = getToolAction("Export Tool"); tool.removeAction(export); - new MatchStatusUpdaterAssociationHook(controller); - new ImpliedMatchAssociationHook(controller); - initializeOptions(); - } private DockingActionIf getToolAction(String actionName) { @@ -145,9 +140,16 @@ public class VTPlugin extends Plugin { protected void init() { removeUnwantedPlugins(); - addCustomPlugins(); + matchesProvider = new VTMatchTableProvider(controller); + markupProvider = new VTMarkupItemsTableProvider(controller); + impliedMatchesTable = new VTImpliedMatchesTableProvider(controller); + functionAssociationProvider = new VTFunctionAssociationProvider(controller); + + new MatchStatusUpdaterAssociationHook(controller); + new ImpliedMatchAssociationHook(controller); + maybeShowHelp(); } @@ -161,11 +163,11 @@ public class VTPlugin extends Plugin { private void addCustomPlugins() { - List names = - new ArrayList<>(List.of("ghidra.features.codecompare.plugin.FunctionComparisonPlugin")); List plugins = tool.getManagedPlugins(); - Set existingNames = - plugins.stream().map(c -> c.getName()).collect(Collectors.toSet()); + Set existingNames = new HashSet<>( + plugins.stream() + .map(c -> c.getName()) + .collect(Collectors.toSet())); // Note: we check to see if the plugins we want to add have already been added to the tool. // We should not need to do this, but once the tool has been saved with the plugins added, @@ -173,7 +175,7 @@ public class VTPlugin extends Plugin { // easier than modifying the default to file to load the plugins, since the amount of xml // required for that is non-trivial. try { - for (String className : names) { + for (String className : additionalPluginNames) { if (!existingNames.contains(className)) { tool.addPlugin(className); } diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/functionassociation/VTFunctionAssociationProvider.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/functionassociation/VTFunctionAssociationProvider.java index d1437e0698..341f64863b 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/functionassociation/VTFunctionAssociationProvider.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/functionassociation/VTFunctionAssociationProvider.java @@ -39,6 +39,7 @@ import docking.widgets.label.GDLabel; import docking.widgets.table.threaded.ThreadedTableModel; import generic.theme.GIcon; import generic.theme.GThemeDefaults.Colors; +import ghidra.app.services.FunctionComparisonService; import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.feature.vt.api.db.DeletedMatch; import ghidra.feature.vt.api.impl.VTEvent; @@ -48,7 +49,7 @@ import ghidra.feature.vt.gui.actions.*; import ghidra.feature.vt.gui.duallisting.VTListingNavigator; import ghidra.feature.vt.gui.plugin.*; import ghidra.feature.vt.gui.util.MatchInfo; -import ghidra.features.base.codecompare.listing.ListingCodeComparisonPanel; +import ghidra.features.base.codecompare.listing.ListingCodeComparisonView; import ghidra.features.base.codecompare.panel.FunctionComparisonPanel; import ghidra.framework.model.*; import ghidra.framework.options.Options; @@ -218,10 +219,10 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter @Override public List getPopupActions(Tool t, ActionContext context) { if (context.getComponentProvider() == this) { - ListingCodeComparisonPanel dualListingPanel = - functionComparisonPanel.getDualListingPanel(); - if (dualListingPanel != null) { - ListingPanel leftPanel = dualListingPanel.getListingPanel(LEFT); + ListingCodeComparisonView dualListingProvider = + functionComparisonPanel.getDualListingView(); + if (dualListingProvider != null) { + ListingPanel leftPanel = dualListingProvider.getListingPanel(LEFT); return leftPanel.getHeaderActions(getOwner()); } } @@ -247,22 +248,22 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter // Tool bar or function compare panel. if (isToolbarButtonAction || functionComparisonPanel.isAncestorOf(sourceComponent)) { - ListingCodeComparisonPanel dualListingPanel = - functionComparisonPanel.getDualListingPanel(); + ListingCodeComparisonView dualListingProvider = + functionComparisonPanel.getDualListingView(); boolean isShowingDualListing = - (dualListingPanel != null) && dualListingPanel.isVisible(); + (dualListingProvider != null) && dualListingProvider.isVisible(); boolean sourceIsADualFieldPanel = - isShowingDualListing && dualListingPanel.isAncestorOf(sourceComponent) && + isShowingDualListing && dualListingProvider.isAncestorOf(sourceComponent) && (sourceComponent instanceof FieldPanel); ListingPanel listingPanel = null; // Default is don't create a function association listing context. // Is the action being taken on the dual listing? if (sourceIsADualFieldPanel) { - listingPanel = dualListingPanel.getListingPanel((FieldPanel) sourceComponent); + listingPanel = dualListingProvider.getListingPanel((FieldPanel) sourceComponent); } // Is the action being taken on a toolbar button while the dual listing is visible? else if (isToolbarButtonAction && isShowingDualListing) { - listingPanel = dualListingPanel.getActiveListingPanel(); + listingPanel = dualListingProvider.getActiveListingPanel(); } // If the dual listing is showing and this is a toolbar action or the action is // on one of the listings in the ListingCodeComparisonPanel @@ -270,14 +271,13 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter // popup actions for the ListingDiff and also the function association actions // for the functions selected in the tables. if (listingPanel != null) { - VTListingNavigator vtListingNavigator = - new VTListingNavigator(dualListingPanel, listingPanel); + VTListingNavigator vtListingNavigator = new VTListingNavigator(listingPanel); VTFunctionAssociationCompareContext vtListingContext = new VTFunctionAssociationCompareContext(this, vtListingNavigator, tool, sourceFunction, destinationFunction, getExistingMatch(sourceFunction, destinationFunction)); - vtListingContext.setCodeComparisonPanel(dualListingPanel); - vtListingContext.setContextObject(dualListingPanel); + vtListingContext.setCodeComparisonPanel(dualListingProvider); + vtListingContext.setContextObject(dualListingProvider); vtListingContext.setSourceObject(source); return vtListingContext; } @@ -334,6 +334,8 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter destinationFunctionsTable.dispose(); destinationTableFilterPanel.dispose(); + functionComparisonPanel.dispose(); + tool.removePopupActionProvider(this); } @@ -368,9 +370,12 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter statusPanel.add(statusLabel, BorderLayout.CENTER); dualTablePanel.add(statusPanel, BorderLayout.SOUTH); - functionComparisonPanel = new FunctionComparisonPanel(tool, getOwner()); + // Note: this service should never be null, since it is added by the VTPlugin + FunctionComparisonService fcService = tool.getService(FunctionComparisonService.class); + functionComparisonPanel = fcService.createComparisonViewer(); + addSpecificCodeComparisonActions(); - functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.NAME); + functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonView.NAME); functionComparisonPanel.setTitlePrefixes("Source:", "Destination:"); comparisonSplitPane = @@ -760,12 +765,9 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter sourceFunctionsModel.setFilterSettings(filterSettings); destinationFunctionsModel.setFilterSettings(filterSettings); reload(); - functionComparisonPanel.readConfigState(getName(), saveState); } public void writeConfigState(SaveState saveState) { - // save config state here - functionComparisonPanel.writeConfigState(getName(), saveState); saveState.putEnum(FILTER_SETTINGS_KEY, filterSettings); } diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/VTMarkupItemContext.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/VTMarkupItemContext.java index 9ee05cfb95..5086314c31 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/VTMarkupItemContext.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/VTMarkupItemContext.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. @@ -19,17 +19,17 @@ import java.util.List; import docking.DefaultActionContext; import ghidra.feature.vt.api.main.VTMarkupItem; -import ghidra.features.base.codecompare.panel.CodeComparisonPanel; -import ghidra.features.base.codecompare.panel.CodeComparisonPanelActionContext; +import ghidra.features.base.codecompare.panel.CodeComparisonView; +import ghidra.features.base.codecompare.panel.CodeComparisonViewActionContext; /** * Action context for the version tracking markup item provider. */ public class VTMarkupItemContext extends DefaultActionContext - implements CodeComparisonPanelActionContext { + implements CodeComparisonViewActionContext { private final List selectedItems; - private CodeComparisonPanel codeComparisonPanel = null; + private CodeComparisonView codeComparisonView; /** * Creates an action context for the VT markup item provider. @@ -50,16 +50,15 @@ public class VTMarkupItemContext extends DefaultActionContext } /** - * Sets the CodeComparisonPanel associated with this context. - * @param codeComparisonPanel the code comparison panel. + * Sets the comparison provider associated with this context. + * @param codeComparisonView the code comparison view. */ - public void setCodeComparisonPanel( - CodeComparisonPanel codeComparisonPanel) { - this.codeComparisonPanel = codeComparisonPanel; + public void setCodeComparisonView(CodeComparisonView codeComparisonView) { + this.codeComparisonView = codeComparisonView; } @Override - public CodeComparisonPanel getCodeComparisonPanel() { - return codeComparisonPanel; + public CodeComparisonView getCodeComparisonView() { + return codeComparisonView; } } diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/VTMarkupItemsTableProvider.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/VTMarkupItemsTableProvider.java index 51bd59b1a0..2f24f2ac6e 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/VTMarkupItemsTableProvider.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/gui/provider/markuptable/VTMarkupItemsTableProvider.java @@ -39,6 +39,7 @@ import docking.widgets.table.GTable; import docking.widgets.table.RowObjectTableModel; import docking.widgets.table.threaded.ThreadedTableModel; import generic.theme.GIcon; +import ghidra.app.services.FunctionComparisonService; import ghidra.app.util.viewer.listingpanel.ListingPanel; import ghidra.app.util.viewer.listingpanel.ProgramLocationListener; import ghidra.feature.vt.api.main.*; @@ -51,8 +52,8 @@ import ghidra.feature.vt.gui.filters.Filter.FilterEditingStatus; import ghidra.feature.vt.gui.plugin.*; import ghidra.feature.vt.gui.provider.markuptable.VTMarkupItemsTableModel.AppliedDestinationAddressTableColumn; import ghidra.feature.vt.gui.util.*; -import ghidra.features.base.codecompare.listing.ListingCodeComparisonPanel; -import ghidra.features.base.codecompare.panel.CodeComparisonPanel; +import ghidra.features.base.codecompare.listing.ListingCodeComparisonView; +import ghidra.features.base.codecompare.panel.CodeComparisonView; import ghidra.features.base.codecompare.panel.FunctionComparisonPanel; import ghidra.framework.model.DomainObjectChangedEvent; import ghidra.framework.options.Options; @@ -154,28 +155,33 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter markupItemsTablePanel.add(tablePanel, BorderLayout.CENTER); markupItemsTablePanel.add(filterAreaPanel, BorderLayout.SOUTH); - functionComparisonPanel = new FunctionComparisonPanel(tool, getOwner()); + // Note: this service should never be null, since it is added by the VTPlugin + FunctionComparisonService fcService = tool.getService(FunctionComparisonService.class); + functionComparisonPanel = fcService.createComparisonViewer(); + addSpecificCodeComparisonActions(); - functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.NAME); + functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonView.NAME); functionComparisonPanel.getAccessibleContext().setAccessibleName("Function Comparison"); functionComparisonPanel.setTitlePrefixes("Source:", "Destination:"); - ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel(); - if (dualListingPanel != null) { + ListingCodeComparisonView dualListingProvider = + functionComparisonPanel.getDualListingView(); + if (dualListingProvider != null) { - dualListingPanel.getListingPanel(LEFT) + dualListingProvider.getListingPanel(LEFT) .setProgramLocationListener(new SourceProgramLocationListener()); - dualListingPanel.getListingPanel(RIGHT) + dualListingProvider.getListingPanel(RIGHT) .setProgramLocationListener( new DestinationProgramLocationListener()); sourceHighlightProvider = new VTDualListingHighlightProvider(controller, true); destinationHighlightProvider = new VTDualListingHighlightProvider(controller, false); - dualListingPanel.addHighlightProviders(sourceHighlightProvider, + dualListingProvider.addHighlightProviders(sourceHighlightProvider, destinationHighlightProvider); - sourceHighlightProvider.setListingPanel(dualListingPanel.getListingPanel(LEFT)); - destinationHighlightProvider.setListingPanel(dualListingPanel.getListingPanel(RIGHT)); + sourceHighlightProvider.setListingPanel(dualListingProvider.getListingPanel(LEFT)); + destinationHighlightProvider + .setListingPanel(dualListingProvider.getListingPanel(RIGHT)); - new VTDualListingDragNDropHandler(controller, dualListingPanel); + new VTDualListingDragNDropHandler(controller, dualListingProvider); } splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, markupItemsTablePanel, @@ -250,8 +256,8 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter // the same destination address. processingMarkupItemSelected = true; - ListingCodeComparisonPanel dualListingPanel = - functionComparisonPanel.getDualListingPanel(); + ListingCodeComparisonView dualListingPanel = + functionComparisonPanel.getDualListingView(); VTMarkupItem markupItem = null; if (table.getSelectedRowCount() == 1) { // we get out the model here in case it has been wrapped by one of the filters @@ -358,7 +364,8 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter * Otherwise, hide it. */ private void showComparisonPanelWithinProvider(boolean show) { - ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel(); + ListingCodeComparisonView dualListingProvider = + functionComparisonPanel.getDualListingView(); boolean contains = markupPanel.isAncestorOf(splitPane); if (show) { if (!contains) { @@ -369,10 +376,10 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter splitPane.add(markupItemsTablePanel); splitPane.add(functionComparisonPanel); markupPanel.add(splitPane, BorderLayout.CENTER); - if (dualListingPanel != null) { - dualListingPanel.getListingPanel(LEFT) + if (dualListingProvider != null) { + dualListingProvider.getListingPanel(LEFT) .setProgramLocationListener(new SourceProgramLocationListener()); - dualListingPanel.getListingPanel(LEFT) + dualListingProvider.getListingPanel(LEFT) .setProgramLocationListener(new DestinationProgramLocationListener()); } @@ -386,9 +393,9 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter else { if (contains) { // Remove the split pane. - if (dualListingPanel != null) { - dualListingPanel.getListingPanel(LEFT).setProgramLocationListener(null); - dualListingPanel.getListingPanel(RIGHT).setProgramLocationListener(null); + if (dualListingProvider != null) { + dualListingProvider.getListingPanel(LEFT).setProgramLocationListener(null); + dualListingProvider.getListingPanel(RIGHT).setProgramLocationListener(null); } markupPanel.remove(splitPane); splitPane.remove(functionComparisonPanel); @@ -471,9 +478,10 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter @Override public List getPopupActions(Tool t, ActionContext context) { - ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel(); - if (context.getComponentProvider() == this && dualListingPanel != null) { - ListingPanel sourcePanel = dualListingPanel.getListingPanel(LEFT); + ListingCodeComparisonView dualListingProvider = + functionComparisonPanel.getDualListingView(); + if (context.getComponentProvider() == this && dualListingProvider != null) { + ListingPanel sourcePanel = dualListingProvider.getListingPanel(LEFT); return sourcePanel.getHeaderActions(getOwner()); } return new ArrayList<>(); @@ -483,34 +491,35 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter public ActionContext getActionContext(MouseEvent event) { Object source = (event != null) ? event.getSource() : null; Component sourceComponent = (source instanceof Component) ? (Component) source : null; + // If action is on the markup table, return a markup item context for markup popup actions. if (event == null || tablePanel.isAncestorOf(sourceComponent)) { List selectedItems = getSelectedMarkupItems(); VTMarkupItemContext vtMarkupItemContext = new VTMarkupItemContext(this, selectedItems); if (functionComparisonPanel.isVisible()) { - CodeComparisonPanel displayedPanel = - functionComparisonPanel.getDisplayedPanel(); - vtMarkupItemContext.setCodeComparisonPanel(displayedPanel); + CodeComparisonView displayedProvider = + functionComparisonPanel.getDisplayedView(); + vtMarkupItemContext.setCodeComparisonView(displayedProvider); } return vtMarkupItemContext; } + // Is the action being taken on the dual listing. - ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel(); - if (dualListingPanel != null && dualListingPanel.isAncestorOf(sourceComponent)) { - // If the action is on one of the listings in the ListingCodeComparisonPanel + ListingCodeComparisonView listingView = functionComparisonPanel.getDualListingView(); + if (listingView != null && listingView.isAncestorOf(sourceComponent)) { + // If the action is on one of the listings in the Listing view // then return a special version tracking listing context. This will allow // popup actions for the ListingDiff and also the markup item actions for the // current markup item. if (sourceComponent instanceof FieldPanel) { ListingPanel listingPanel = - dualListingPanel.getListingPanel((FieldPanel) sourceComponent); + listingView.getListingPanel((FieldPanel) sourceComponent); if (listingPanel != null) { - VTListingNavigator vtListingNavigator = - new VTListingNavigator(dualListingPanel, listingPanel); + VTListingNavigator vtListingNavigator = new VTListingNavigator(listingPanel); VTListingContext vtListingContext = new VTListingContext(this, vtListingNavigator); - vtListingContext.setCodeComparisonPanel(dualListingPanel); - vtListingContext.setContextObject(dualListingPanel); + vtListingContext.setCodeComparisonPanel(listingView); + vtListingContext.setContextObject(listingView); vtListingContext.setSourceObject(source); return vtListingContext; } @@ -542,6 +551,8 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter return; } + functionComparisonPanel.dispose(); + // must remove the listener first to avoid callback whilst we are disposing ListSelectionModel selectionModel = markupItemsTable.getSelectionModel(); selectionModel.removeListSelectionListener(markupItemSelectionListener); @@ -564,9 +575,10 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter private void refresh() { markupItemsTableModel.reload(false); markupItemsTable.repaint(); - ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel(); - if (dualListingPanel != null) { - dualListingPanel.updateListings(); + ListingCodeComparisonView dualListingProvider = + functionComparisonPanel.getDualListingView(); + if (dualListingProvider != null) { + dualListingProvider.updateListings(); } sourceHighlightProvider.updateMarkup(); destinationHighlightProvider.updateMarkup(); @@ -773,11 +785,12 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter * @return true if the dual listing is showing */ public boolean isDualListingShowing() { - ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel(); - if (dualListingPanel == null) { + ListingCodeComparisonView dualListingProvider = + functionComparisonPanel.getDualListingView(); + if (dualListingProvider == null) { return false; } - return dualListingPanel.isShowing(); + return dualListingProvider.isShowing(); } @Override @@ -836,7 +849,6 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter * @param saveState the configuration state to restore */ public void readConfigState(SaveState saveState) { - functionComparisonPanel.readConfigState(getName(), saveState); showComparisonPanelWithinProvider(saveState.getBoolean(SHOW_COMPARISON_PANEL, true)); for (Filter filter : filters) { @@ -881,8 +893,6 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter * @param saveState the new configuration state */ public void writeConfigState(SaveState saveState) { - // save config state here - functionComparisonPanel.writeConfigState(getName(), saveState); saveState.putBoolean(SHOW_COMPARISON_PANEL, functionComparisonPanel.isShowing()); for (Filter filter : filters) { @@ -947,6 +957,15 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter refilter(); // this will do nothing if we are frozen } + /** + * Gets the function comparison panel component that possibly contains multiple different views + * for comparing code such as a dual listing. + * @return the function comparison panel + */ + public FunctionComparisonPanel getFunctionComparisonPanel() { + return functionComparisonPanel; + } + //================================================================================================== // Inner Classes //================================================================================================== @@ -1010,13 +1029,4 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter } } } - - /** - * Gets the function comparison panel component that possibly contains multiple different views - * for comparing code such as a dual listing. - * @return the function comparison panel - */ - public FunctionComparisonPanel getFunctionComparisonPanel() { - return functionComparisonPanel; - } } diff --git a/Ghidra/Features/VersionTracking/src/screen/java/help/screenshot/VersionTrackingPluginScreenShots.java b/Ghidra/Features/VersionTracking/src/screen/java/help/screenshot/VersionTrackingPluginScreenShots.java index c2181bf555..ce19a69057 100644 --- a/Ghidra/Features/VersionTracking/src/screen/java/help/screenshot/VersionTrackingPluginScreenShots.java +++ b/Ghidra/Features/VersionTracking/src/screen/java/help/screenshot/VersionTrackingPluginScreenShots.java @@ -52,7 +52,7 @@ import ghidra.feature.vt.gui.provider.onetomany.VTMatchSourceTableProvider; import ghidra.feature.vt.gui.task.*; import ghidra.feature.vt.gui.util.MatchInfo; import ghidra.feature.vt.gui.wizard.add.*; -import ghidra.features.base.codecompare.listing.ListingCodeComparisonPanel; +import ghidra.features.base.codecompare.listing.ListingCodeComparisonView; import ghidra.framework.main.DataTreeDialog; import ghidra.framework.main.datatree.DataTree; import ghidra.framework.main.datatree.ProjectDataTreePanel; @@ -765,7 +765,7 @@ public class VersionTrackingPluginScreenShots extends GhidraScreenShotGenerator JComponent component = provider.getComponent(); Component listingComponent = - findComponentByName(component, ListingCodeComparisonPanel.NAME); + findComponentByName(component, ListingCodeComparisonView.NAME); if (listingComponent == null) { return false; // not in the parent's hierarchy } diff --git a/Ghidra/Features/VersionTracking/src/test.slow/java/ghidra/feature/vt/gui/provider/VTImpliedMatchCorrelatorTest.java b/Ghidra/Features/VersionTracking/src/test.slow/java/ghidra/feature/vt/gui/provider/VTImpliedMatchCorrelatorTest.java index 25ef7055cd..72cd6c8855 100644 --- a/Ghidra/Features/VersionTracking/src/test.slow/java/ghidra/feature/vt/gui/provider/VTImpliedMatchCorrelatorTest.java +++ b/Ghidra/Features/VersionTracking/src/test.slow/java/ghidra/feature/vt/gui/provider/VTImpliedMatchCorrelatorTest.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. @@ -193,7 +193,7 @@ public class VTImpliedMatchCorrelatorTest extends AbstractVTCorrelatorTest { // get the resulting implied matches and verify that none of the matches that were already // created VTMatchSet impliedMatchSet = getVTMatchSet("Implied Match"); - Assert.assertNotEquals("vtMatchSet does not exist", null, impliedMatchSet); + assertNotNull(impliedMatchSet); // Now test that only the expected items are in this set for the given function we just // applied @@ -276,9 +276,7 @@ public class VTImpliedMatchCorrelatorTest extends AbstractVTCorrelatorTest { protected VTMatch getMatch(VTMatchSet matches, Address sourceAddress, Address destinationAddress) { - Iterator it = matches.getMatches().iterator(); - while (it.hasNext()) { - VTMatch match = it.next(); + for (VTMatch match : matches.getMatches()) { if (match.getSourceAddress().equals(sourceAddress) && match.getDestinationAddress().equals(destinationAddress)) { return match; diff --git a/Ghidra/Features/VersionTracking/src/test/java/ghidra/feature/vt/gui/VTTestEnv.java b/Ghidra/Features/VersionTracking/src/test/java/ghidra/feature/vt/gui/VTTestEnv.java index e750961871..e290d65cbb 100644 --- a/Ghidra/Features/VersionTracking/src/test/java/ghidra/feature/vt/gui/VTTestEnv.java +++ b/Ghidra/Features/VersionTracking/src/test/java/ghidra/feature/vt/gui/VTTestEnv.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. @@ -33,9 +33,9 @@ import ghidra.feature.vt.api.db.VTSessionDB; import ghidra.feature.vt.api.main.*; import ghidra.feature.vt.api.util.VTOptions; import ghidra.feature.vt.gui.plugin.*; +import ghidra.feature.vt.gui.provider.markuptable.VTMarkupItemsTableProvider; import ghidra.feature.vt.gui.provider.matchtable.VTMatchTableModel; import ghidra.feature.vt.gui.provider.matchtable.VTMatchTableProvider; -import ghidra.framework.plugintool.Plugin; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.listing.Program; import ghidra.test.AbstractGhidraHeadedIntegrationTest; @@ -56,9 +56,10 @@ public class VTTestEnv extends TestEnv { public VTTestEnv() throws Exception { - PluginTool tool = getTool(); - tool.removePlugins(new Plugin[] { getPlugin(ProgramManagerPlugin.class) }); - tool.addPlugin(VTPlugin.class.getName()); + PluginTool pluignTool = getTool(); + pluignTool.removePlugins(List.of(getPlugin(ProgramManagerPlugin.class))); + pluignTool.addPlugin(VTPlugin.class.getName()); + plugin = getPlugin(VTPlugin.class); controller = (VTController) getInstanceField("controller", plugin); matchTableProvider = (VTMatchTableProvider) getInstanceField("matchesProvider", plugin); @@ -238,7 +239,19 @@ public class VTTestEnv extends TestEnv { } public void focusMatchTable() { - runSwing(() -> matchTableProvider.getComponent().requestFocus()); + runSwing(() -> matchTableProvider.requestFocus()); + } + + public VTMarkupItemsTableProvider getMarkupItemsProvider() { + return (VTMarkupItemsTableProvider) getInstanceField("markupProvider", plugin); + } + + public void focusMarkupItemsTable() { + VTMarkupItemsTableProvider markupProvider = getMarkupItemsProvider(); + runSwing(() -> { + markupProvider.toFront(); + markupProvider.requestFocus(); + }); } public void triggerMatchTableDataChanged() { @@ -250,4 +263,5 @@ public class VTTestEnv extends TestEnv { public VTMatchTableProvider getMatchTableProvider() { return matchTableProvider; } + } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateDockingAction.java b/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateDockingAction.java index d30953c01c..e26cf20da7 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateDockingAction.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/menu/MultiStateDockingAction.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. @@ -67,7 +67,18 @@ public abstract class MultiStateDockingAction extends DockingAction { * @param owner the owner */ public MultiStateDockingAction(String name, String owner) { - super(name, owner); + this(name, owner, KeyBindingType.INDIVIDUAL); + } + + /** + * Constructor + * + * @param name the action name + * @param owner the owner + * @param type the key binding type + */ + public MultiStateDockingAction(String name, String owner, KeyBindingType type) { + super(name, owner, type); multiActionGenerator = context -> getStateActions(); // set this here so we don't have to check for null elsewhere diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/ObjectChooserDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/ObjectChooserDialog.java index 4062696e5e..905f7c46d3 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/ObjectChooserDialog.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/ObjectChooserDialog.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. @@ -88,6 +88,10 @@ public class ObjectChooserDialog extends DialogComponentProvider { return selectedObject; } + public void setSelectedObject(T t) { + table.selectRowObject(t); + } + public void setFilterText(String text) { table.setFilterText(text); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/FieldPanelCoordinator.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/FieldPanelScrollCoordinator.java similarity index 73% rename from Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/FieldPanelCoordinator.java rename to Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/FieldPanelScrollCoordinator.java index 1e2e02ea0d..9ce36a22ae 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/FieldPanelCoordinator.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/FieldPanelScrollCoordinator.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,10 @@ import docking.widgets.fieldpanel.FieldPanel; import docking.widgets.fieldpanel.listener.ViewListener; import docking.widgets.fieldpanel.support.ViewerPosition; - /** * Coordinates the scrolling of a set of field panels by sharing bound scroll models. */ -public class FieldPanelCoordinator implements ViewListener { +public class FieldPanelScrollCoordinator implements ViewListener { FieldPanel[] panels; boolean valuesChanging; @@ -33,19 +32,28 @@ public class FieldPanelCoordinator implements ViewListener { * Constructs a new FieldPanelCoordinatro to synchronize the scrolling of the given field panels. * @param panels the array of panels to synchronize. */ - public FieldPanelCoordinator(FieldPanel[] panels) { + public FieldPanelScrollCoordinator(FieldPanel[] panels) { this.panels = new FieldPanel[panels.length]; System.arraycopy(panels, 0, this.panels, 0, panels.length); - for(int i=0;i Note: The layouts that are locked together will be positioned so that the bottom of those - * layouts line up within the field panels. - */ -public class LayoutLockedFieldPanelCoordinator extends LineLockedFieldPanelCoordinator { - - /** - * Constructor for the coordinator. - * @param panels the field panels that will have their positions coordinated with each other. - */ - public LayoutLockedFieldPanelCoordinator(FieldPanel... panels) { - super(panels); - } - - @Override - public void viewChanged(FieldPanel fp, BigInteger index, int xPos, int yPos) { - if (valuesChanging) { - return; - } - try { - valuesChanging = true; - // "lockedLineIndex" is the IndexMap index indicating where this field panel - // is locked to the other when scrolling. - BigInteger lockedLineIndex1 = getLockedLineForPanel(fp); - if (lockedLineIndex1 == null) { // This shouldn't happen. - throw new AssertException("Couldn't find line number for indicated field panel." + - " FieldPanel is not one of those being managed by this coordinator."); - } - - // "topIndex" is the IndexMap index of the top of the listing in view. - BigInteger topIndex1 = index; - LayoutModel layoutModel1 = fp.getLayoutModel(); - Layout lockedLineLayout1 = layoutModel1.getLayout(lockedLineIndex1); - if (lockedLineLayout1 == null) { - return; // transitioning from one function to another. - } - - // "lockedLineHeight" is the height of the layout in this field panel - // where it is locked to the other panel when scrolling. - int lockedLineHeight1 = lockedLineLayout1.getHeight(); - - // numIndexes is the total number of indexes in this field panels indexMap. - BigInteger numIndexes1 = layoutModel1.getNumIndexes(); - Layout firstLayout1 = layoutModel1.getLayout(topIndex1); - - // "yPos" is a negative number indicating the number of pixels the start of the current - // layout is above the top of the field panel view. - - // "remainingHeight" is the number of pixels vertically from the first visible pixel - // in the layout at the top of the listing view to the end of that layout. - int remainingHeight = firstLayout1.getHeight() + yPos; - - // "offsetInLayout" is the number of pixels that the top of the listing view is below - // the start of the current layout. - int offsetInLayout1 = 0; - int offsetFromLockedIndex1 = 0; - if (lockedLineIndex1.compareTo(topIndex1) == 0) { - offsetInLayout1 -= yPos; - offsetFromLockedIndex1 += offsetInLayout1; - } - else if (lockedLineIndex1.compareTo(topIndex1) < 0) { - BigInteger currentIndex1 = lockedLineIndex1; - while (currentIndex1 != null && (currentIndex1.compareTo(numIndexes1) < 0) && - currentIndex1.compareTo(topIndex1) < 0) { - Layout currentLayout = layoutModel1.getLayout(currentIndex1); - if (currentLayout != null) { - offsetFromLockedIndex1 += currentLayout.getHeight(); - } - currentIndex1 = layoutModel1.getIndexAfter(currentIndex1); - } - offsetFromLockedIndex1 -= yPos; - } - else { // lockedlineIndex1 > topIndex1 - BigInteger currentIndex1 = layoutModel1.getIndexAfter(topIndex1); - while (currentIndex1 != null && (currentIndex1.compareTo(numIndexes1) < 0) && - currentIndex1.compareTo(lockedLineIndex1) < 0) { - Layout currentLayout = layoutModel1.getLayout(currentIndex1); - if (currentLayout != null) { - offsetFromLockedIndex1 -= currentLayout.getHeight(); - } - currentIndex1 = layoutModel1.getIndexAfter(currentIndex1); - } - offsetFromLockedIndex1 -= remainingHeight; - } - - // Position the views for the other panels to match the changed one the best they can. - for (int i = 0; i < panels.length; i++) { - if (panels[i] != fp) { - // Get the difference in height between our two line locked layouts. - LayoutModel layoutModel2 = panels[i].getLayoutModel(); - BigInteger numIndexes2 = layoutModel2.getNumIndexes(); - BigInteger lockedLineIndex2 = lockedLineNumbers[i]; - Layout lockedLineLayout2 = layoutModel2.getLayout(lockedLineIndex2); - if (lockedLineLayout2 == null) { - return; // Initializing panels. - } - int lockedLineHeight2 = lockedLineLayout2.getHeight(); - - // Handle when the locked line's layout is at the top of the viewer. - if (lockedLineIndex1.equals(topIndex1)) { - int difference = lockedLineHeight1 - lockedLineHeight2; // positive means layout1 is larger. - int yPos2 = yPos + difference; - // A negative yPos indicates the number of pixels to move the layout - // above the top of the view. - panels[i].setViewerPosition(lockedLineIndex2, xPos, yPos2); - return; - } - - // Start with the layout of the line locked index and position the top of the - // view at the same distance from it as the other view is from the layout for - // its locked line. - int offsetFromLockedIndex2 = - offsetFromLockedIndex1 + (lockedLineHeight2 - lockedLineHeight1); - int remainingOffset2 = offsetFromLockedIndex2; - BigInteger currentIndex2 = lockedLineIndex2; - if (remainingOffset2 < 0) { - currentIndex2 = layoutModel2.getIndexBefore(currentIndex2); - } - while (currentIndex2 != null && currentIndex2.compareTo(BigInteger.ZERO) >= 0 && - currentIndex2.compareTo(numIndexes2) < 0) { - Layout currentLayout2 = layoutModel2.getLayout(currentIndex2); - // Gaps in the code will cause the currentIndex to be the last byte's - // index before the gap. This results in a null layout for that index, - // so we need to go again to get past it. - if (currentLayout2 == null) { - if (remainingOffset2 < 0) { - // Go again when processing layout heights in reverse direction. - currentIndex2 = layoutModel2.getIndexBefore(currentIndex2); - continue; - } - else if (remainingOffset2 > 0) { - // Go again when processing layout heights in forward direction. - currentIndex2 = layoutModel2.getIndexAfter(currentIndex2); - continue; - } - return; // currentLayout2 is null. - } - int height = currentLayout2.getHeight(); - if (remainingOffset2 == 0) { - panels[i].setViewerPosition(currentIndex2, xPos, 0); - return; - } - else if (remainingOffset2 < 0) { - int offset = height + remainingOffset2; - if (offset >= 0) { - panels[i].setViewerPosition(currentIndex2, xPos, -offset); - return; - } - currentIndex2 = layoutModel2.getIndexBefore(currentIndex2); - remainingOffset2 = offset; - } - else { // remainingOffset2 > 0 - if (remainingOffset2 < height) { - panels[i].setViewerPosition(currentIndex2, xPos, -remainingOffset2); - return; - } - currentIndex2 = layoutModel2.getIndexAfter(currentIndex2); - remainingOffset2 -= height; - } - } - } - } - } - finally { - valuesChanging = false; - } - } -} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LayoutLockedFieldPanelScrollCoordinator.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LayoutLockedFieldPanelScrollCoordinator.java new file mode 100644 index 0000000000..c7308f12f0 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LayoutLockedFieldPanelScrollCoordinator.java @@ -0,0 +1,220 @@ +/* ### + * 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 docking.widgets.fieldpanel.internal; + +import java.math.BigInteger; + +import docking.widgets.fieldpanel.*; +import ghidra.util.exception.AssertException; + +/** + * A LayoutLockedFieldPanelCoordinator is an extension of a LineLockedFieldPanelCoordinator that + * handles the fact that field panel layouts vary in size. It coordinates the scrolling of a set + * of field panels by sharing bound scroll models that are locked together by a set of index + * numbers for the FieldPanel Layouts. All the field panels are locked together at the index + * numbers specified in the locked line array. + * In other words this coordinator tries to keep the layout indicated by the line (or index) + * for each field panel side by side with the indicated layout for each other field panel. + *
Note: The layouts that are locked together will be positioned so that the bottom of those + * layouts line up within the field panels. + */ +public class LayoutLockedFieldPanelScrollCoordinator extends LineLockedFieldPanelScrollCoordinator { + + /** + * Constructor for the coordinator. + * @param panels the field panels that will have their positions coordinated with each other. + */ + public LayoutLockedFieldPanelScrollCoordinator(FieldPanel... panels) { + super(panels); + } + + @Override + public void viewChanged(FieldPanel fp, BigInteger index, int xPos, int yPos) { + if (valuesChanging) { + return; + } + + valuesChanging = true; + try { + update(fp, index, xPos, yPos); + } + finally { + valuesChanging = false; + } + } + + private void update(FieldPanel fp, BigInteger index, int xPos, int yPos) { + + // "lockedLineIndex" is the IndexMap index indicating where this field panel + // is locked to the other when scrolling. + BigInteger lockedLineIndex1 = getLockedLineForPanel(fp); + if (lockedLineIndex1 == null) { // This shouldn't happen. + throw new AssertException("Couldn't find line number for indicated field panel." + + " FieldPanel is not one of those being managed by this coordinator."); + } + + // "topIndex" is the IndexMap index of the top of the listing in view. + BigInteger topIndex1 = index; + LayoutModel layoutModel1 = fp.getLayoutModel(); + Layout lockedLineLayout1 = layoutModel1.getLayout(lockedLineIndex1); + if (lockedLineLayout1 == null) { + return; // transitioning from one function to another. + } + + // "lockedLineHeight" is the height of the layout in this field panel + // where it is locked to the other panel when scrolling. + int lockedLineHeight1 = lockedLineLayout1.getHeight(); + + int offsetFromLockedIndex1 = + getOffsetFromLockedIndex(layoutModel1, topIndex1, lockedLineIndex1, yPos); + + // Position the views for the other panels to match the changed one the best they can. + FieldPanel otherPanel = getOtherPanel(fp); + + // Get the difference in height between our two line locked layouts. + LayoutModel layoutModel2 = otherPanel.getLayoutModel(); + BigInteger numIndexes2 = layoutModel2.getNumIndexes(); + BigInteger lockedLineIndex2 = getLockedLineNumber(otherPanel); + Layout lockedLineLayout2 = layoutModel2.getLayout(lockedLineIndex2); + if (lockedLineLayout2 == null) { + return; // Initializing panels. + } + int lockedLineHeight2 = lockedLineLayout2.getHeight(); + + // Handle when the locked line's layout is at the top of the viewer. + if (lockedLineIndex1.equals(topIndex1)) { + int difference = lockedLineHeight1 - lockedLineHeight2; // positive means layout1 is larger. + int yPos2 = yPos + difference; + // A negative yPos indicates the number of pixels to move the layout + // above the top of the view. + otherPanel.setViewerPosition(lockedLineIndex2, xPos, yPos2); + return; + } + + // Start with the layout of the line locked index and position the top of the + // view at the same distance from it as the other view is from the layout for + // its locked line. + int offsetFromLockedIndex2 = + offsetFromLockedIndex1 + (lockedLineHeight2 - lockedLineHeight1); + int remainingOffset2 = offsetFromLockedIndex2; + BigInteger currentIndex2 = lockedLineIndex2; + if (remainingOffset2 < 0) { + currentIndex2 = layoutModel2.getIndexBefore(currentIndex2); + } + while (currentIndex2 != null && currentIndex2.compareTo(BigInteger.ZERO) >= 0 && + currentIndex2.compareTo(numIndexes2) < 0) { + Layout currentLayout2 = layoutModel2.getLayout(currentIndex2); + // Gaps in the code will cause the currentIndex to be the last byte's + // index before the gap. This results in a null layout for that index, + // so we need to go again to get past it. + if (currentLayout2 == null) { + if (remainingOffset2 < 0) { + // Go again when processing layout heights in reverse direction. + currentIndex2 = layoutModel2.getIndexBefore(currentIndex2); + continue; + } + else if (remainingOffset2 > 0) { + // Go again when processing layout heights in forward direction. + currentIndex2 = layoutModel2.getIndexAfter(currentIndex2); + continue; + } + return; // currentLayout2 is null. + } + int height = currentLayout2.getHeight(); + if (remainingOffset2 == 0) { + otherPanel.setViewerPosition(currentIndex2, xPos, 0); + return; + } + else if (remainingOffset2 < 0) { + int offset = height + remainingOffset2; + if (offset >= 0) { + otherPanel.setViewerPosition(currentIndex2, xPos, -offset); + return; + } + currentIndex2 = layoutModel2.getIndexBefore(currentIndex2); + remainingOffset2 = offset; + } + else { // remainingOffset2 > 0 + if (remainingOffset2 < height) { + otherPanel.setViewerPosition(currentIndex2, xPos, -remainingOffset2); + return; + } + currentIndex2 = layoutModel2.getIndexAfter(currentIndex2); + remainingOffset2 -= height; + } + } + + } + + private int getOffsetFromLockedIndex(LayoutModel layoutModel1, BigInteger topIndex1, + BigInteger lockedLineIndex1, int yPos) { + + // numIndexes is the total number of indexes in this field panels indexMap. + BigInteger numIndexes1 = layoutModel1.getNumIndexes(); + Layout firstLayout1 = layoutModel1.getLayout(topIndex1); + + // "yPos" is a negative number indicating the number of pixels the start of the current + // layout is above the top of the field panel view. + + // "remainingHeight" is the number of pixels vertically from the first visible pixel + // in the layout at the top of the listing view to the end of that layout. + int remainingHeight = firstLayout1.getHeight() + yPos; + + // "offsetInLayout" is the number of pixels that the top of the listing view is below + // the start of the current layout. + int offsetInLayout1 = 0; + int offsetFromLockedIndex1 = 0; + if (lockedLineIndex1.compareTo(topIndex1) == 0) { + offsetInLayout1 -= yPos; + offsetFromLockedIndex1 += offsetInLayout1; + } + else if (lockedLineIndex1.compareTo(topIndex1) < 0) { + return addIndexesBelow(layoutModel1, topIndex1, lockedLineIndex1, + offsetFromLockedIndex1, yPos); + } + else { // lockedlineIndex1 > topIndex1 + BigInteger currentIndex1 = layoutModel1.getIndexAfter(topIndex1); + while (currentIndex1 != null && (currentIndex1.compareTo(numIndexes1) < 0) && + currentIndex1.compareTo(lockedLineIndex1) < 0) { + Layout currentLayout = layoutModel1.getLayout(currentIndex1); + if (currentLayout != null) { + offsetFromLockedIndex1 -= currentLayout.getHeight(); + } + currentIndex1 = layoutModel1.getIndexAfter(currentIndex1); + } + offsetFromLockedIndex1 -= remainingHeight; + } + + return offsetFromLockedIndex1; + } + + private int addIndexesBelow(LayoutModel layoutModel, BigInteger topIndex1, + BigInteger lockedLineIndex, int offsetFromLockedIndex1, int yPos) { + + BigInteger numIndexes = layoutModel.getNumIndexes(); + BigInteger currentIndex = lockedLineIndex; + while (currentIndex != null && (currentIndex.compareTo(numIndexes) < 0) && + currentIndex.compareTo(topIndex1) < 0) { + Layout currentLayout = layoutModel.getLayout(currentIndex); + if (currentLayout != null) { + offsetFromLockedIndex1 += currentLayout.getHeight(); + } + currentIndex = layoutModel.getIndexAfter(currentIndex); + } + offsetFromLockedIndex1 -= yPos; + return offsetFromLockedIndex1; + } +} diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LineLockedFieldPanelCoordinator.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LineLockedFieldPanelScrollCoordinator.java similarity index 92% rename from Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LineLockedFieldPanelCoordinator.java rename to Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LineLockedFieldPanelScrollCoordinator.java index bcbdc8e3cb..b08c038f5d 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LineLockedFieldPanelCoordinator.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LineLockedFieldPanelScrollCoordinator.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. @@ -29,17 +29,30 @@ import ghidra.util.exception.AssertException; * In other words this coordinator tries to keep the indicated line for each field panel * side by side with the indicated line for each other field panel. */ -public class LineLockedFieldPanelCoordinator extends FieldPanelCoordinator { +public class LineLockedFieldPanelScrollCoordinator extends FieldPanelScrollCoordinator { // Keep an array of the line number for each field panel where we are locking // these field panels together when scrolling or moving the cursor location. protected BigInteger[] lockedLineNumbers; - public LineLockedFieldPanelCoordinator(FieldPanel[] panels) { + public LineLockedFieldPanelScrollCoordinator(FieldPanel[] panels) { super(panels); resetLockedLines(); } + protected BigInteger getLockedLineNumber(FieldPanel fp) { + int index = getIndex(fp); + return lockedLineNumbers[index]; + } + + private int getIndex(FieldPanel fp) { + + if (panels[0] == fp) { + return 0; + } + return 1; + } + /** * Resets the locked line numbers for this field panel coordinator to their default * of each being zero. @@ -117,8 +130,9 @@ public class LineLockedFieldPanelCoordinator extends FieldPanelCoordinator { @Override public void viewChanged(FieldPanel fp, BigInteger index, int xPos, int yPos) { - if (valuesChanging) + if (valuesChanging) { return; + } try { valuesChanging = true; BigInteger fpLineNumber = getLockedLineForPanel(fp); diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GFilterTable.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GFilterTable.java index d804c0a48b..81d853e41b 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GFilterTable.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GFilterTable.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. @@ -150,6 +150,10 @@ public class GFilterTableextends JPanel { return rowObject; } + public int getRow(ROW_OBJECT rowObject) { + return filterPanel.getViewRow(rowObject); + } + public ROW_OBJECT getItemAt(Point point) { int viewRow = table.rowAtPoint(point); if (viewRow < 0) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableFilterPanel.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableFilterPanel.java index aa09704008..39a01abed4 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableFilterPanel.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableFilterPanel.java @@ -669,6 +669,16 @@ public class GTableFilterPanel extends JPanel { return rowObject; } + /** + * Returns the view row for the given object. + + * @param t the row object + * @return the view row + */ + public int getViewRow(ROW_OBJECT t) { + return rowObjectFilterModel.getViewIndex(t); + } + /** * Select the given row object. No selection will be made if the object is filtered out of * view. Passing {@code null} will clear the selection. diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableWidget.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableWidget.java index 3ad662413d..a90b337d2b 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableWidget.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/table/GTableWidget.java @@ -213,6 +213,10 @@ public class GTableWidget extends JPanel { return gFilterTable.getRowObject(row); } + public int getRow(T rowObject) { + return gFilterTable.getRow(rowObject); + } + public void selectRow(int row) { table.selectRow(row); } diff --git a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/featurette/VgSatelliteFeaturette.java b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/featurette/VgSatelliteFeaturette.java index 31cac30c53..a77c645cb4 100644 --- a/Ghidra/Framework/Graph/src/main/java/ghidra/graph/featurette/VgSatelliteFeaturette.java +++ b/Ghidra/Framework/Graph/src/main/java/ghidra/graph/featurette/VgSatelliteFeaturette.java @@ -113,7 +113,7 @@ public class VgSatelliteFeaturette clientSatelliteListener = Optional.empty(); + private List satelliteListeners = new ArrayList<>(); // this internal listener is the way we manage keeping our state in sync with the // graph component, as well as how we notify the client listener @@ -110,7 +111,7 @@ public class VisualGraphView l.satelliteVisibilityChanged(docked, visible)); + satelliteListeners.forEach(l -> l.satelliteVisibilityChanged(docked, visible)); }; private boolean satelliteDocked = true; @@ -186,8 +187,10 @@ public class VisualGraphView l) { @@ -454,10 +457,14 @@ public class VisualGraphView (primaryViewer)); diff --git a/Ghidra/Framework/Graph/src/test.slow/java/ghidra/graph/viewer/GraphComponentTest.java b/Ghidra/Framework/Graph/src/test.slow/java/ghidra/graph/viewer/GraphComponentTest.java index a802981ad9..d0757df62e 100644 --- a/Ghidra/Framework/Graph/src/test.slow/java/ghidra/graph/viewer/GraphComponentTest.java +++ b/Ghidra/Framework/Graph/src/test.slow/java/ghidra/graph/viewer/GraphComponentTest.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. @@ -16,6 +16,7 @@ package ghidra.graph.viewer; import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.*; import java.awt.*; @@ -37,7 +38,6 @@ import generic.test.TestUtils; import generic.util.WindowUtilities; import ghidra.graph.graphs.*; import ghidra.graph.support.*; -import ghidra.util.Msg; import util.CollectionUtils; public class GraphComponentTest extends AbstractVisualGraphTest { @@ -198,21 +198,6 @@ public class GraphComponentTest extends AbstractVisualGraphTest { twinkle(v); - // TODO debug - if (v.hasBeenEmphasised()) { - - // below, once we are zoomed-out, then the emphasis should happen - Msg.debug(this, "No vertice should have been emphasized, since we are zoomed in " + - " - twinkled vertex: " + v + "; all vertex states: "); - vertices.forEach(vertex -> { - Msg.debug(this, vertex + " - " + vertex.hasBeenEmphasised()); - }); - - // maybe the graph was scaled?? - Msg.debug(this, "graph scale (should be 1.0): " + - GraphViewerUtils.getGraphScale(graphComponent.getPrimaryViewer())); - } - assertFalse(v.hasBeenEmphasised()); scaleGraphPastInteractionThreshold(); diff --git a/Ghidra/Framework/Graph/src/test.slow/java/ghidra/graph/viewer/VisualGraphViewTest.java b/Ghidra/Framework/Graph/src/test.slow/java/ghidra/graph/viewer/VisualGraphViewTest.java index 6beaaf9695..14ff160c21 100644 --- a/Ghidra/Framework/Graph/src/test.slow/java/ghidra/graph/viewer/VisualGraphViewTest.java +++ b/Ghidra/Framework/Graph/src/test.slow/java/ghidra/graph/viewer/VisualGraphViewTest.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. @@ -58,7 +58,7 @@ public class VisualGraphViewTest extends AbstractSimpleVisualGraphTest { */ SpySatelliteListener listener = new SpySatelliteListener(); - view.setSatelliteListener(listener); + view.addSatelliteListener(listener); assertTrue(view.isSatelliteVisible()); assertTrue(view.isSatelliteDocked()); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/SymbolPathParser.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/SymbolPathParser.java index 412017fc96..48dc4d910e 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/SymbolPathParser.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/util/SymbolPathParser.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. @@ -56,8 +56,7 @@ public class SymbolPathParser { */ public static List parse(String name, boolean ignoreLeaderParens) { if (StringUtils.isBlank(name)) { - throw new IllegalArgumentException( - "Symbol list must contain at least one symbol name!"); + throw new IllegalArgumentException("Pathname cannot be empty!"); } if (skipParsing(name, ignoreLeaderParens)) { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/correlate/HashedFunctionAddressCorrelation.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/correlate/HashedFunctionAddressCorrelation.java index 3770eb175a..e58ce8c14c 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/correlate/HashedFunctionAddressCorrelation.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/correlate/HashedFunctionAddressCorrelation.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. @@ -83,7 +83,7 @@ public class HashedFunctionAddressCorrelation implements ListingAddressCorrelati * @param leftFunction the first function * @param rightFunction the second function * @param monitor the task monitor that indicates progress and allows the user to cancel. - * @throws CancelledException if the user cancels + * @throws CancelledException if the user cancels1 * @throws MemoryAccessException if either functions memory can't be accessed. */ public HashedFunctionAddressCorrelation(Function leftFunction, Function rightFunction, @@ -186,8 +186,9 @@ public class HashedFunctionAddressCorrelation implements ListingAddressCorrelati destStore.removeHash(destEntry); // Remove this HashEntry cancelMatch = true; // Cancel the match } - if (cancelMatch) + if (cancelMatch) { return; + } ArrayList srcInstructVec = new ArrayList (); ArrayList destInstructVec = new ArrayList (); ArrayList srcBlockVec = new ArrayList (); @@ -197,8 +198,9 @@ public class HashedFunctionAddressCorrelation implements ListingAddressCorrelati HashStore.extendMatch(matchSize, srcInstruct, srcMatch, destInstruct, destMatch, hashCalc); srcStore.matchHash(srcMatch, srcInstructVec, srcBlockVec); destStore.matchHash(destMatch, destInstructVec, destBlockVec); - for (int i = 0; i < srcInstructVec.size(); ++i) + for (int i = 0; i < srcInstructVec.size(); ++i) { srcToDest.put(srcInstructVec.get(i).getAddress(), destInstructVec.get(i).getAddress()); + } } /** @@ -218,16 +220,15 @@ public class HashedFunctionAddressCorrelation implements ListingAddressCorrelati int matchSize = entry.hash.size; for (InstructHash curInstruct : entry.instList) { ArrayList hashList = strategy.calcHashes(curInstruct, matchSize, store); - Iterator iter = hashList.iterator(); - while (iter.hasNext()) { - Hash curHash = iter.next(); + for (Hash curHash : hashList) { DisambiguatorEntry curEntry = entryMap.get(curHash); if (curEntry == null) { curEntry = new DisambiguatorEntry(curHash, curInstruct); entryMap.put(curHash, curEntry); } - else + else { curEntry.count += 1; + } } } return entryMap; @@ -249,21 +250,24 @@ public class HashedFunctionAddressCorrelation implements ListingAddressCorrelati TreeMap destDisambig = constructDisambiguatorTree(destEntry, destStore, strategy); int count = 0; - Iterator iter = srcDisambig.values().iterator(); - while (iter.hasNext()) { - DisambiguatorEntry srcDisEntry = iter.next(); - if (srcDisEntry.count != 1) + for (DisambiguatorEntry srcDisEntry : srcDisambig.values()) { + if (srcDisEntry.count != 1) { continue; + } // Its possible for this InstructHash to have been matched by an earlier DisambiguatorEntry - if (srcDisEntry.instruct.isMatched) + if (srcDisEntry.instruct.isMatched) { continue; + } DisambiguatorEntry destDisEntry = destDisambig.get(srcDisEntry.hash); - if (destDisEntry == null) + if (destDisEntry == null) { continue; - if (destDisEntry.count != 1) + } + if (destDisEntry.count != 1) { continue; - if (destDisEntry.instruct.isMatched) + } + if (destDisEntry.instruct.isMatched) { continue; + } // If both sides have exactly one matching InstructHash, call it a match declareMatch(srcEntry, srcDisEntry.instruct, destEntry, destDisEntry.instruct); count += 1; @@ -281,25 +285,32 @@ public class HashedFunctionAddressCorrelation implements ListingAddressCorrelati */ private boolean disambiguateMatchingNgrams(HashEntry srcEntry, HashEntry destEntry) throws CancelledException, MemoryAccessException { - if (srcEntry.hasDuplicateBlocks()) + if (srcEntry.hasDuplicateBlocks()) { return false; - if (destEntry.hasDuplicateBlocks()) + } + if (destEntry.hasDuplicateBlocks()) { return false; - if (srcEntry.hash.size != destEntry.hash.size) + } + if (srcEntry.hash.size != destEntry.hash.size) { return false; // This likely never happens, because we know the hash values are equal + } int count = disambiguateNgramsWithStrategy(new DisambiguateByParent(), srcEntry, destEntry); - if (count != 0) + if (count != 0) { return true; + } count = disambiguateNgramsWithStrategy(new DisambiguateByChild(), srcEntry, destEntry); - if (count != 0) + if (count != 0) { return true; + } count = disambiguateNgramsWithStrategy(new DisambiguateByBytes(), srcEntry, destEntry); - if (count != 0) + if (count != 0) { return true; + } count = disambiguateNgramsWithStrategy(new DisambiguateByParentWithOrder(), srcEntry, destEntry); - if (count != 0) + if (count != 0) { return true; + } return false; } @@ -333,8 +344,9 @@ public class HashedFunctionAddressCorrelation implements ListingAddressCorrelati destEntry2.instList.getFirst()); } else { - if (!disambiguateMatchingNgrams(srcEntry, destEntry)) + if (!disambiguateMatchingNgrams(srcEntry, destEntry)) { srcStore.removeHash(srcEntry); + } } } } @@ -359,8 +371,9 @@ public class HashedFunctionAddressCorrelation implements ListingAddressCorrelati destStore.calcHashes(minLength, maxLength, wholeBlock, matchBlock, hashCalc); for (int pass = 0; pass < maxPasses; ++pass) { int curMatch = srcStore.numMatchedInstructions(); - if (curMatch == srcStore.getTotalInstructions()) + if (curMatch == srcStore.getTotalInstructions()) { break; // quit if there are no unmatched instructions + } srcStore.clearSort(); destStore.clearSort(); @@ -368,8 +381,9 @@ public class HashedFunctionAddressCorrelation implements ListingAddressCorrelati destStore.insertHashes(); findMatches(); - if (curMatch == srcStore.numMatchedInstructions()) + if (curMatch == srcStore.numMatchedInstructions()) { break; // quit if no new matched instructions + } } } @@ -389,30 +403,37 @@ public class HashedFunctionAddressCorrelation implements ListingAddressCorrelati findMatches(); - if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions()) + if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions()) { return; - if (destStore.numMatchedInstructions() == destStore.getTotalInstructions()) + } + if (destStore.numMatchedInstructions() == destStore.getTotalInstructions()) { return; + } // Now try multiple passes of 3 and 4 long n-grams hopefully filling in a lot of small holes in our match // given a scaffolding of previously matched basic blocks runPasses(3, 4, true, true, 10); - if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions()) + if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions()) { return; - if (destStore.numMatchedInstructions() == destStore.getTotalInstructions()) + } + if (destStore.numMatchedInstructions() == destStore.getTotalInstructions()) { return; + } // Repeat with big n-grams int curMatch = srcStore.numMatchedInstructions(); runPasses(5, 10, false, false, 3); - if (srcStore.numMatchedInstructions() == curMatch) + if (srcStore.numMatchedInstructions() == curMatch) { return; // No progress - if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions()) + } + if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions()) { return; - if (destStore.numMatchedInstructions() == destStore.getTotalInstructions()) + } + if (destStore.numMatchedInstructions() == destStore.getTotalInstructions()) { return; + } // Repeat with small n-grams runPasses(3, 4, true, true, 10); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/LabelFieldLocation.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/LabelFieldLocation.java index 973515ccfd..dd648b93d3 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/LabelFieldLocation.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/LabelFieldLocation.java @@ -168,15 +168,19 @@ public class LabelFieldLocation extends CodeUnitLocation { } @Override - public void saveState(SaveState obj) { - super.saveState(obj); - obj.putStrings("_SYMBOL_PATH", symbolPath.asArray()); + public void saveState(SaveState ss) { + super.saveState(ss); + ss.putStrings("_SYMBOL_PATH", symbolPath.asArray()); } @Override - public void restoreState(Program p, SaveState obj) { - super.restoreState(p, obj); - String[] symbolPathArray = obj.getStrings("_SYMBOL_PATH", null); - symbolPath = symbolPathArray == null ? new SymbolPath("") : new SymbolPath(symbolPathArray); + public void restoreState(Program p, SaveState ss) { + super.restoreState(p, ss); + String[] symbolPathArray = ss.getStrings("_SYMBOL_PATH", null); + if (symbolPathArray == null) { + throw new IllegalArgumentException("SaveState does not contain a SymbolPath"); + } + + symbolPath = new SymbolPath(symbolPathArray); } } diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionComparisonScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionComparisonScreenShots.java index aa84613578..87fcc6e420 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionComparisonScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionComparisonScreenShots.java @@ -30,7 +30,7 @@ import docking.widgets.table.GFilterTable; import docking.widgets.table.GTable; import ghidra.app.cmd.disassemble.DisassembleCommand; import ghidra.app.util.viewer.listingpanel.ListingPanel; -import ghidra.features.base.codecompare.listing.ListingCodeComparisonPanel; +import ghidra.features.base.codecompare.listing.ListingCodeComparisonView; import ghidra.features.base.codecompare.panel.FunctionComparisonPanel; import ghidra.features.codecompare.plugin.FunctionComparisonPlugin; import ghidra.features.codecompare.plugin.FunctionComparisonProvider; @@ -102,8 +102,8 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator { FunctionComparisonPanel functionComparisonPanel = provider.getComponent(); runSwing(() -> { functionComparisonPanel.setCurrentTabbedComponent("Listing View"); - ListingCodeComparisonPanel dualListing = - (ListingCodeComparisonPanel) functionComparisonPanel.getDisplayedPanel(); + ListingCodeComparisonView dualListing = + (ListingCodeComparisonView) functionComparisonPanel.getDisplayedView(); ListingPanel leftPanel = dualListing.getListingPanel(LEFT); leftPanel.goTo(addr(0x004119aa)); }); diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionGraphPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionGraphPluginScreenShots.java index 671de541fc..2d0998d197 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionGraphPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionGraphPluginScreenShots.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. @@ -441,8 +441,7 @@ public class FunctionGraphPluginScreenShots extends AbstractFunctionGraphTest { } private GenericHeader getHeader() { - FGController controller = getFunctionGraphController(); - ComponentProvider provider = controller.getProvider(); + ComponentProvider provider = getProvider(); DockableComponent dc = getDockableComponent(provider.getComponent()); return dc.getHeader(); } diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/compare/CompareFunctionsDecompilerViewTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/compare/CompareFunctionsDecompilerViewTest.java index a921a17038..da63e5e774 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/compare/CompareFunctionsDecompilerViewTest.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/compare/CompareFunctionsDecompilerViewTest.java @@ -25,7 +25,7 @@ import org.junit.*; import ghidra.features.base.codecompare.model.FunctionComparisonModel; import ghidra.features.codecompare.decompile.CDisplay; -import ghidra.features.codecompare.decompile.DecompilerCodeComparisonPanel; +import ghidra.features.codecompare.decompile.DecompilerCodeComparisonView; import ghidra.features.codecompare.plugin.FunctionComparisonPlugin; import ghidra.features.codecompare.plugin.FunctionComparisonProvider; import ghidra.program.model.address.Address; @@ -48,10 +48,10 @@ public class CompareFunctionsDecompilerViewTest extends AbstractGhidraHeadedInte @Before public void setUp() throws Exception { env = new TestEnv(); - plugin = env.addPlugin(FunctionComparisonPlugin.class); program1 = buildTestProgram(); - showTool(plugin.getTool()); - env.open(program1); + env.showTool(program1); + plugin = env.addPlugin(FunctionComparisonPlugin.class); + FunctionManager functionManager = program1.getFunctionManager(); fun1 = functionManager.getFunctionAt(addr(0x01002cf5)); fun2 = functionManager.getFunctionAt(addr(0x0100415a)); @@ -67,7 +67,7 @@ public class CompareFunctionsDecompilerViewTest extends AbstractGhidraHeadedInte } @Test - public void testDecompDifView() throws Exception { + public void testDecompilerDiffView() throws Exception { assertFalse(program1.isClosed()); Set functions = Set.of(fun1, fun2); @@ -77,12 +77,13 @@ public class CompareFunctionsDecompilerViewTest extends AbstractGhidraHeadedInte waitForComponentProvider(FunctionComparisonProvider.class); checkFunctions(provider, LEFT, fun1, fun1, fun2); - DecompilerCodeComparisonPanel panel = (DecompilerCodeComparisonPanel) provider - .getCodeComparisonPanelByName(DecompilerCodeComparisonPanel.NAME); + DecompilerCodeComparisonView comparisonProvider = + (DecompilerCodeComparisonView) provider + .getCodeComparisonView(DecompilerCodeComparisonView.NAME); - waitForDecompiler(panel); - assertHasLines(panel.getLeftPanel(), 28); - assertHasLines(panel.getRightPanel(), 22); + waitForDecompiler(comparisonProvider); + assertHasLines(comparisonProvider.getLeftPanel(), 28); + assertHasLines(comparisonProvider.getRightPanel(), 22); } private void checkFunctions(FunctionComparisonProvider provider, Side side, @@ -101,7 +102,7 @@ public class CompareFunctionsDecompilerViewTest extends AbstractGhidraHeadedInte assertEquals(lineCount, panel.getDecompilerPanel().getLines().size()); } - private void waitForDecompiler(DecompilerCodeComparisonPanel panel) { + private void waitForDecompiler(DecompilerCodeComparisonView panel) { waitForSwing(); waitForCondition(() -> !panel.isBusy()); waitForSwing(); diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/compare/CompareFunctionsFunctionGraphViewTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/compare/CompareFunctionsFunctionGraphViewTest.java new file mode 100644 index 0000000000..de550e05e2 --- /dev/null +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/compare/CompareFunctionsFunctionGraphViewTest.java @@ -0,0 +1,585 @@ +/* ### + * 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.compare; + +import static ghidra.util.datastruct.Duo.Side.*; +import static org.junit.Assert.*; + +import java.util.*; + +import javax.swing.JButton; + +import org.junit.*; + +import docking.ActionContext; +import docking.action.*; +import docking.widgets.dialogs.ObjectChooserDialog; +import ghidra.app.plugin.core.functiongraph.SetFormatDialogComponentProvider; +import ghidra.app.plugin.core.functiongraph.graph.layout.FGLayoutProvider; +import ghidra.app.plugin.core.functiongraph.mvc.FGController; +import ghidra.app.plugin.core.functiongraph.mvc.FgEnv; +import ghidra.app.util.viewer.field.FieldFactory; +import ghidra.app.util.viewer.format.*; +import ghidra.app.util.viewer.format.actions.AddFieldAction; +import ghidra.app.util.viewer.listingpanel.ListingPanel; +import ghidra.features.base.codecompare.model.FunctionComparisonModel; +import ghidra.features.codecompare.functiongraph.FgDisplay; +import ghidra.features.codecompare.functiongraph.FunctionGraphCodeComparisonView; +import ghidra.features.codecompare.plugin.FunctionComparisonPlugin; +import ghidra.features.codecompare.plugin.FunctionComparisonProvider; +import ghidra.framework.options.SaveState; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.*; +import ghidra.test.*; +import ghidra.util.datastruct.Duo; +import ghidra.util.datastruct.Duo.Side; + +public class CompareFunctionsFunctionGraphViewTest extends AbstractGhidraHeadedIntegrationTest { + + private TestEnv env; + private Program program; + private Function fun1; + private Function fun2; + private FunctionComparisonPlugin plugin; + + @Before + public void setUp() throws Exception { + + env = new TestEnv(); + showTool(); + } + + private void showTool() throws Exception { + program = buildTestProgram(); + env.launchDefaultTool(program); + plugin = env.addPlugin(FunctionComparisonPlugin.class); + + FunctionManager functionManager = program.getFunctionManager(); + fun1 = functionManager.getFunctionAt(addr(0x01002cf5)); + fun2 = functionManager.getFunctionAt(addr(0x0100415a)); + } + + private Program buildTestProgram() throws Exception { + ClassicSampleX86ProgramBuilder builder = new ClassicSampleX86ProgramBuilder("Test", false); + return builder.getProgram(); + } + + private Address addr(long offset) { + return program.getAddressFactory().getDefaultAddressSpace().getAddress(offset); + } + + @After + public void tearDown() throws Exception { + env.dispose(); + } + + @Test + public void testFunctionGraphDiffView() throws Exception { + + Set functions = Set.of(fun1, fun2); + compareFunctions(functions); + + FunctionComparisonProvider provider = + waitForComponentProvider(FunctionComparisonProvider.class); + + selectFgPanel(provider); + + checkFunctions(provider, LEFT, fun1, fun1, fun2); + FunctionGraphCodeComparisonView fgProvider = getFgComparisonProvider(provider); + + waitForFunctionGraph(fgProvider); + + assertEachFunctionIsGraphed(fgProvider, fun1, fun2); + } + + @Test + public void testChangeFormat() throws Exception { + + Set functions = Set.of(fun1, fun2); + compareFunctions(functions); + + FunctionComparisonProvider provider = + waitForComponentProvider(FunctionComparisonProvider.class); + selectFgPanel(provider); + FunctionGraphCodeComparisonView fgProvider = getFgComparisonProvider(provider); + waitForFunctionGraph(fgProvider); + + FormatDescription currentFormat = getFormat(fgProvider); + + FormatDescription newFormat = changeFormat(provider); + assertNotEquals(currentFormat, newFormat); + + close(provider); + env.saveRestoreToolState(); + + compareFunctions(functions); + provider = waitForComponentProvider(FunctionComparisonProvider.class); + selectFgPanel(provider); + fgProvider = getFgComparisonProvider(provider); + FormatDescription restoredFormat = getFormat(fgProvider); + assertEquals(newFormat, restoredFormat); + } + + @Test + public void testChangeShowPopups() { + + Set functions = Set.of(fun1, fun2); + compareFunctions(functions); + + FunctionComparisonProvider provider = + waitForComponentProvider(FunctionComparisonProvider.class); + selectFgPanel(provider); + FunctionGraphCodeComparisonView fgProvider = getFgComparisonProvider(provider); + waitForFunctionGraph(fgProvider); + + boolean isShowingPopups = isShowingPopups(fgProvider); + setShowingPopups(provider, !isShowingPopups); + + close(provider); + env.saveRestoreToolState(); + + compareFunctions(functions); + provider = waitForComponentProvider(FunctionComparisonProvider.class); + selectFgPanel(provider); + fgProvider = getFgComparisonProvider(provider); + boolean restoredIsShowingPopups = isShowingPopups(fgProvider); + assertEquals(!isShowingPopups, restoredIsShowingPopups); + } + + @Test + public void testChangeLayout() { + + Set functions = Set.of(fun1, fun2); + compareFunctions(functions); + + FunctionComparisonProvider provider = + waitForComponentProvider(FunctionComparisonProvider.class); + selectFgPanel(provider); + FunctionGraphCodeComparisonView fgProvider = getFgComparisonProvider(provider); + waitForFunctionGraph(fgProvider); + + FGLayoutProvider currentLayout = getLayout(fgProvider); + FGLayoutProvider newLayout = changeLayout(provider); + assertNotEquals(currentLayout, newLayout); + + close(provider); + env.saveRestoreToolState(); + + compareFunctions(functions); + provider = waitForComponentProvider(FunctionComparisonProvider.class); + selectFgPanel(provider); + fgProvider = getFgComparisonProvider(provider); + FGLayoutProvider updatedLayout = getLayout(fgProvider); + assertEquals(newLayout.getLayoutName(), updatedLayout.getLayoutName()); + } + + @Test + public void testHideSatellite() { + + Set functions = Set.of(fun1, fun2); + compareFunctions(functions); + + FunctionComparisonProvider provider = + waitForComponentProvider(FunctionComparisonProvider.class); + selectFgPanel(provider); + FunctionGraphCodeComparisonView fgProvider = getFgComparisonProvider(provider); + waitForFunctionGraph(fgProvider); + + boolean isShowingSatellite = isShowingSatellite(fgProvider); + setShowingSatellite(provider, !isShowingSatellite); + + close(provider); + env.saveRestoreToolState(); + + compareFunctions(functions); + provider = waitForComponentProvider(FunctionComparisonProvider.class); + selectFgPanel(provider); + fgProvider = getFgComparisonProvider(provider); + boolean restoredIsShowingSatellite = isShowingSatellite(fgProvider); + assertEquals(!isShowingSatellite, restoredIsShowingSatellite); + } + +//================================================================================================= +// Private Methods +//================================================================================================= + + private void assertEachFunctionIsGraphed(FunctionGraphCodeComparisonView panel, + Function leftFunction, Function rightFunction) { + + Duo displays = panel.getDisplays(); + FgDisplay leftFgDisplay = displays.get(LEFT); + FgDisplay rightFgDisplay = displays.get(RIGHT); + FGController leftController = leftFgDisplay.getController(); + FGController rightController = rightFgDisplay.getController(); + assertTrue(leftController.hasResults()); + assertTrue(rightController.hasResults()); + + Function leftControllerFunction = leftController.getGraphedFunction(); + assertEquals(leftFunction, leftControllerFunction); + + Function rightControllerFunction = rightController.getGraphedFunction(); + assertEquals(rightFunction, rightControllerFunction); + } + + private FGLayoutProvider getLayout(FunctionGraphCodeComparisonView panel) { + + Duo displays = panel.getDisplays(); + FgDisplay leftDisplay = displays.get(LEFT); + FGController leftController = leftDisplay.getController(); + FGLayoutProvider leftLayout = leftController.getLayoutProvider(); + + FgDisplay rightDisplay = displays.get(RIGHT); + FGController rightController = rightDisplay.getController(); + FGLayoutProvider rightLayout = rightController.getLayoutProvider(); + assertEquals(leftLayout.getLayoutName(), rightLayout.getLayoutName()); + + return leftLayout; + } + + private FGLayoutProvider changeLayout(FunctionComparisonProvider provider) { + + FunctionGraphCodeComparisonView panel = getFgComparisonProvider(provider); + DockingAction action = getAction(panel, "Relayout Graph"); + performAction(action, provider, false); + + @SuppressWarnings("unchecked") + ObjectChooserDialog dialog = + waitForDialogComponent(ObjectChooserDialog.class); + + FGLayoutProvider newProvider = getLayout(panel, "Nested Code Layout"); + runSwing(() -> dialog.setSelectedObject(newProvider)); + + pressButtonByText(dialog, "OK", true); + + FGLayoutProvider dialogProvider = dialog.getSelectedObject(); + assertEquals(newProvider, dialogProvider); + return newProvider; + } + + private FGLayoutProvider getLayout(FunctionGraphCodeComparisonView panel, String name) { + Duo displays = panel.getDisplays(); + FgDisplay leftDisplay = displays.get(LEFT); + FGController leftController = leftDisplay.getController(); + FgEnv leftEnv = leftController.getEnv(); + List providers = leftEnv.getLayoutProviders(); + for (FGLayoutProvider provider : providers) { + String layoutName = provider.getLayoutName(); + if (layoutName.equals(name)) { + return provider; + } + } + + fail("Unable to find graph layout by name: " + name); + return null; + } + + private boolean isShowingPopups(FunctionGraphCodeComparisonView fgProvider) { + + Duo displays = fgProvider.getDisplays(); + FgDisplay leftDisplay = displays.get(LEFT); + FGController leftController = leftDisplay.getController(); + boolean leftVisible = leftController.arePopupsVisible(); + + FgDisplay rightDisplay = displays.get(RIGHT); + FGController rightController = rightDisplay.getController(); + boolean rightVisible = rightController.arePopupsVisible(); + + assertEquals(leftVisible, rightVisible); + return leftVisible; + } + + private void setShowingPopups(FunctionComparisonProvider provider, boolean shouldBeVisible) { + + FunctionGraphCodeComparisonView fgProvider = getFgComparisonProvider(provider); + ToggleDockingAction action = + (ToggleDockingAction) getAction(fgProvider, "Display Popup Windows"); + boolean currentlyVisible = runSwing(() -> action.isSelected()); + if (currentlyVisible != shouldBeVisible) { + performAction(action, provider, true); + } + } + + private boolean isShowingSatellite(FunctionGraphCodeComparisonView fgProvider) { + + Duo displays = fgProvider.getDisplays(); + FgDisplay leftDisplay = displays.get(LEFT); + FGController leftController = leftDisplay.getController(); + boolean leftShowing = leftController.isSatelliteVisible(); + + FgDisplay rightDisplay = displays.get(RIGHT); + FGController rightController = rightDisplay.getController(); + boolean rightShowing = rightController.isSatelliteVisible(); + + assertEquals(leftShowing, rightShowing); + return leftShowing; + } + + private void setShowingSatellite(FunctionComparisonProvider provider, boolean shouldBeVisible) { + + FunctionGraphCodeComparisonView panel = getFgComparisonProvider(provider); + ToggleDockingAction action = + (ToggleDockingAction) getAction(panel, "Display Satellite View"); + + boolean currentlyVisible = runSwing(() -> action.isSelected()); + if (currentlyVisible != shouldBeVisible) { + performAction(action, provider, true); + } + } + + private FormatDescription getFormat(FunctionGraphCodeComparisonView fgProvider) { + + Duo displays = fgProvider.getDisplays(); + FgDisplay leftDisplay = displays.get(LEFT); + FGController leftController = leftDisplay.getController(); + FormatManager leftFormat = leftController.getMinimalFormatManager(); + FormatDescription leftFd = new FormatDescription(leftFormat); + + FgDisplay rightDisplay = displays.get(RIGHT); + FGController rightController = rightDisplay.getController(); + FormatManager rightFormat = rightController.getMinimalFormatManager(); + FormatDescription rightFd = new FormatDescription(rightFormat); + + assertEquals(leftFd, rightFd); + + return leftFd; + } + + private FormatDescription changeFormat(FunctionComparisonProvider provider) { + + FunctionGraphCodeComparisonView fgProvider = getFgComparisonProvider(provider); + DockingAction action = getAction(fgProvider, "Edit Code Block Fields"); + performAction(action, provider, false); + + SetFormatDialogComponentProvider dialog = + waitForDialogComponent(SetFormatDialogComponentProvider.class); + + String tabName = "Instruction/Data"; + String actionName = "Bytes"; + selectFormatTab(dialog, tabName); + + assertField(dialog, actionName, false); + + FieldHeaderLocation fhLoc = createFieldHeaderLocation(dialog); + ActionContext context = createContext(fhLoc); + DockingActionIf addFieldAction = getFormatAction(dialog, tabName, actionName); + performAction(addFieldAction, context, true); + + waitForConditionWithoutFailing(() -> fieldIsVisible(dialog, actionName)); + + assertTrue(actionName + " field was not added to the model", + fieldIsVisible(dialog, actionName)); + + JButton OKButton = findButtonByText(dialog, "OK"); + pressButton(OKButton); + waitForSwing(); + + return getFormat(fgProvider); + } + + private DockingActionIf getFormatAction(SetFormatDialogComponentProvider provider, + String formatName, String actionName) { + + Set actions = provider.getActions(); + for (DockingActionIf action : actions) { + if (!action.getName().equals(actionName)) { + continue; + } + + if (!(action instanceof AddFieldAction)) { + continue; + } + + FieldFormatModel formatModel = + (FieldFormatModel) getInstanceField("formatModel", action); + String name = formatModel.getName(); + if (name.equals(formatName)) { + return action; + } + } + + fail("Unable to find action '" + actionName + "' in format model '" + formatName + "'"); + return null; + } + + private void selectFormatTab(SetFormatDialogComponentProvider provider, String tabName) { + + ListingPanel listingPanel = (ListingPanel) getInstanceField("listingPanel", provider); + FieldHeader header = (FieldHeader) getInstanceField("headerPanel", listingPanel); + + FieldFormatModel model = null; + FormatManager manager = (FormatManager) getInstanceField("formatManager", listingPanel); + for (int i = 0; i < manager.getNumModels(); i++) { + FieldFormatModel formatModel = manager.getModel(i); + String name = formatModel.getName(); + if (name.equals(tabName)) { + model = formatModel; + break; + } + } + assertNotNull("Could not find format '" + tabName + "'", model); + + int index = header.indexOfTab(tabName); + runSwing(() -> header.setSelectedIndex(index)); + + waitForCondition(() -> header.getSelectedIndex() == index); + } + + private FieldHeaderLocation createFieldHeaderLocation( + SetFormatDialogComponentProvider provider) { + FieldHeader headerPanel = provider.getFieldHeader(); + FieldHeaderComp fieldHeaderComp = headerPanel.getHeaderTab(); + FieldFormatModel model = fieldHeaderComp.getModel(); + + int row = 1; + int col = 0; + FieldFactory factory = model.getFactorys(row)[col]; + return new FieldHeaderLocation(model, factory, row, col); + } + + private void assertField(SetFormatDialogComponentProvider provider, String name, + boolean shouldExist) { + + if (shouldExist) { + assertTrue("Field '" + name + "' is not in the model, but it should be", + fieldIsVisible(provider, name)); + } + else { + assertFalse("Field '" + name + "' is in the model, but it should not be", + fieldIsVisible(provider, name)); + } + } + + private boolean fieldIsVisible(SetFormatDialogComponentProvider provider, String name) { + FieldHeader headerPanel = provider.getFieldHeader(); + FieldHeaderComp fieldHeaderComp = headerPanel.getHeaderTab(); + FieldFormatModel model = fieldHeaderComp.getModel(); + + FieldFactory[] unused = model.getUnusedFactories(); + for (FieldFactory ff : unused) { + if (ff.getFieldName().equals(name)) { + return false; // field is hidden/unused + } + } + + // sanity check + FieldFactory[] visible = model.getAllFactories(); + for (FieldFactory ff : visible) { + if (ff.getFieldName().equals(name)) { + return true; // visible + } + } + + fail("Field '" + name + "' + is not in the model at all, hidden or visible!"); + return false; // can't get here + } + + private DockingAction getAction(FunctionGraphCodeComparisonView panel, String actionName) { + List actions = panel.getActions(); + for (DockingAction action : actions) { + String name = action.getName(); + if (name.equals(actionName)) { + return action; + } + } + + fail("Could not find action '%s'".formatted(actionName)); + return null; + } + + private void selectFgPanel(FunctionComparisonProvider provider) { + runSwing(() -> provider.selectComparisonView(FunctionGraphCodeComparisonView.NAME)); + } + + private FunctionGraphCodeComparisonView getFgComparisonProvider( + FunctionComparisonProvider provider) { + return runSwing(() -> (FunctionGraphCodeComparisonView) provider + .getCodeComparisonView(FunctionGraphCodeComparisonView.NAME)); + } + + private void waitForFunctionGraph(FunctionGraphCodeComparisonView panel) { + waitForSwing(); + waitForCondition(() -> !panel.isBusy()); + waitForSwing(); + } + + private void compareFunctions(Set functions) { + runSwing(() -> plugin.createComparison(functions)); + waitForSwing(); + } + + private void checkFunctions(FunctionComparisonProvider provider, Side side, + Function activeFunction, Function... functions) { + Set funcs = Set.of(functions); + + FunctionComparisonModel model = provider.getModel(); + assertEquals(activeFunction, model.getActiveFunction(side)); + + List fcs = model.getFunctions(side); + assertEquals(fcs.size(), funcs.size()); + assertTrue(fcs.containsAll(funcs)); + } + + private void close(FunctionComparisonProvider provider) { + runSwing(() -> provider.closeComponent()); + } + + private class FormatDescription { + + private String string; + + FormatDescription(FormatManager fm) { + SaveState ss = new SaveState(); + fm.saveState(ss); + string = ss.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + getEnclosingInstance().hashCode(); + result = prime * result + Objects.hash(string); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + FormatDescription other = (FormatDescription) obj; + if (!getEnclosingInstance().equals(other.getEnclosingInstance())) { + return false; + } + return Objects.equals(string, other.string); + } + + @Override + public String toString() { + return string; + } + + private CompareFunctionsFunctionGraphViewTest getEnclosingInstance() { + return CompareFunctionsFunctionGraphViewTest.this; + } + } +}