diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserPlugin.java index df6f4267f5..17bb7b6d48 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/codebrowser/AbstractCodeBrowserPlugin.java @@ -852,16 +852,6 @@ public abstract class AbstractCodeBrowserPlugin

ex return getAddressIndexMap().getAddress(index); } - @Override - public void formatModelAdded(FieldFormatModel model) { - // uninterested - } - - @Override - public void formatModelRemoved(FieldFormatModel model) { - // uninterested - } - @Override public void formatModelChanged(FieldFormatModel model) { tool.setConfigChanged(true); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonData.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonData.java deleted file mode 100644 index eeb9f4c944..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonData.java +++ /dev/null @@ -1,184 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.app.plugin.core.functioncompare; - -import ghidra.program.model.address.AddressSet; -import ghidra.program.model.address.AddressSetView; -import ghidra.program.model.listing.*; - -/** - * Defines the information being displayed in the left or right panels - * of a {@link FunctionComparisonPanel}, which can display either - * {@link Function functions}, {@link Data data}, or specified - * {@link AddressSet address sets}. At any given time, only one of the - * Function or Data attributes may be set; the other will be - * set to null. - */ -class FunctionComparisonData { - - protected Program program; - protected Function function; - protected Data data; - protected AddressSetView addressSet = new AddressSet(); - - /** - * Returns the program for this model - * - * @return the program, or null if not set - */ - public Program getProgram() { - return program; - } - - /** - * Sets the program for this model - * - * @param program the program to set - */ - public void setProgram(Program program) { - this.program = program; - } - - /** - * Returns the function for this model - * - * @return the function, or null if not set - */ - public Function getFunction() { - return function; - } - - /** - * Sets the function for this model - * - * @param function the function to set - */ - public void setFunction(Function function) { - if (function == null) { - clear(); - return; - } - this.function = function; - this.data = null; - this.program = function.getProgram(); - this.addressSet = function.getBody(); - } - - /** - * Returns the data for this model - * - * @return the data, or null if not set - */ - public Data getData() { - return data; - } - - /** - * Sets the data for this model - * - * @param data the data to set - */ - public void setData(Data data) { - if (data == null) { - clear(); - return; - } - this.data = data; - this.function = null; - this.program = data.getProgram(); - this.addressSet = new AddressSet(data.getMinAddress(), data.getMaxAddress()); - } - - /** - * Returns the address set for this model - * - * @return the address set, or null if not set - */ - public AddressSetView getAddressSet() { - return addressSet; - } - - /** - * Sets the address for this model - * - * @param addressSet the addressSet to set - */ - public void setAddressSet(AddressSetView addressSet) { - this.addressSet = addressSet; - this.data = null; - this.function = null; - } - - /** - * Returns true if the data being managed by this model is of type - * {@link Data} - * - * @return true if this model is set to display {@link Data} - */ - public boolean isData() { - return data != null; - } - - /** - * Returns true if the data being managed by this model is of type - * {@link Function} - * - * @return true if this model is set to display a {@link Function} - */ - public boolean isFunction() { - return function != null; - } - - /** - * Returns true if this class holds no function, data or address set - * information - * - * @return true if this class holds no function, data or address set - * information - */ - public boolean isEmpty() { - return function == null && data == null && addressSet == null; - } - - /** - * Resets all fields in this model to a nominal state - */ - public void clear() { - this.function = null; - this.data = null; - this.addressSet = new AddressSet(); - this.program = null; - } - - public String toString() { - String str = ""; - - if (function != null) { - str = function.getName(); - } - else if (data != null) { - str = data.getAddress().toString(); - } - else if (addressSet != null) { - str = addressSet.toString(); - } - else { - str = "none"; - } - - return str; - } -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPanel.java index 7ddc827b98..f97615bc02 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPanel.java @@ -15,6 +15,9 @@ */ package ghidra.app.plugin.core.functioncompare; +import static ghidra.app.util.viewer.util.ComparisonData.*; +import static ghidra.util.datastruct.Duo.Side.*; + import java.awt.*; import java.awt.event.MouseEvent; import java.lang.reflect.Constructor; @@ -29,11 +32,10 @@ import javax.swing.event.ChangeListener; import docking.ActionContext; import docking.ComponentProvider; import docking.action.*; -import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; import docking.widgets.tabbedpane.DockingTabRenderer; import generic.theme.GIcon; import ghidra.app.util.viewer.listingpanel.ListingCodeComparisonPanel; -import ghidra.app.util.viewer.util.CodeComparisonPanel; +import ghidra.app.util.viewer.util.*; import ghidra.framework.options.SaveState; import ghidra.framework.plugintool.ComponentProviderAdapter; import ghidra.framework.plugintool.PluginTool; @@ -43,6 +45,7 @@ import ghidra.program.model.listing.*; import ghidra.util.HelpLocation; import ghidra.util.Msg; import ghidra.util.classfinder.ClassSearcher; +import ghidra.util.datastruct.Duo; import help.Help; import help.HelpService; @@ -54,11 +57,9 @@ import help.HelpService; * be compared, use a {@link MultiFunctionComparisonPanel} */ public class FunctionComparisonPanel extends JPanel implements ChangeListener { + private static final String ORIENTATION_PROPERTY_NAME = "ORIENTATION"; - private FunctionComparisonData leftComparisonData; - private FunctionComparisonData rightComparisonData; - - private static final String DEFAULT_CODE_COMPARISON_VIEW = ListingCodeComparisonPanel.TITLE; + private static final String DEFAULT_CODE_COMPARISON_VIEW = ListingCodeComparisonPanel.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"; @@ -78,26 +79,22 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { private Map tabNameToComponentMap; protected PluginTool tool; protected ComponentProviderAdapter provider; - private List> codeComparisonPanels; + private List codeComparisonPanels; private ToggleScrollLockAction toggleScrollLockAction; private boolean syncScrolling = false; + private Duo comparisonData = new Duo(); + /** * Constructor * * @param provider the GUI provider that includes this panel * @param tool the tool containing this panel - * @param leftFunction the function displayed in the left side of the panel - * @param rightFunction the function displayed in the right side of the panel */ - public FunctionComparisonPanel(ComponentProviderAdapter provider, PluginTool tool, - Function leftFunction, Function rightFunction) { + public FunctionComparisonPanel(ComponentProviderAdapter provider, PluginTool tool) { this.provider = provider; this.tool = tool; - this.leftComparisonData = new FunctionComparisonData(); - this.rightComparisonData = new FunctionComparisonData(); - this.leftComparisonData.setFunction(leftFunction); - this.rightComparisonData.setFunction(rightFunction); + this.comparisonData = new Duo<>(EMPTY, EMPTY); this.codeComparisonPanels = getCodeComparisonPanels(); tabNameToComponentMap = new HashMap<>(); createMainPanel(); @@ -113,15 +110,11 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { * @param rightFunction The function for the right side of the panel */ public void loadFunctions(Function leftFunction, Function rightFunction) { - leftComparisonData.setFunction(leftFunction); - rightComparisonData.setFunction(rightFunction); - - CodeComparisonPanel activePanel = - getActiveComparisonPanel(); - if (activePanel != null) { - activePanel.loadFunctions(leftComparisonData.getFunction(), - rightComparisonData.getFunction()); - } + ComparisonData left = + leftFunction == null ? EMPTY : new FunctionComparisonData(leftFunction); + ComparisonData right = + rightFunction == null ? EMPTY : new FunctionComparisonData(rightFunction); + loadComparisons(left, right); } /** @@ -131,14 +124,19 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { * @param rightData The data for the right side of the panel */ public void loadData(Data leftData, Data rightData) { - leftComparisonData.setData(leftData); - rightComparisonData.setData(rightData); + ComparisonData left = new DataComparisonData(leftData, rightData.getLength()); + ComparisonData right = new DataComparisonData(rightData, leftData.getLength()); + loadComparisons(left, right); + } - CodeComparisonPanel activePanel = - getActiveComparisonPanel(); + public void loadComparisons(ComparisonData left, ComparisonData right) { + comparisonData = new Duo<>(left, right); + + CodeComparisonPanel activePanel = getActiveComparisonPanel(); if (activePanel != null) { - activePanel.loadData(leftComparisonData.getData(), rightComparisonData.getData()); + activePanel.loadComparisons(left, right); } + } /** @@ -154,17 +152,9 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { */ public void loadAddresses(Program leftProgram, Program rightProgram, AddressSetView leftAddresses, AddressSetView rightAddresses) { - leftComparisonData.setAddressSet(leftAddresses); - rightComparisonData.setAddressSet(rightAddresses); - leftComparisonData.setProgram(leftProgram); - rightComparisonData.setProgram(rightProgram); - CodeComparisonPanel activePanel = - getActiveComparisonPanel(); - if (activePanel != null) { - activePanel.loadAddresses(leftComparisonData.getProgram(), - rightComparisonData.getProgram(), leftComparisonData.getAddressSet(), - rightComparisonData.getAddressSet()); - } + ComparisonData left = new AddressSetComparisonData(leftProgram, leftAddresses); + ComparisonData right = new AddressSetComparisonData(rightProgram, rightAddresses); + loadComparisons(left, right); } /** @@ -183,35 +173,23 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { * @return the description */ public String getDescription() { - Function leftFunc = leftComparisonData.getFunction(); - Function rightFunc = rightComparisonData.getFunction(); - Data leftData = leftComparisonData.getData(); - Data rightData = rightComparisonData.getData(); + String leftShort = comparisonData.get(LEFT).getShortDescription(); + String rightShort = comparisonData.get(LEFT).getShortDescription(); - if (leftFunc != null && rightFunc != null) { - return leftFunc.getName(true) + " & " + rightFunc.getName(true); - } - if (leftData != null && rightData != null) { - return leftData.getDataType().getName() + " & " + rightData.getDataType().getName(); - } - - // Otherwise give a simple description for address sets - return "Nothing selected"; + return leftShort + " & " + rightShort; } /** * Clear both sides of this panel */ public void clear() { - leftComparisonData.clear(); - rightComparisonData.clear(); + comparisonData = new Duo<>(EMPTY, EMPTY); // Setting the addresses to be displayed to null effectively clears // the display - CodeComparisonPanel activePanel = - getActiveComparisonPanel(); + CodeComparisonPanel activePanel = getActiveComparisonPanel(); if (activePanel != null) { - activePanel.loadAddresses(null, null, null, null); + activePanel.clearComparisons(); } } @@ -222,7 +200,7 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { * @return true if the comparison window has no information to display */ public boolean isEmpty() { - return leftComparisonData.isEmpty() || rightComparisonData.isEmpty(); + return comparisonData.get(LEFT).isEmpty() || comparisonData.get(RIGHT).isEmpty(); } /** @@ -232,10 +210,9 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { * @return the comparison panel or null */ public ListingCodeComparisonPanel getDualListingPanel() { - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - JComponent component = codeComparisonPanel.getComponent(); - if (component instanceof ListingCodeComparisonPanel) { - return (ListingCodeComparisonPanel) component; + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + if (codeComparisonPanel instanceof ListingCodeComparisonPanel listingPanel) { + return listingPanel; } } return null; @@ -300,11 +277,26 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { tabbedPane.removeAll(); setVisible(false); - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { codeComparisonPanel.dispose(); } } + void programClosed(Program program) { + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + codeComparisonPanel.programClosed(program); + } + } + + CodeComparisonPanel getCodeComparisonPanelByName(String name) { + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + if (name.equals(codeComparisonPanel.getName())) { + return codeComparisonPanel; + } + } + return null; + } + /** * Create the main tabbed panel */ @@ -317,12 +309,9 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { add(tabbedPane, BorderLayout.CENTER); setPreferredSize(new Dimension(200, 300)); - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - codeComparisonPanel.loadFunctions(leftComparisonData.getFunction(), - rightComparisonData.getFunction()); - JComponent component = codeComparisonPanel.getComponent(); - tabbedPane.add(codeComparisonPanel.getTitle(), component); - tabNameToComponentMap.put(codeComparisonPanel.getTitle(), component); + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + tabbedPane.add(codeComparisonPanel.getName(), codeComparisonPanel); + tabNameToComponentMap.put(codeComparisonPanel.getName(), codeComparisonPanel); } } @@ -331,24 +320,11 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { * the appropriate data to be compared. */ private void tabChanged() { - CodeComparisonPanel activePanel = - getActiveComparisonPanel(); + CodeComparisonPanel activePanel = getActiveComparisonPanel(); if (activePanel == null) { return; // initializing } - - if (leftComparisonData.isFunction() || rightComparisonData.isFunction()) { - activePanel.loadFunctions(leftComparisonData.getFunction(), - rightComparisonData.getFunction()); - } - else if (leftComparisonData.isData() || rightComparisonData.isData()) { - activePanel.loadData(leftComparisonData.getData(), rightComparisonData.getData()); - } - else { - activePanel.loadAddresses(leftComparisonData.getProgram(), - rightComparisonData.getProgram(), leftComparisonData.getAddressSet(), - rightComparisonData.getAddressSet()); - } + activePanel.loadComparisons(comparisonData.get(LEFT), comparisonData.get(RIGHT)); } /** @@ -357,100 +333,8 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { * @return the currently selected comparison panel, or null if nothing * selected */ - private CodeComparisonPanel getActiveComparisonPanel() { - JComponent c = (JComponent) tabbedPane.getSelectedComponent(); - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - JComponent component = codeComparisonPanel.getComponent(); - if (c == component) { - return codeComparisonPanel; - } - } - - return null; - } - - /** - * Returns the comparison data object for the left panel - * - * @return the comparison data object for the left panel - */ - public FunctionComparisonData getLeftComparisonData() { - return leftComparisonData; - } - - /** - * Returns the comparison data object for the right panel - * - * @return the comparison data object for the right panel - */ - public FunctionComparisonData getRightComparisonData() { - return rightComparisonData; - } - - /** - * Gets the function currently displayed in the left side of this panel - * - * @return the left function or null - */ - public Function getLeftFunction() { - return leftComparisonData.getFunction(); - } - - /** - * Sets the function to display in the left side of this panel - * - * @param function the function to display - */ - protected void setLeftFunction(Function function) { - loadFunctions(function, rightComparisonData.getFunction()); - } - - /** - * Gets the function currently displayed in the right side of this panel - * - * @return the right function or null - */ - public Function getRightFunction() { - return rightComparisonData.getFunction(); - } - - /** - * Sets the function to display in the right side of this panel - * - * @param function the function to display - */ - protected void setRightFunction(Function function) { - loadFunctions(leftComparisonData.getFunction(), function); - } - - /** - * Gets the data displayed in the left side of this panel - * - * @return the left data or null - */ - public Data getLeftData() { - return leftComparisonData.getData(); - } - - /** - * Gets the data displayed in the right side of this panel - * - * @return the right data - */ - public Data getRightData() { - return rightComparisonData.getData(); - } - - /** - * Enables/disables mouse navigation for all the CodeComparisonPanels - * displayed by this panel - * - * @param enabled true to enable mouse navigation in the panels - */ - public void setMouseNavigationEnabled(boolean enabled) { - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - codeComparisonPanel.setMouseNavigationEnabled(enabled); - } + private CodeComparisonPanel getActiveComparisonPanel() { + return (CodeComparisonPanel) tabbedPane.getSelectedComponent(); } /** @@ -466,9 +350,10 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { setCurrentTabbedComponent(currentTabView); setScrollingSyncState( saveState.getBoolean(prefix + CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, true)); - ListingCodeComparisonPanel dualListingPanel = getDualListingPanel(); - if (dualListingPanel != null) { - dualListingPanel.readConfigState(prefix, saveState); + + for (CodeComparisonPanel panel : codeComparisonPanels) { + String key = prefix + panel.getName() + ORIENTATION_PROPERTY_NAME; + panel.setSideBySide(saveState.getBoolean(key, true)); } } @@ -485,10 +370,13 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { saveState.putString(prefix + COMPARISON_VIEW_DISPLAYED, getCurrentComponentName()); } saveState.putBoolean(prefix + CODE_COMPARISON_LOCK_SCROLLING_TOGETHER, isScrollingSynced()); - ListingCodeComparisonPanel dualListingPanel = getDualListingPanel(); - if (dualListingPanel != null) { - dualListingPanel.writeConfigState(prefix, saveState); + + for (CodeComparisonPanel panel : codeComparisonPanels) { + String key = prefix + panel.getName() + ORIENTATION_PROPERTY_NAME; + boolean sideBySide = panel.isSideBySide(); + saveState.putBoolean(key, sideBySide); } + } /** @@ -499,18 +387,18 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { */ 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 each CodeComparisonPanel - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - DockingAction[] actions = codeComparisonPanel.getActions(); - for (DockingAction dockingAction : actions) { - dockingActionList.add(dockingAction); - } + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + dockingActionList.addAll(codeComparisonPanel.getActions()); } + return dockingActionList.toArray(new DockingAction[dockingActionList.size()]); } @@ -524,8 +412,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 CodeComparisonPanel) { + ((CodeComparisonPanel) component).setTitlePrefixes(leftTitlePrefix, rightTitlePrefix); } } @@ -539,34 +427,10 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { * @return the action context */ public ActionContext getActionContext(MouseEvent event, ComponentProvider componentProvider) { - Object source = (event != null) ? event.getSource() : null; - Component sourceComponent = (source instanceof Component) ? (Component) source : null; - ListingCodeComparisonPanel dualListingPanel = getDualListingPanel(); - // Is the action being taken on the dual listing. - if (dualListingPanel != null && dualListingPanel.isAncestorOf(sourceComponent)) { - return dualListingPanel.getActionContext(event, componentProvider); - } - - CodeComparisonPanel activePanel = - getFocusedComparisonPanel(); + CodeComparisonPanel activePanel = getDisplayedPanel(); if (activePanel != null) { return activePanel.getActionContext(componentProvider, event); } - - return null; - } - - private CodeComparisonPanel getFocusedComparisonPanel() { - Component focused = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); - if (focused != null) { - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { - JComponent component = codeComparisonPanel.getComponent(); - boolean isParent = SwingUtilities.isDescendingFrom(focused, component); - if (isParent) { - return codeComparisonPanel; - } - } - } return null; } @@ -594,8 +458,8 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { 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.setScrollingSyncState(syncScrolling); + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + codeComparisonPanel.setSynchronizedScrolling(syncScrolling); } this.syncScrolling = syncScrolling; } @@ -605,17 +469,17 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { * * @return the current panel or null. */ - public CodeComparisonPanel getDisplayedPanel() { + public CodeComparisonPanel getDisplayedPanel() { int selectedIndex = tabbedPane.getSelectedIndex(); Component component = tabbedPane.getComponentAt(selectedIndex); - return (CodeComparisonPanel) component; + return (CodeComparisonPanel) component; } /** * Updates the enablement for all actions provided by each panel */ public void updateActionEnablement() { - for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { + for (CodeComparisonPanel codeComparisonPanel : codeComparisonPanels) { codeComparisonPanel.updateActionEnablement(); } } @@ -625,8 +489,8 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { * * @return null if there is no code comparison panel */ - CodeComparisonPanel getCurrentComponent() { - return (CodeComparisonPanel) tabbedPane.getSelectedComponent(); + CodeComparisonPanel getCurrentComponent() { + return (CodeComparisonPanel) tabbedPane.getSelectedComponent(); } /** @@ -677,7 +541,7 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { } } - public List> getComparisonPanels() { + public List getComparisonPanels() { return codeComparisonPanels; } @@ -686,17 +550,17 @@ public class FunctionComparisonPanel extends JPanel implements ChangeListener { * * @return the CodeComparisonPanels which are extension points */ - private List> getCodeComparisonPanels() { + private List getCodeComparisonPanels() { if (codeComparisonPanels == null) { codeComparisonPanels = createAllPossibleCodeComparisonPanels(); - codeComparisonPanels.sort((p1, p2) -> p1.getTitle().compareTo(p2.getTitle())); + codeComparisonPanels.sort((p1, p2) -> p1.getName().compareTo(p2.getName())); } return codeComparisonPanels; } @SuppressWarnings({ "rawtypes", "unchecked" }) - private ArrayList> createAllPossibleCodeComparisonPanels() { - ArrayList> instances = + private ArrayList createAllPossibleCodeComparisonPanels() { + ArrayList instances = new ArrayList<>(); List> classes = ClassSearcher.getClasses(CodeComparisonPanel.class); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPlugin.java index 33abb8bb5d..15df0fc755 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonPlugin.java @@ -19,19 +19,13 @@ import java.util.Set; import java.util.function.Supplier; import ghidra.app.CorePluginPackage; -import ghidra.app.events.ProgramActivatedPluginEvent; -import ghidra.app.events.ProgramClosedPluginEvent; -import ghidra.app.events.ProgramSelectionPluginEvent; +import ghidra.app.events.*; import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.ProgramPlugin; import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsAction; import ghidra.app.plugin.core.functioncompare.actions.CompareFunctionsFromListingAction; import ghidra.app.services.FunctionComparisonService; -import ghidra.framework.model.DomainObjectChangeRecord; -import ghidra.framework.model.DomainObjectChangedEvent; -import ghidra.framework.model.DomainObjectEvent; -import ghidra.framework.model.DomainObjectListener; -import ghidra.framework.model.EventType; +import ghidra.framework.model.*; import ghidra.framework.plugintool.PluginInfo; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.util.PluginStatus; @@ -117,7 +111,7 @@ public class FunctionComparisonPlugin extends ProgramPlugin EventType eventType = doRecord.getEventType(); if (eventType == DomainObjectEvent.RESTORED) { - functionComparisonManager.domainObjectRestored(ev); + functionComparisonManager.domainObjectRestored((Program) ev.getSource()); } else if (eventType == ProgramEvent.FUNCTION_REMOVED) { ProgramChangeRecord rec = (ProgramChangeRecord) ev.getChangeRecord(i); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProvider.java index 98d6b99dc6..11c5087db0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProvider.java @@ -15,6 +15,8 @@ */ package ghidra.app.plugin.core.functioncompare; +import static ghidra.util.datastruct.Duo.Side.*; + import java.awt.event.MouseEvent; import java.util.*; @@ -23,7 +25,6 @@ import docking.Tool; import docking.action.DockingAction; import docking.action.DockingActionIf; import docking.actions.PopupActionProvider; -import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; import ghidra.app.services.FunctionComparisonModel; import ghidra.app.services.FunctionComparisonService; import ghidra.app.util.viewer.listingpanel.ListingCodeComparisonPanel; @@ -87,7 +88,7 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter @Override public FunctionComparisonPanel getComponent() { if (functionComparisonPanel == null) { - functionComparisonPanel = new FunctionComparisonPanel(this, tool, null, null); + functionComparisonPanel = new FunctionComparisonPanel(this, tool); } return functionComparisonPanel; } @@ -107,19 +108,14 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter buff.append(getName() + "\n"); buff.append("Tab Text: "); buff.append(getTabText() + "\n"); - Function leftFunction = functionComparisonPanel.getLeftFunction(); - String leftName = (leftFunction != null) ? leftFunction.getName() : "No Function"; - buff.append("Function 1: " + leftName + "\n"); - Function rightFunction = functionComparisonPanel.getRightFunction(); - String rightName = (rightFunction != null) ? rightFunction.getName() : "No Function"; - buff.append("Function 2: " + rightName + "\n"); + buff.append(functionComparisonPanel.getDescription()); buff.append("tool = " + tool + "\n"); return buff.toString(); } @Override public ActionContext getActionContext(MouseEvent event) { - CodeComparisonPanel currentComponent = + CodeComparisonPanel currentComponent = functionComparisonPanel.getCurrentComponent(); return currentComponent.getActionContext(this, event); } @@ -145,7 +141,7 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel(); if (dualListingPanel != null) { - ListingPanel leftPanel = dualListingPanel.getLeftPanel(); + ListingPanel leftPanel = dualListingPanel.getListingPanel(LEFT); return leftPanel.getHeaderActions(getName()); } } @@ -178,6 +174,7 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter * @param program the program being closed */ public void programClosed(Program program) { + functionComparisonPanel.programClosed(program); model.removeFunctions(program); closeIfEmpty(); } @@ -210,7 +207,7 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter * @param program the program that was restored (undo/redo) */ public void programRestored(Program program) { - CodeComparisonPanel comparePanel = + CodeComparisonPanel comparePanel = functionComparisonPanel.getCurrentComponent(); comparePanel.programRestored(program); } @@ -283,6 +280,10 @@ public class FunctionComparisonProvider extends ComponentProviderAdapter this.closeListener = Callback.dummyIfNull(closeListener); } + public CodeComparisonPanel getCodeComparisonPanelByName(String name) { + return functionComparisonPanel.getCodeComparisonPanelByName(name); + } + public void dispose() { functionComparisonPanel.dispose(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderManager.java index 6bbd6e60ba..8b233fc7f4 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/FunctionComparisonProviderManager.java @@ -20,7 +20,6 @@ import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import docking.ComponentProviderActivationListener; -import ghidra.framework.model.*; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; @@ -225,20 +224,10 @@ public class FunctionComparisonProviderManager implements FunctionComparisonProv * will notify all the function comparison providers. This allows them to * refresh if they are showing a function from the program * - * @param ev the object changed event + * @param program the program that was restored */ - public void domainObjectRestored(DomainObjectChangedEvent ev) { - for (DomainObjectChangeRecord domainObjectChangeRecord : ev) { - EventType eventType = domainObjectChangeRecord.getEventType(); - if (eventType != DomainObjectEvent.RESTORED) { - return; - } - Object source = ev.getSource(); - if (source instanceof Program) { - Program program = (Program) source; - providers.stream().forEach(p -> p.programRestored(program)); - } - } + public void domainObjectRestored(Program program) { + providers.stream().forEach(p -> p.programRestored(program)); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonPanel.java index 939d9dc7f3..22b7f3c0e9 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/MultiFunctionComparisonPanel.java @@ -15,6 +15,8 @@ */ package ghidra.app.plugin.core.functioncompare; +import static ghidra.util.datastruct.Duo.Side.*; + import java.awt.*; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; @@ -23,11 +25,11 @@ import java.util.Set; import javax.swing.*; -import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; import ghidra.app.services.FunctionComparisonModel; import ghidra.app.util.viewer.util.CodeComparisonPanel; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.listing.Function; +import ghidra.util.datastruct.Duo.Side; import help.Help; import help.HelpService; @@ -67,7 +69,7 @@ public class MultiFunctionComparisonPanel extends FunctionComparisonPanel { * @param tool the active plugin tool */ public MultiFunctionComparisonPanel(MultiFunctionComparisonProvider provider, PluginTool tool) { - super(provider, tool, null, null); + super(provider, tool); JPanel choicePanel = new JPanel(new GridLayout(1, 2)); choicePanel.add(createSourcePanel()); @@ -77,7 +79,7 @@ public class MultiFunctionComparisonPanel extends FunctionComparisonPanel { // For the multi-panels we don't need to show the title of each // comparison panel because the name of the function/data being shown // is already visible in the combo box - getComparisonPanels().forEach(p -> p.setShowTitles(false)); + getComparisonPanels().forEach(p -> p.setShowDataTitles(false)); setPreferredSize(new Dimension(1200, 600)); } @@ -99,7 +101,6 @@ public class MultiFunctionComparisonPanel extends FunctionComparisonPanel { // Fire a notification to update the UI state; without this the // actions would not be properly enabled/disabled tool.contextChanged(provider); - tool.setStatusInfo("function comparisons updated"); } /** @@ -108,10 +109,14 @@ public class MultiFunctionComparisonPanel extends FunctionComparisonPanel { * @return the focused component */ public JComboBox getFocusedComponent() { - CodeComparisonPanel currentComponent = - getCurrentComponent(); - boolean sourceHasFocus = currentComponent.leftPanelHasFocus(); - return sourceHasFocus ? sourceFunctionsCB : targetFunctionsCB; + CodeComparisonPanel currentComponent = getCurrentComponent(); + Side side = currentComponent.getActiveSide(); + return side == LEFT ? sourceFunctionsCB : targetFunctionsCB; + } + + public Side getFocusedSide() { + CodeComparisonPanel currentComponent = getCurrentComponent(); + return currentComponent.getActiveSide(); } /** @@ -260,9 +265,6 @@ public class MultiFunctionComparisonPanel extends FunctionComparisonPanel { return; } - Function selected = (Function) sourceFunctionsCBModel.getSelectedItem(); - loadFunctions(selected, null); - // Each time a source function is selected we need // to load the targets associated with it reloadTargetList((Function) sourceFunctionsCBModel.getSelectedItem()); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/NavigateToFunctionAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/NavigateToFunctionAction.java index 6b2b3168d0..0efece36a5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/NavigateToFunctionAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/functioncompare/actions/NavigateToFunctionAction.java @@ -15,21 +15,27 @@ */ package ghidra.app.plugin.core.functioncompare.actions; +import static ghidra.util.datastruct.Duo.Side.*; + import java.awt.event.*; import java.util.List; import javax.swing.Icon; +import javax.swing.JComboBox; +import docking.ActionContext; import docking.action.ToggleDockingAction; import docking.action.ToolBarData; -import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; -import ghidra.app.plugin.core.functioncompare.*; +import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonPanel; +import ghidra.app.plugin.core.functioncompare.MultiFunctionComparisonProvider; import ghidra.app.services.GoToService; import ghidra.app.util.viewer.util.CodeComparisonPanel; -import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSetView; import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; import ghidra.util.HTMLUtilities; import ghidra.util.HelpLocation; +import ghidra.util.datastruct.Duo.Side; import resources.Icons; /** @@ -50,6 +56,8 @@ public class NavigateToFunctionAction extends ToggleDockingAction { private static final Icon NAV_FUNCTION_ICON = Icons.NAVIGATE_ON_INCOMING_EVENT_ICON; + private MultiFunctionComparisonPanel comparisonPanel; + /** * Constructor * @@ -57,6 +65,7 @@ public class NavigateToFunctionAction extends ToggleDockingAction { */ public NavigateToFunctionAction(MultiFunctionComparisonProvider provider) { super("Navigate To Selected Function", provider.getName()); + comparisonPanel = (MultiFunctionComparisonPanel) provider.getComponent(); goToService = provider.getTool().getService(GoToService.class); @@ -70,8 +79,15 @@ public class NavigateToFunctionAction extends ToggleDockingAction { setHelpLocation( new HelpLocation(MultiFunctionComparisonPanel.HELP_TOPIC, "Navigate_To_Function")); - addFocusListeners(provider); - addChangeListeners(provider); + addFocusListeners(); + addChangeListeners(); + } + + @Override + public void actionPerformed(ActionContext context) { + JComboBox combo = comparisonPanel.getFocusedComponent(); + Function f = (Function) combo.getSelectedItem(); + goToService.goTo(f.getEntryPoint(), f.getProgram()); } /** @@ -79,108 +95,75 @@ public class NavigateToFunctionAction extends ToggleDockingAction { * comparison provider. When a new function is selected, a GoTo event * is generated for the entry point of the function. * - * @param provider the function comparison provider */ - private void addChangeListeners(MultiFunctionComparisonProvider provider) { - MultiFunctionComparisonPanel panel = (MultiFunctionComparisonPanel) provider.getComponent(); + private void addChangeListeners() { + JComboBox sourceCombo = comparisonPanel.getSourceComponent(); + JComboBox targetCombo = comparisonPanel.getTargetComponent(); + sourceCombo.addItemListener(new PanelItemListener(LEFT)); + targetCombo.addItemListener(new PanelItemListener(RIGHT)); - panel.getSourceComponent().addItemListener(new ItemListener() { - @Override - public void itemStateChanged(ItemEvent e) { - if (e.getStateChange() != ItemEvent.SELECTED) { - return; - } - - if (panel.getFocusedComponent() != panel.getSourceComponent()) { - return; - } - - if (NavigateToFunctionAction.this.isSelected()) { - Function f = (Function) panel.getSourceComponent().getSelectedItem(); - goToService.goTo(f.getEntryPoint(), f.getProgram()); - } - } - }); - - panel.getTargetComponent().addItemListener(new ItemListener() { - @Override - public void itemStateChanged(ItemEvent e) { - if (e.getStateChange() != ItemEvent.SELECTED) { - return; - } - - if (panel.getFocusedComponent() != panel.getTargetComponent()) { - return; - } - - if (NavigateToFunctionAction.this.isSelected()) { - Function f = (Function) panel.getTargetComponent().getSelectedItem(); - goToService.goTo(f.getEntryPoint(), f.getProgram()); - } - } - }); } /** * Adds a listener to each panel in the function comparison provider, * triggered when focus has been changed. If focused is gained in a panel, * a GoTo event is issued containing the function start address. - * - * @param provider the function comparison provider */ - private void addFocusListeners(MultiFunctionComparisonProvider provider) { + private void addFocusListeners() { + List panels = comparisonPanel.getComparisonPanels(); - FunctionComparisonPanel mainPanel = provider.getComponent(); - List> panels = - mainPanel.getComparisonPanels(); + for (CodeComparisonPanel panel : panels) { + panel.getComparisonComponent(LEFT) + .addFocusListener(new PanelFocusListener(panel, Side.LEFT)); + panel.getComparisonComponent(RIGHT) + .addFocusListener(new PanelFocusListener(panel, Side.RIGHT)); + } + } - for (CodeComparisonPanel panel : panels) { + private class PanelItemListener implements ItemListener { + private Side side; - panel.getRightFieldPanel().addFocusListener(new FocusAdapter() { + PanelItemListener(Side side) { + this.side = side; + } - @Override - public void focusGained(FocusEvent e) { - if (NavigateToFunctionAction.this.isSelected()) { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() != ItemEvent.SELECTED) { + return; + } + if (comparisonPanel.getFocusedSide() != side) { + return; + } - Address addr = null; + if (isSelected()) { + JComboBox combo = (JComboBox) e.getSource(); + Function f = (Function) combo.getSelectedItem(); + goToService.goTo(f.getEntryPoint(), f.getProgram()); + } + } - if (panel.getRightFunction() != null) { - addr = panel.getRightFunction().getBody().getMinAddress(); - } - else if (panel.getRightData() != null) { - addr = panel.getRightData().getAddress(); - } - else if (panel.getRightAddresses() != null) { - addr = panel.getRightAddresses().getMinAddress(); - } + } - goToService.goTo(addr, panel.getRightProgram()); - } - } - }); + private class PanelFocusListener extends FocusAdapter { + private CodeComparisonPanel panel; + private Side side; - panel.getLeftFieldPanel().addFocusListener(new FocusAdapter() { + PanelFocusListener(CodeComparisonPanel panel, Side side) { + this.panel = panel; + this.side = side; + } - @Override - public void focusGained(FocusEvent e) { - if (NavigateToFunctionAction.this.isSelected()) { - Address addr = null; - - if (panel.getLeftFunction() != null) { - addr = panel.getLeftFunction().getBody().getMinAddress(); - } - else if (panel.getLeftData() != null) { - addr = panel.getLeftData().getAddress(); - } - else if (panel.getLeftAddresses() != null) { - addr = panel.getLeftAddresses().getMinAddress(); - } - - goToService.goTo(addr, panel.getLeftProgram()); - } - } - - }); + @Override + public void focusGained(FocusEvent e) { + if (!isSelected()) { + return; + } + Program program = panel.getProgram(side); + AddressSetView addresses = panel.getAddresses(side); + if (program != null && addresses != null && !addresses.isEmpty()) { + goToService.goTo(addresses.getMinAddress(), program); + } } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerManager.java index 426719096f..170cfc6635 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/marker/MarkerManager.java @@ -107,6 +107,11 @@ public class MarkerManager implements MarkerService { Gui.addThemeListener(themeListener); } + public void clearAll() { + programMarkersByGroup.clear(); + markerSetCache.clear(); + } + private void themeChanged(ThemeEvent e) { if (e instanceof ColorChangedThemeEvent) { markerSetCache.clearColors(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/format/FieldHeader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/format/FieldHeader.java index 1c0c58e06b..51febf1049 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/format/FieldHeader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/format/FieldHeader.java @@ -40,15 +40,6 @@ public class FieldHeader extends JTabbedPane implements ChangeListener { private FormatManager formatManager; private FormatModelListener formatListener = new FormatModelListener() { - @Override - public void formatModelAdded(FieldFormatModel formatModel) { - createTabs(); - } - - @Override - public void formatModelRemoved(FieldFormatModel formatModel) { - createTabs(); - } @Override public void formatModelChanged(FieldFormatModel formatModel) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/format/FormatManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/format/FormatManager.java index ff085e412a..4e3e47e354 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/format/FormatManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/format/FormatManager.java @@ -159,7 +159,9 @@ public class FormatManager implements OptionsChangeListener { * @param listener the listener to be added */ public void addFormatModelListener(FormatModelListener listener) { - formatListeners.add(listener); + if (listener != null) { + formatListeners.add(listener); + } } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/format/FormatModelListener.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/format/FormatModelListener.java index fb1ef9a745..cbbfdcc2a7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/format/FormatModelListener.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/format/FormatModelListener.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,16 +19,26 @@ package ghidra.app.util.viewer.format; * Interface for listeners to format model changes. */ public interface FormatModelListener { + /** - * Notifies that a new format model was added to the format manager. - * @param model the new model. + * Format model added. Not used. + * @param model the model that was added + * @deprecated not used */ - void formatModelAdded(FieldFormatModel model); + @Deprecated(since = "11.2", forRemoval = true) + default void formatModelAdded(FieldFormatModel model) { + // not used + } + /** - * Notifies that a format model was removed. - * @param model the model that was removed. + * Format model removed. Not used. + * @param model the model that was added + * @deprecated not used */ - void formatModelRemoved(FieldFormatModel model); + @Deprecated(since = "11.2", forRemoval = true) + default void formatModelRemoved(FieldFormatModel model) { + // not used + } /** * Notifies that the given format model was changed. diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingActionContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingActionContext.java index b1e61b956f..a8a3e6287c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingActionContext.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingActionContext.java @@ -16,57 +16,32 @@ package ghidra.app.util.viewer.listingpanel; import docking.ComponentProvider; -import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; import ghidra.app.util.viewer.util.CodeComparisonActionContext; -import ghidra.app.util.viewer.util.CodeComparisonPanel; -import ghidra.program.model.listing.Function; -// Note: If you want to get the typical actions for things like comments, labels, bookmarks, etc. -// that are available in the CodeBrowser then change this to extend ListingActionContext. -// This currently extends NavigatableActionContext so that it does NOT get the typical -// CodeBrowser Listing actions. /** * Action context for a ListingCodeComparisonPanel. */ public class DualListingActionContext extends CodeComparisonActionContext { - private CodeComparisonPanel codeComparisonPanel = null; + private ListingCodeComparisonPanel 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 DualListingActionContext(ComponentProvider provider) { - super(provider); + public DualListingActionContext(ComponentProvider provider, ListingCodeComparisonPanel panel) { + super(provider, panel, panel.getActiveListingPanel().getFieldPanel()); + this.codeComparisonPanel = panel; } /** - * Sets the CodeComparisonPanel associated with this context. - * @param codeComparisonPanel the code comparison panel + * Returns the {@link ListingCodeComparisonPanel} that generated this context + * @return the listing comparison panel that generated this context */ - public void setCodeComparisonPanel( - CodeComparisonPanel codeComparisonPanel) { - this.codeComparisonPanel = codeComparisonPanel; - } - @Override - public CodeComparisonPanel getCodeComparisonPanel() { + public ListingCodeComparisonPanel getCodeComparisonPanel() { return codeComparisonPanel; } - @Override - public Function getSourceFunction() { - boolean leftHasFocus = codeComparisonPanel.leftPanelHasFocus(); - - return leftHasFocus ? codeComparisonPanel.getRightFunction() - : codeComparisonPanel.getLeftFunction(); - } - - @Override - public Function getTargetFunction() { - boolean leftHasFocus = codeComparisonPanel.leftPanelHasFocus(); - - return leftHasFocus ? codeComparisonPanel.getLeftFunction() - : codeComparisonPanel.getRightFunction(); - } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingFieldPanelCoordinator.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingFieldPanelCoordinator.java deleted file mode 100644 index 139d062add..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingFieldPanelCoordinator.java +++ /dev/null @@ -1,37 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.app.util.viewer.listingpanel; - -import ghidra.program.util.ProgramLocation; -import docking.widgets.fieldpanel.listener.ViewListener; - -/** - * Coordinates the locations between the left and right sides of a dual listing panel. - */ -public interface DualListingFieldPanelCoordinator extends ViewListener { - - /** - * Method that gets called when the location changes in the left side's program listing. - * @param leftLocation the new location in the left side. - */ - public void leftLocationChanged(ProgramLocation leftLocation); - - /** - * Method that gets called when the location changes in the right side's program listing. - * @param rightLocation the new location in the right side. - */ - public void rightLocationChanged(ProgramLocation rightLocation); -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingGoToService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingGoToService.java index de2aa3b298..51cb42ade0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingGoToService.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingGoToService.java @@ -22,6 +22,7 @@ import ghidra.program.model.address.AddressSetView; import ghidra.program.model.listing.Program; import ghidra.program.model.symbol.ExternalLocation; import ghidra.program.util.ProgramLocation; +import ghidra.util.datastruct.Duo.Side; import ghidra.util.task.TaskMonitor; /** @@ -32,7 +33,7 @@ import ghidra.util.task.TaskMonitor; class DualListingGoToService implements GoToService { private ListingCodeComparisonPanel dualListing; - private boolean isLeftSide; + private Side side; private GoToOverrideService overrideService; private GoToService goToService; @@ -41,13 +42,13 @@ class DualListingGoToService implements GoToService { * @param goToService the GoToService that this overrides and that can be used when the * GoToService methods don't pertain specifically to the left or right listing panel. * @param dualListing the dual listing panel - * @param isLeftSide true means this GoToService is for the left listing panel of the dual listing. + * @param side the LEFT or RIGHT side */ DualListingGoToService(GoToService goToService, ListingCodeComparisonPanel dualListing, - boolean isLeftSide) { + Side side) { this.goToService = goToService; this.dualListing = dualListing; - this.isLeftSide = isLeftSide; + this.side = side; } @Override @@ -92,8 +93,7 @@ class DualListingGoToService implements GoToService { if (addr == null) { return false; } - AddressSetView addresses = - isLeftSide ? dualListing.getLeftAddresses() : dualListing.getRightAddresses(); + AddressSetView addresses = dualListing.getAddresses(side); if (!addresses.contains(addr)) { dualListing.setStatusInfo( "\"" + addr.toString() + "\" is outside the current listing's view."); @@ -112,8 +112,7 @@ class DualListingGoToService implements GoToService { return false; } - ListingPanel listingPanel = - (isLeftSide) ? dualListing.getLeftPanel() : dualListing.getRightPanel(); + ListingPanel listingPanel = dualListing.getListingPanel(side); return listingPanel.goTo(loc); } @@ -124,8 +123,7 @@ class DualListingGoToService implements GoToService { return false; } - ListingPanel listingPanel = - (isLeftSide) ? dualListing.getLeftPanel() : dualListing.getRightPanel(); + ListingPanel listingPanel = dualListing.getListingPanel(side); return listingPanel.goTo(addr); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingNavigator.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingNavigator.java index 5a585056ba..adf74a6757 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingNavigator.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingNavigator.java @@ -36,15 +36,14 @@ class DualListingNavigator implements Navigatable { /** * Constructor for a dual listing navigator. - * @param dualListingPanel the dual listing whose left or right listing panel is to be controlled. - * @param isLeftSide true indicates that this navigator is for the left side listing. + * @param listingPanel the dual listing whose left or right listing panel is to be controlled. + * @param goToService which side LEFT or RIGHT * false means it's for the right side listing. */ - DualListingNavigator(ListingCodeComparisonPanel dualListingPanel, boolean isLeftSide) { + DualListingNavigator(ListingPanel listingPanel, GoToService goToService) { - this.listingPanel = - isLeftSide ? dualListingPanel.getLeftPanel() : dualListingPanel.getRightPanel(); - this.goToService = dualListingPanel.getGoToService(isLeftSide); + this.listingPanel = listingPanel; + this.goToService = goToService; id = UniversalIdGenerator.nextID().getValue(); } @@ -152,7 +151,8 @@ class DualListingNavigator implements Navigatable { } @Override - public void removeHighlightProvider(ListingHighlightProvider highlightProvider, Program program) { + public void removeHighlightProvider(ListingHighlightProvider highlightProvider, + Program program) { // currently unsupported } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingServiceProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingServiceProvider.java index c93b6e24e8..4a80ad1891 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingServiceProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/DualListingServiceProvider.java @@ -18,6 +18,7 @@ package ghidra.app.util.viewer.listingpanel; import ghidra.app.services.GoToService; import ghidra.framework.plugintool.ServiceProvider; import ghidra.framework.plugintool.util.ServiceListener; +import ghidra.util.datastruct.Duo.Side; /** * This provides services, but overrides and implements its own goTo for one of the listing @@ -27,22 +28,21 @@ import ghidra.framework.plugintool.util.ServiceListener; * the dual listing code comparison panel. */ class DualListingServiceProvider implements ServiceProvider { - + private ServiceProvider serviceProvider; private DualListingGoToService dualListingGoToService; - + /** * Constructor for a DualListingServiceProvider. * @param serviceProvider the service provider to use for acquiring services other than goTo. * @param panel the dual listing code comparison panel. - * @param isLeftPanel true indicates this is the left listing panel of the dual panel. - * false indicates the right panel. + * @param side LEFT or RIGHT */ DualListingServiceProvider(ServiceProvider serviceProvider, ListingCodeComparisonPanel panel, - boolean isLeftPanel) { + Side side) { this.serviceProvider = serviceProvider; GoToService goToService = serviceProvider.getService(GoToService.class); - this.dualListingGoToService = new DualListingGoToService(goToService, panel, isLeftPanel); + this.dualListingGoToService = new DualListingGoToService(goToService, panel, side); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/LinearAddressCorrelation.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/LinearAddressCorrelation.java new file mode 100644 index 0000000000..21c1d823ba --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/LinearAddressCorrelation.java @@ -0,0 +1,96 @@ +/* ### + * 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.util.viewer.listingpanel; + +import static ghidra.util.datastruct.Duo.Side.*; + +import ghidra.app.util.viewer.util.ComparisonData; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.*; +import ghidra.program.util.ListingAddressCorrelation; +import ghidra.util.datastruct.Duo; +import ghidra.util.datastruct.Duo.Side; + +/** + * Creates an address correlation with a simplistic correlation where each address correlates based + * on an offset from the address set's minimum address. + */ +public class LinearAddressCorrelation implements ListingAddressCorrelation { + private Duo comparisonData; + + public LinearAddressCorrelation(Duo comparisonData) { + this.comparisonData = comparisonData; + } + + @Override + public Program getProgram(Side side) { + return comparisonData.get(LEFT).getProgram(); + } + + @Override + public AddressSetView getAddresses(Side side) { + return comparisonData.get(LEFT).getAddressSet(); + } + + @Override + public Function getFunction(Side side) { + return null; + } + + @Override + public Address getAddress(Side side, Address otherAddress) { + Side otherSide = side.otherSide(); + if (!isValidAddress(otherSide, otherAddress) || !isCodeUnitStart(otherSide, otherAddress)) { + return null; + } + AddressSetView otherSet = comparisonData.get(otherSide).getAddressSet(); + Address minOtherAddress = otherSet.getMinAddress(); + long offset = otherAddress.subtract(minOtherAddress); + Address minAddress = comparisonData.get(side).getAddressSet().getMinAddress(); + Address address = minAddress.addWrap(offset); + if (!isValidAddress(side, address)) { + return null; + } + return normalizeToCodeUnitStart(side, address); + } + + private boolean isValidAddress(Side side, Address address) { + AddressSetView addresses = comparisonData.get(side).getAddressSet(); + return addresses.contains(address); + } + + private boolean isCodeUnitStart(Side side, Address address) { + Listing listing = getListing(side); + CodeUnit cu = listing.getCodeUnitAt(address); + return cu != null; + } + + private Address normalizeToCodeUnitStart(Side side, Address address) { + Listing listing = getListing(side); + CodeUnit cu = listing.getCodeUnitContaining(address); + Address minAddress = cu.getMinAddress(); + if (isValidAddress(side, minAddress)) { + return minAddress; + } + return null; + } + + private Listing getListing(Side side) { + return comparisonData.get(side).getProgram().getListing(); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingCodeComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingCodeComparisonPanel.java index f7350c454b..0bcbdbaa7f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingCodeComparisonPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingCodeComparisonPanel.java @@ -15,79 +15,56 @@ */ package ghidra.app.util.viewer.listingpanel; -import java.awt.*; -import java.awt.event.*; +import static ghidra.util.datastruct.Duo.Side.*; + +import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.List; -import javax.swing.*; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; +import javax.swing.Icon; +import javax.swing.JComponent; import docking.*; -import docking.action.*; -import docking.menu.ActionState; +import docking.action.DockingAction; +import docking.action.ToggleDockingAction; +import docking.action.builder.*; import docking.menu.MultiStateDockingAction; import docking.options.OptionsService; import docking.widgets.EventTrigger; import docking.widgets.fieldpanel.FieldPanel; -import docking.widgets.fieldpanel.field.Field; -import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; -import docking.widgets.fieldpanel.listener.FieldLocationListener; -import docking.widgets.fieldpanel.support.FieldLocation; -import docking.widgets.fieldpanel.support.ViewerPosition; import generic.theme.GIcon; -import generic.theme.GThemeDefaults.Colors.Palette; -import ghidra.GhidraOptions; -import ghidra.app.nav.Navigatable; -import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel; -import ghidra.app.plugin.core.codebrowser.hover.*; import ghidra.app.plugin.core.functioncompare.actions.*; -import ghidra.app.plugin.core.marker.MarkerManager; -import ghidra.app.services.*; import ghidra.app.util.ListingHighlightProvider; -import ghidra.app.util.SymbolPath; import ghidra.app.util.viewer.format.*; -import ghidra.app.util.viewer.util.*; +import ghidra.app.util.viewer.util.CodeComparisonPanel; +import ghidra.app.util.viewer.util.CodeComparisonPanelActionContext; import ghidra.framework.options.*; import ghidra.framework.plugintool.PluginTool; -import ghidra.framework.plugintool.ServiceProviderDecorator; import ghidra.program.model.address.*; import ghidra.program.model.correlate.HashedFunctionAddressCorrelation; -import ghidra.program.model.listing.*; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; import ghidra.program.model.mem.MemoryAccessException; -import ghidra.program.model.mem.MemoryBlock; -import ghidra.program.model.symbol.*; import ghidra.program.util.*; -import ghidra.util.*; +import ghidra.util.HelpLocation; +import ghidra.util.Msg; +import ghidra.util.datastruct.Duo; +import ghidra.util.datastruct.Duo.Side; +import ghidra.util.exception.AssertException; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; import help.Help; -import help.HelpService; /** * Panel that displays two listings for comparison. */ public class ListingCodeComparisonPanel - extends CodeComparisonPanel implements - FormatModelListener, CodeFormatService, ListingDiffChangeListener, OptionsChangeListener { - - private static final Color FG_COLOR_TITLE = Palette.DARK_GRAY; - - private static final String DUAL_LISTING_HEADER_SHOWING = "DUAL_LISTING_HEADER_SHOWING"; - private static final String DUAL_LISTING_SIDE_BY_SIDE = "DUAL_LISTING_SIDE_BY_SIDE"; - public static final String NAME = "DualListing"; - public static final String TITLE = "Listing View"; - - protected static final HelpService help = Help.getHelpService(); - - private static final String DUAL_LISTING_HELP_TOPIC = "FunctionComparison"; - private static final String DUAL_LISTING_ACTION_GROUP = NAME; + extends CodeComparisonPanel implements + FormatModelListener, OptionsChangeListener { + public static final String NAME = "Listing View"; private static final String DIFF_NAVIGATE_GROUP = "A2_DiffNavigate"; - private static final String HOVER_GROUP = "A5_Hovers"; - private static final String PROPERTIES_GROUP = "B1_Properties"; //@formatter:off private static final Icon NEXT_DIFF_ICON = new GIcon("icon.base.util.listingcompare.diff.next"); @@ -95,67 +72,33 @@ public class ListingCodeComparisonPanel private static final Icon BOTH_VIEWS_ICON = new GIcon("icon.base.util.listingcompare.area.markers.all"); private static final Icon UNMATCHED_ICON = new GIcon("icon.base.util.listingcompare.area.markers.unmatched"); private static final Icon DIFF_ICON = new GIcon("icon.base.util.listingcompare.area.markers.diff"); - private static final Icon CURSOR_LOC_ICON = new GIcon("icon.base.util.listingcompare.cursor"); - //@formatter:on - private static final Icon HOVER_ON_ICON = new GIcon("icon.base.util.listingcompare.hover.on"); private static final Icon HOVER_OFF_ICON = new GIcon("icon.base.util.listingcompare.hover.off"); + //@formatter:on - private static final String ALL_AREA_MARKERS = "All Area Markers"; - private static final String UNMATCHED_AREA_MARKERS = "Unmatched Area Markers"; - private static final String DIFF_AREA_MARKERS = "Diff Area Markers"; - private String nextPreviousAreaType; + private enum NavigateType { + ALL, UNMATCHED, DIFF + } - private ListingPanel[] listingPanels = new ListingPanel[2]; - private ListingDiff listingDiff; - private ListingDiffActionManager diffActionManager; - private DualListingServiceProvider[] dualListingServiceProviders = - new DualListingServiceProvider[2]; - private DualListingNavigator[] navigatables = new DualListingNavigator[2]; - private FieldNavigator[] fieldNavigators = new FieldNavigator[2]; - private AddressIndexMap[] indexMaps = new AddressIndexMap[2]; - private AddressSetView[] addressSets = - new AddressSetView[] { EMPTY_ADDRESS_SET, EMPTY_ADDRESS_SET }; - private MarkerManager[] markerManagers = new MarkerManager[2]; - private MarkerSet[] unmatchedCodeMarkers = new MarkerSet[2]; - private MarkerSet[] diffMarkers = new MarkerSet[2]; - private MarkerSet[] currentCursorMarkers = new MarkerSet[2]; - private static final Color CURSOR_LINE_COLOR = GhidraOptions.DEFAULT_CURSOR_LINE_COLOR; - private Color cursorHighlightColor; - private boolean isShowingEntireListing; - private boolean isSideBySide = true; - private boolean fieldLocationChanging = false; - private LeftLocationListener leftLocationListener; - private RightLocationListener rightLocationListener; - private ToggleHeaderAction toggleHeaderAction; - private ToggleOrientationAction toggleOrientationAction; - private ToggleHoverAction toggleHoverAction; - private NextPreviousAreaMarkerAction nextPreviousAreaMarkerAction; - private NextDiffAction nextDiffAction; - private PreviousDiffAction previousDiffAction; - private ListingCodeComparisonOptionsAction optionsAction; - private DockingAction[] diffActions; - private FunctionNameApplyAction applyNameAction; - private EmptySignatureApplyAction applySignatureEmptyDatatypesAction; - private SignatureWithDatatypesApplyAction applySignatureWithDatatypesAction; - private JSplitPane splitPane; - - private ListingDiffHighlightProvider leftDiffHighlightProvider; - private ListingDiffHighlightProvider rightDiffHighlightProvider; - - private FunctionAddressCorrelation correlator; - - private boolean adjustingLeftLocation = false; - private boolean adjustingRightLocation = false; - - ReferenceListingHover referenceHoverService; - DataTypeListingHover dataTypeHoverService; - TruncatedTextListingHover truncatedTextHoverService; - FunctionNameListingHover functionNameHoverService; - private String leftTitle; - private String rightTitle; private ListingCodeComparisonOptions comparisonOptions; - private Address[] coordinatorLockedAddresses; + + private Duo displays; + + private ListingAddressCorrelation addressCorrelator; + private ListingDiff listingDiff; + private ListingCoordinator coordinator; + private boolean listingsLocked; + + private ListingDiffActionManager diffActionManager; + private DockingAction nextDiffAction; + private DockingAction previousDiffAction; + private DockingAction optionsAction; + private DockingAction applyFunctionNameAction; + private DockingAction applyEmptySignatureAction; + private DockingAction applySignatureAction; + private ToggleDockingAction toggleHeaderAction; + private ToggleDockingAction toggleHoverAction; + private MultiStateDockingAction nextPreviousAreaTypeAction; /** * Creates a comparison panel with two listings. @@ -165,32 +108,45 @@ public class ListingCodeComparisonPanel */ public ListingCodeComparisonPanel(String owner, PluginTool tool) { super(owner, tool); - initialize(); - } - - private void initialize() { - listingDiff = new ListingDiff(); - diffActionManager = new ListingDiffActionManager(listingDiff); - initializeGoToServiceProviders(); // Must be before other buildPanel() and initialize methods. - buildPanel(); - initializeListingFieldPanels(); - initializeListingFieldNavigation(); - initializeListingHoverService(); - setupMarkerManagers(); - createActions(); - listingDiff.addListingDiffChangeListener(this); - setScrollingSyncState(true); - help.registerHelp(this, new HelpLocation(DUAL_LISTING_HELP_TOPIC, "Dual Listing")); - - comparisonOptions = new ListingCodeComparisonOptions(); + Help.getHelpService().registerHelp(this, new HelpLocation(HELP_TOPIC, "Dual Listing")); initializeOptions(); + + listingDiff = buildListingDiff(); + displays = buildListingDisplays(); + buildPanel(); + createActions(); + + setSynchronizedScrolling(true); } - private void initializeOptions() { - ToolOptions options = tool.getOptions(ListingCodeComparisonOptions.OPTIONS_CATEGORY_NAME); - options.addOptionsChangeListener(this); - comparisonOptions.initializeOptions(options); - comparisonOptions.loadOptions(options); + private ListingDiff buildListingDiff() { + ListingDiff diff = new ListingDiff(); + diffActionManager = new ListingDiffActionManager(diff); + return diff; + } + + private Duo buildListingDisplays() { + ListingDisplay leftDisplay = + new ListingDisplay(tool, owner, listingDiff, comparisonOptions, LEFT); + ListingDisplay rightDisplay = + new ListingDisplay(tool, owner, listingDiff, comparisonOptions, RIGHT); + + // make the right format manager always be the same as the left format manager + leftDisplay.getFormatManager().addFormatModelListener(this); + + leftDisplay.setProgramLocationListener((l, t) -> programLocationChanged(LEFT, l, t)); + rightDisplay.setProgramLocationListener((l, t) -> programLocationChanged(RIGHT, l, t)); + + return new Duo<>(leftDisplay, rightDisplay); + } + + @Override + public JComponent getComparisonComponent(Side side) { + return displays.get(side).getListingPanel(); + } + + public ListingPanel getListingPanel(Side side) { + return displays.get(side).getListingPanel(); } @Override @@ -198,113 +154,20 @@ public class ListingCodeComparisonPanel Object newValue) { comparisonOptions.loadOptions(options); - - repaint(); // Refresh the highlights. (byte, mnemonic, & operand) - - if (programs[LEFT] == null) { - // not data is showing; no widgets to update - return; - } - - // Refresh the area markers for Diff Code Units and Unmatched Code Units. - Color unmatchedCodeUnitsBackgroundColor = - comparisonOptions.getUnmatchedCodeUnitsBackgroundColor(); - unmatchedCodeMarkers[LEFT].setMarkerColor(unmatchedCodeUnitsBackgroundColor); - unmatchedCodeMarkers[RIGHT].setMarkerColor(unmatchedCodeUnitsBackgroundColor); - Color diffCodeUnitsBackgroundColor = comparisonOptions.getDiffCodeUnitsBackgroundColor(); - diffMarkers[LEFT].setMarkerColor(diffCodeUnitsBackgroundColor); - diffMarkers[RIGHT].setMarkerColor(diffCodeUnitsBackgroundColor); + updateProgramViews(); } @Override - public JComponent getComponent() { - return this; + public String getName() { + return NAME; } @Override - public String getTitle() { - return TITLE; - } - - @Override - public void setVisible(boolean aFlag) { - super.setVisible(aFlag); + public void setVisible(boolean b) { + super.setVisible(b); updateActionEnablement(); } - private FormatManager createFormatManager(int leftOrRight) { - ToolOptions displayOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_DISPLAY); - ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS); - - FormatManager formatManager = new FormatManager(displayOptions, fieldOptions); - - ServiceProviderDecorator sp = ServiceProviderDecorator.createEmptyDecorator(); - sp.overrideService(GoToService.class, - dualListingServiceProviders[leftOrRight].getService(GoToService.class)); - formatManager.setServiceProvider(sp); - - // - // Unusual Code Alert! - // In a normal tool, this option is registered by the Code Browse Plugin. In the VT - // tool, nobody registers this option. Our system logs a warning if an option is used - // but not registered. So, when in a real tool, use the registered/managed option. - // Otherwise, just use the default. - // - if (fieldOptions.isRegistered(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR)) { - cursorHighlightColor = - fieldOptions.getColor(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR, CURSOR_LINE_COLOR); - } - else { - cursorHighlightColor = CURSOR_LINE_COLOR; - } - - return formatManager; - } - - private void initializeGoToServiceProviders() { - dualListingServiceProviders[LEFT] = new DualListingServiceProvider(tool, this, true); - dualListingServiceProviders[RIGHT] = new DualListingServiceProvider(tool, this, false); - } - - private void initializeListingFieldPanels() { - FieldPanel[] fieldPanels = new FieldPanel[2]; - for (int i = 0; i < 2; i++) { - fieldPanels[i] = listingPanels[i].getFieldPanel(); - fieldPanels[i].addFocusListener(this); - fieldPanels[i].addMouseListener(new DualListingMouseListener(i)); - } - - leftLocationListener = new LeftLocationListener(); - rightLocationListener = new RightLocationListener(); - fieldPanels[LEFT].addFieldLocationListener(leftLocationListener); - fieldPanels[RIGHT].addFieldLocationListener(rightLocationListener); - } - - /** - * Sets the coordinator for the two listings within this code comparison panel. It coordinates - * their scrolling and location synchronization. - * - * @param listingFieldPanelCoordinator the coordinator for the two listings - */ - @Override - public void setFieldPanelCoordinator( - ListingComparisonFieldPanelCoordinator listingFieldPanelCoordinator) { - ListingComparisonFieldPanelCoordinator fieldPanelCoordinator = getFieldPanelCoordinator(); - if (fieldPanelCoordinator == listingFieldPanelCoordinator) { - return; - } - - super.setFieldPanelCoordinator(listingFieldPanelCoordinator); - - if (listingFieldPanelCoordinator != null) { - ListingPanel focusedListingPanel = getFocusedListingPanel(); - ProgramLocation programLocation = focusedListingPanel.getProgramLocation(); - if (programLocation != null) { - focusedListingPanel.goTo(programLocation); - } - } - } - /** * Adds the indicated highlight providers for the left and right listing panels. * @@ -313,16 +176,8 @@ public class ListingCodeComparisonPanel */ public void addHighlightProviders(ListingHighlightProvider leftHighlightProvider, ListingHighlightProvider rightHighlightProvider) { - addLeftHighlightProvider(leftHighlightProvider); - addRightHighlightProvider(rightHighlightProvider); - } - - private void addLeftHighlightProvider(ListingHighlightProvider leftHighlightProvider) { - listingPanels[LEFT].getFormatManager().addHighlightProvider(leftHighlightProvider); - } - - private void addRightHighlightProvider(ListingHighlightProvider rightHighlightProvider) { - listingPanels[RIGHT].getFormatManager().addHighlightProvider(rightHighlightProvider); + displays.get(LEFT).addHighlightProvider(leftHighlightProvider); + displays.get(RIGHT).addHighlightProvider(rightHighlightProvider); } /** @@ -333,153 +188,26 @@ public class ListingCodeComparisonPanel */ public void removeHighlightProviders(ListingHighlightProvider leftHighlightProvider, ListingHighlightProvider rightHighlightProvider) { - removeLeftHighlightProvider(leftHighlightProvider); - removeRightHighlightProvider(rightHighlightProvider); - } - - private void removeLeftHighlightProvider(ListingHighlightProvider leftHighlightProvider) { - listingPanels[LEFT].getFormatManager().removeHighlightProvider(leftHighlightProvider); - } - - private void removeRightHighlightProvider(ListingHighlightProvider rightHighlightProvider) { - listingPanels[RIGHT].getFormatManager().removeHighlightProvider(rightHighlightProvider); + displays.get(LEFT).removeHighlightProvider(leftHighlightProvider); + displays.get(RIGHT).removeHighlightProvider(rightHighlightProvider); } @Override - protected void setPrograms(Program leftProgram, Program rightProgram) { - boolean programChanged = false; - if (leftProgram != programs[LEFT]) { - programs[LEFT] = leftProgram; - listingPanels[LEFT].setProgram(leftProgram); - addressSets[LEFT] = EMPTY_ADDRESS_SET; - indexMaps[LEFT] = new AddressIndexMap(addressSets[LEFT]); - updateLeftListingTitle(); - programChanged = true; - } - if (rightProgram != programs[RIGHT]) { - programs[RIGHT] = rightProgram; - listingPanels[RIGHT].setProgram(rightProgram); - addressSets[RIGHT] = EMPTY_ADDRESS_SET; - indexMaps[RIGHT] = new AddressIndexMap(addressSets[RIGHT]); - updateRightListingTitle(); - programChanged = true; - } - setupAreaMarkerSets(); - setupCursorMarkerSets(); - if (programChanged) { - showEntireListing(isShowingEntireListing); - } - } + public List getActions() { + List actions = super.getActions(); - private void updateLeftListingTitle() { - titlePanels[LEFT].setTitleName(getLeftProgramName()); - } + actions.add(nextPreviousAreaTypeAction); + actions.add(toggleHeaderAction); + actions.add(toggleHoverAction); + actions.add(applyFunctionNameAction); + actions.add(applyEmptySignatureAction); + actions.add(applySignatureAction); + actions.add(nextDiffAction); + actions.add(previousDiffAction); + actions.add(optionsAction); - private String getLeftProgramName() { - String leftProgramName = - (programs[LEFT] != null) ? programs[LEFT].getDomainFile().toString() : "none"; - return leftProgramName; - } + actions.addAll(diffActionManager.getActions()); - private void updateRightListingTitle() { - titlePanels[RIGHT].setTitleName(getRightProgramName()); - } - - private String getRightProgramName() { - String rightProgramName = - (programs[RIGHT] != null) ? programs[RIGHT].getDomainFile().toString() : "none"; - return rightProgramName; - } - - private void initializeListingFieldNavigation() { - initializeListingFieldNavigation(LEFT); - initializeListingFieldNavigation(RIGHT); - } - - private void initializeListingFieldNavigation(int leftOrRight) { - boolean isLeftSide = (leftOrRight == LEFT); - navigatables[leftOrRight] = new DualListingNavigator(this, isLeftSide); - fieldNavigators[leftOrRight] = - new FieldNavigator(dualListingServiceProviders[leftOrRight], navigatables[leftOrRight]); - listingPanels[leftOrRight].addButtonPressedListener(fieldNavigators[leftOrRight]); - } - - private Navigatable getFocusedNavigatable() { - return navigatables[currProgramIndex]; - } - - private void initializeListingHoverService() { - - // The CodeFormatService is needed by the ReferenceHover. - referenceHoverService = new ReferenceListingHover(tool, this); - dataTypeHoverService = new DataTypeListingHover(tool); - truncatedTextHoverService = new TruncatedTextListingHover(tool); - functionNameHoverService = new FunctionNameListingHover(tool); - - initializeListingHoverService(LEFT); - initializeListingHoverService(RIGHT); - } - - private void initializeListingHoverService(int leftOrRight) { - ListingPanel listingPanel = listingPanels[leftOrRight]; - listingPanel.addHoverService(referenceHoverService); - listingPanel.addHoverService(dataTypeHoverService); - listingPanel.addHoverService(truncatedTextHoverService); - listingPanel.addHoverService(functionNameHoverService); - listingPanel.setHoverMode(true); - } - - /** - * Sets a listener for program location changes for the left side's listing panel. - * - * @param programLocationListener the listener - */ - public void setLeftProgramLocationListener(ProgramLocationListener programLocationListener) { - listingPanels[LEFT].setProgramLocationListener(programLocationListener); - } - - /** - * Sets a listener for program location changes for the right side's listing panel. - * - * @param programLocationListener the listener - */ - public void setRightProgramLocationListener(ProgramLocationListener programLocationListener) { - listingPanels[RIGHT].setProgramLocationListener(programLocationListener); - } - - private void createActions() { - toggleHeaderAction = new ToggleHeaderAction(); - toggleOrientationAction = new ToggleOrientationAction(); - toggleHoverAction = new ToggleHoverAction(); - applyNameAction = new FunctionNameApplyAction(owner); - applySignatureEmptyDatatypesAction = new EmptySignatureApplyAction(owner); - applySignatureWithDatatypesAction = new SignatureWithDatatypesApplyAction(owner); - nextDiffAction = new NextDiffAction(); - previousDiffAction = new PreviousDiffAction(); - optionsAction = new ListingCodeComparisonOptionsAction(); - // NextDiff and PreviousDiff must be created before the area marker action. - nextPreviousAreaMarkerAction = new NextPreviousAreaMarkerAction(owner); - diffActions = getListingDiffActions(); - } - - private DockingAction[] getListingDiffActions() { - return diffActionManager.getActions(); - } - - @Override - public DockingAction[] getActions() { - DockingAction[] codeCompActions = super.getActions(); - DockingAction[] otherActions = new DockingAction[] { toggleHeaderAction, - toggleOrientationAction, toggleHoverAction, applyNameAction, - applySignatureEmptyDatatypesAction, applySignatureWithDatatypesAction, - nextPreviousAreaMarkerAction, nextDiffAction, previousDiffAction, optionsAction }; - int compCount = codeCompActions.length; - int otherCount = otherActions.length; - int diffCount = diffActions.length; - DockingAction[] actions = new DockingAction[compCount + otherCount + diffCount]; - System.arraycopy(codeCompActions, 0, actions, 0, compCount); - System.arraycopy(otherActions, 0, actions, compCount, otherCount); - System.arraycopy(diffActions, 0, actions, compCount + otherCount, diffCount); return actions; } @@ -496,1596 +224,38 @@ public class ListingCodeComparisonPanel diffActionManager.updateActionEnablement(listingDiffActionEnablement); } - class ToggleHeaderAction extends ToggleDockingAction { - ToggleHeaderAction() { - super("Dual Listing Toggle Header", owner); - setDescription("Toggle Format Header"); - setEnabled(true); - MenuData menuData = new MenuData(new String[] { "Show Listing Format Header" }, - DUAL_LISTING_ACTION_GROUP); - setMenuBarData(menuData); - - setHelpLocation( - new HelpLocation(DUAL_LISTING_HELP_TOPIC, "Dual Listing Toggle Format Header")); - } - - @Override - public void actionPerformed(ActionContext context) { - ListingPanel listingPanel = getLeftPanel(); - boolean show = !listingPanel.isHeaderShowing(); - listingPanel.showHeader(show); - listingPanel.validate(); - listingPanel.invalidate(); - } - } - - class ToggleOrientationAction extends ToggleDockingAction { - ToggleOrientationAction() { - super("Dual Listing Toggle Orientation", owner); - setDescription("Toggle the layout of the listings " + - "
between side-by-side and one above the other."); - setEnabled(true); - setSelected(isSideBySide); - MenuData menuData = new MenuData(new String[] { "Show Listings Side-by-Side" }, - DUAL_LISTING_ACTION_GROUP); - setMenuBarData(menuData); - - setHelpLocation( - new HelpLocation(DUAL_LISTING_HELP_TOPIC, "Dual Listing Toggle Orientation")); - } - - @Override - public void actionPerformed(ActionContext context) { - boolean sideBySide = !isSideBySide(); - showSideBySide(sideBySide); - } - } - - class ToggleHoverAction extends ToggleDockingAction { - ToggleHoverAction() { - super("Dual Listing Toggle Mouse Hover Popups", owner); - setEnabled(true); - setToolBarData(new ToolBarData(HOVER_ON_ICON, HOVER_GROUP)); - setSelected(true); - - setHelpLocation(new HelpLocation(DUAL_LISTING_HELP_TOPIC, - "Dual Listing Toggle Mouse Hover Popups")); - setHover(true); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - return isShowing(); - } - - @Override - public void actionPerformed(ActionContext context) { - setHover(isSelected()); - } - - void setHover(boolean enabled) { - getToolBarData().setIcon(enabled ? HOVER_ON_ICON : HOVER_OFF_ICON); - setHoverEnabled(enabled); - } - } - - private void setHoverEnabled(boolean enabled) { - listingPanels[LEFT].setHoverMode(enabled); - listingPanels[RIGHT].setHoverMode(enabled); - } - - private boolean isValidPanelContext(ActionContext context) { - CodeComparisonPanel displayedPanel = null; - if (context instanceof CodeComparisonPanelActionContext) { - CodeComparisonPanelActionContext compareContext = - (CodeComparisonPanelActionContext) context; - CodeComparisonPanel codeComparisonPanel = - compareContext.getCodeComparisonPanel(); - if (codeComparisonPanel == this) { - displayedPanel = codeComparisonPanel; - } - } - if (displayedPanel != ListingCodeComparisonPanel.this) { - return false; - } - ListingCodeComparisonPanel dualListingPanel = (ListingCodeComparisonPanel) displayedPanel; - ListingPanel leftPanel = dualListingPanel.getLeftPanel(); - ListingPanel rightPanel = dualListingPanel.getRightPanel(); - - Object sourceObject = context.getSourceObject(); - if (sourceObject instanceof ListingPanel) { - ListingPanel listingPanel = (ListingPanel) sourceObject; - return listingPanel == leftPanel || listingPanel == rightPanel; - } - return true; - } - - private void nextAreaDiff(String currentUserData, boolean forward) { - boolean leftHasFocus = (currProgramIndex == LEFT); - ListingPanel focusPanel = getFocusedListingPanel(); - ProgramLocation focusLocation = focusPanel.getProgramLocation(); - if (focusLocation == null) { - tool.setStatusInfo("The " + (leftHasFocus ? "first" : "second") + " listing is empty."); - return; - } - Address focusAddress = focusLocation.getAddress(); - - ArrayList iteratorList = new ArrayList<>(); - - if (currentUserData.equals(ALL_AREA_MARKERS) || currentUserData.equals(DIFF_AREA_MARKERS)) { - // DIFF Area Markers - AddressSetView focusDiffs = - leftHasFocus ? listingDiff.getListing1Diffs() : listingDiff.getListing2Diffs(); - iteratorList.add(focusDiffs.getAddressRanges(focusAddress, forward)); - } - if (currentUserData.equals(ALL_AREA_MARKERS) || - currentUserData.equals(UNMATCHED_AREA_MARKERS)) { - // UNMATCHED CODE Area Markers - AddressSetView unmatchedCode = leftHasFocus ? listingDiff.getListing1UnmatchedCode() - : listingDiff.getListing2UnmatchedCode(); - iteratorList.add(unmatchedCode.getAddressRanges(focusAddress, forward)); - } - - MultiAddressRangeIterator multiIterator = new MultiAddressRangeIterator( - iteratorList.toArray(new AddressRangeIterator[iteratorList.size()]), forward); - - if (multiIterator.hasNext()) { - AddressRange nextRange = multiIterator.next(); - Address minAddress = nextRange.getMinAddress(); - if ((forward ? nextRange.contains(focusAddress) : minAddress.equals(focusAddress)) && - multiIterator.hasNext()) { - nextRange = multiIterator.next(); - minAddress = nextRange.getMinAddress(); - } - if (minAddress.equals(focusAddress)) { - outputNoNextPreviousMessage(forward, leftHasFocus); - return; - } - tool.clearStatusInfo(); - focusPanel.goTo(minAddress); - } - else { - outputNoNextPreviousMessage(forward, leftHasFocus); - } - } - - private void outputNoNextPreviousMessage(boolean forward, boolean isFirstListing) { - tool.setStatusInfo("There isn't another " + (forward ? "next " : "previous ") + - getCurrentAreaMarkerType().toLowerCase() + " area in the " + - (isFirstListing ? "first" : "second") + " listing."); - } - - private String getCurrentAreaMarkerType() { - String type = "Highlighted"; - if (nextPreviousAreaType.equals(UNMATCHED_AREA_MARKERS)) { - type = "Unmatched"; - } - else if (nextPreviousAreaType.equals(DIFF_AREA_MARKERS)) { - type = "Difference"; - } - return type; - } - - class NextPreviousAreaMarkerAction extends MultiStateDockingAction { - - public NextPreviousAreaMarkerAction(String owner) { - super("Dual Listing Next/Previous Area Marker", owner); - - ToolBarData toolBarData = new ToolBarData(DIFF_ICON, DIFF_NAVIGATE_GROUP); - setToolBarData(toolBarData); - - HelpLocation helpLocation = - new HelpLocation(DUAL_LISTING_HELP_TOPIC, "Dual Listing Next/Previous Area Marker"); - setHelpLocation(helpLocation); - setDescription("Set Navigate Next/Previous Area Marker options"); - - ActionState allAreaMarkers = - new ActionState<>(ALL_AREA_MARKERS, BOTH_VIEWS_ICON, ALL_AREA_MARKERS); - allAreaMarkers.setHelpLocation(helpLocation); - ActionState unmatchedAreaMarkers = - new ActionState<>(UNMATCHED_AREA_MARKERS, UNMATCHED_ICON, UNMATCHED_AREA_MARKERS); - unmatchedAreaMarkers.setHelpLocation(helpLocation); - ActionState diffAreaMarkers = - new ActionState<>(DIFF_AREA_MARKERS, DIFF_ICON, DIFF_AREA_MARKERS); - diffAreaMarkers.setHelpLocation(helpLocation); - - addActionState(allAreaMarkers); - addActionState(unmatchedAreaMarkers); - addActionState(diffAreaMarkers); - - setCurrentActionState(allAreaMarkers); // default - adjustNextPreviousAreaType(); - } - - private void adjustNextPreviousAreaType() { - nextPreviousAreaType = getCurrentUserData(); - nextDiffAction.setMenuString(); - previousDiffAction.setMenuString(); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - return isShowing() && listingDiff.hasCorrelation(); - } - - @Override - public void actionStateChanged(ActionState newActionState, EventTrigger trigger) { - adjustNextPreviousAreaType(); - } - - public void refresh() { - actionStateChanged(getCurrentState(), (EventTrigger) null); - } - } - - class NextDiffAction extends DockingAction { - - NextDiffAction() { - super("Dual Listing Go To Next Area Marker", owner); - setEnabled(true); - setKeyBindingData(new KeyBindingData('N', - DockingUtils.CONTROL_KEY_MODIFIER_MASK | InputEvent.ALT_DOWN_MASK)); - setDescription("Go to the next highlighted area."); - setPopupMenuData(new MenuData(new String[] { "Go To Next Highlighted Area" }, - NEXT_DIFF_ICON, DIFF_NAVIGATE_GROUP)); - ToolBarData newToolBarData = new ToolBarData(NEXT_DIFF_ICON, DIFF_NAVIGATE_GROUP); - setToolBarData(newToolBarData); - - HelpLocation helpLocation = new HelpLocation(DUAL_LISTING_HELP_TOPIC, - "Dual Listing Go To Next Highlighted Area"); - setHelpLocation(helpLocation); - setEnabled(true); - } - - @Override - public boolean isValidContext(ActionContext context) { - return isValidPanelContext(context); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - return isShowing() && listingDiff.hasCorrelation(); - } - - @Override - public void actionPerformed(ActionContext context) { - if (isValidContext(context)) { - nextAreaDiff(nextPreviousAreaType, true); - } - } - - void setMenuString() { - String type = getCurrentAreaMarkerType(); - setPopupMenuData(new MenuData(new String[] { "Go To Next " + type + " Area" }, - NEXT_DIFF_ICON, DIFF_NAVIGATE_GROUP)); - setDescription("Go to the next " + type.toLowerCase() + " area."); - } - } - - class PreviousDiffAction extends DockingAction { - - PreviousDiffAction() { - super("Dual Listing Go To Previous Area Marker", owner); - setEnabled(true); - setKeyBindingData(new KeyBindingData('P', - DockingUtils.CONTROL_KEY_MODIFIER_MASK | InputEvent.ALT_DOWN_MASK)); - setDescription("Go to the previous highlighted area."); - setPopupMenuData(new MenuData(new String[] { "Go To Previous Highlighted Area" }, - PREVIOUS_DIFF_ICON, DIFF_NAVIGATE_GROUP)); - ToolBarData newToolBarData = new ToolBarData(PREVIOUS_DIFF_ICON, DIFF_NAVIGATE_GROUP); - setToolBarData(newToolBarData); - - HelpLocation helpLocation = new HelpLocation(DUAL_LISTING_HELP_TOPIC, - "Dual Listing Go To Previous Highlighted Area"); - setHelpLocation(helpLocation); - setEnabled(true); - } - - @Override - public boolean isValidContext(ActionContext context) { - return isValidPanelContext(context); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - return isShowing() && listingDiff.hasCorrelation(); - } - - @Override - public void actionPerformed(ActionContext context) { - if (isValidContext(context)) { - nextAreaDiff(nextPreviousAreaType, false); - } - } - - void setMenuString() { - String type = getCurrentAreaMarkerType(); - setPopupMenuData(new MenuData(new String[] { "Go To Previous " + type + " Area" }, - PREVIOUS_DIFF_ICON, DIFF_NAVIGATE_GROUP)); - setDescription("Go to the previous " + type.toLowerCase() + " area."); - } - } - - class ListingCodeComparisonOptionsAction extends DockingAction { - - ListingCodeComparisonOptionsAction() { - super("Listing Code Comparison Options", owner); - setEnabled(true); - setDescription("Show the tool options for the Listing Code Comparison."); - setPopupMenuData(new MenuData(new String[] { "Properties" }, null, PROPERTIES_GROUP)); - setHelpLocation( - new HelpLocation(DUAL_LISTING_HELP_TOPIC, "Listing_Code_Comparison_Options")); - setEnabled(true); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - return isShowing() && listingDiff.hasCorrelation(); - } - - @Override - public boolean isValidContext(ActionContext context) { - return isValidPanelContext(context); - } - - @Override - public void actionPerformed(ActionContext context) { - OptionsService service = tool.getService(OptionsService.class); - service.showOptionsDialog(ListingCodeComparisonOptions.OPTIONS_CATEGORY_NAME, - "Listing Code Comparison"); - } - } - - public boolean isEntireListingShowing() { - return isShowingEntireListing; - } - /** - * Sets whether or not the entire programs are displayed in the listings or only the addresses - * in the limited set. - * - * @param show if true, the entire program will be shown. Otherwise the listings will only show - * the limited addresses. - */ - public void showEntireListing(boolean show) { - try { - fieldLocationChanging = true; - - isShowingEntireListing = show; - - ProgramLocation leftLocation = listingPanels[LEFT].getProgramLocation(); - ProgramLocation rightLocation = listingPanels[RIGHT].getProgramLocation(); - if (show) { - loadEntirePrograms(); - } - else { - loadLimitedAddresses(); - } - if (leftLocation != null) { - listingPanels[LEFT].goTo(leftLocation); - } - if (rightLocation != null) { - listingPanels[RIGHT].goTo(rightLocation); - } - } - finally { - fieldLocationChanging = false; - } - } - - /** - * Determines if the listing's layout field header is currently showing. - * - * @return true if the header is showing. - */ - public boolean isHeaderShowing() { - return getLeftPanel().isHeaderShowing(); - } - - /** - * Shows or hides the listing's layout field header. - * - * @param show true means show the field header. false means hide the header. - */ - public void setHeaderShowing(boolean show) { - ListingPanel listingPanel = getLeftPanel(); - boolean isShowing = listingPanel.isHeaderShowing(); - if (show == isShowing) { - return; - } - listingPanel.showHeader(show); - toggleHeaderAction.setSelected(show); - } - - public boolean isSideBySide() { - return isSideBySide; - } - - /** - * Sets whether or not the listings are displayed side by side. - * - * @param sideBySide if true, the listings are side by side, otherwise one is above the other. - */ - public void showSideBySide(boolean sideBySide) { - isSideBySide = sideBySide; - splitPane.setOrientation( - isSideBySide ? JSplitPane.HORIZONTAL_SPLIT : JSplitPane.VERTICAL_SPLIT); - splitPane.setDividerLocation(0.5); - toggleOrientationAction.setSelected(sideBySide); - } - - private void loadEntirePrograms() { - AddressSetView leftSet = - (programs[LEFT] != null) ? programs[LEFT].getMemory() : EMPTY_ADDRESS_SET; - AddressSetView rightSet = - (programs[RIGHT] != null) ? programs[RIGHT].getMemory() : EMPTY_ADDRESS_SET; - ListingComparisonFieldPanelCoordinator fieldPanelCoordinator = getFieldPanelCoordinator(); - if (fieldPanelCoordinator != null) { - fieldPanelCoordinator.resetLockedLines(); - } - listingPanels[LEFT].setView(leftSet); - listingPanels[RIGHT].setView(rightSet); - } - - private void loadLimitedAddresses() { - AddressSetView leftSet = - (addressSets[LEFT] != null) ? addressSets[LEFT] : EMPTY_ADDRESS_SET; - AddressSetView rightSet = - (addressSets[RIGHT] != null) ? addressSets[RIGHT] : EMPTY_ADDRESS_SET; - ListingComparisonFieldPanelCoordinator fieldPanelCoordinator = getFieldPanelCoordinator(); - if (fieldPanelCoordinator != null) { - fieldPanelCoordinator.resetLockedLines(); - } - listingPanels[LEFT].setView(leftSet); - listingPanels[RIGHT].setView(rightSet); - } - - @Override - public void loadFunctions(Function leftFunction, Function rightFunction) { - setFunctions(leftFunction, rightFunction); - } - - /** - * Gets the function loaded in the left listing panel. - * - * @return the function or null - */ - @Override - public Function getLeftFunction() { - return functions[LEFT]; - } - - /** - * Gets the function loaded in the right listing panel. - * - * @return the function or null - */ - @Override - public Function getRightFunction() { - return functions[RIGHT]; - } - - private void setFunctions(Function leftFunction, Function rightFunction) { - - if (leftFunction != functions[LEFT] || rightFunction != functions[RIGHT]) { - clearMarkers(); - - Program leftProgram = - ((leftFunction != null) ? leftFunction.getProgram() : programs[LEFT]); - Program rightProgram = - ((rightFunction != null) ? rightFunction.getProgram() : programs[RIGHT]); - setPrograms(leftProgram, rightProgram); - // Adjust the data and functions only after the correct programs are set. - data[LEFT] = null; - data[RIGHT] = null; - functions[LEFT] = leftFunction; - functions[RIGHT] = rightFunction; - setFunctionTitles(); - } - doLoadFunctions(leftFunction, rightFunction, TaskMonitor.DUMMY); - if (leftFunction == null || rightFunction == null) { - correlator = null; // Clear the correlation. Need 2 functions for a correlation. - } - try { - listingDiff.setCorrelation(correlator); - // Setting the correlation will also reset the locked line numbers. - ListingComparisonFieldPanelCoordinator fieldPanelCoordinator = - getFieldPanelCoordinator(); - if (fieldPanelCoordinator != null) { - fieldPanelCoordinator.setCorrelation(correlator); - } - } - catch (MemoryAccessException e) { - String leftName = (leftFunction != null) ? leftFunction.getName() : "No Function"; - String rightName = (rightFunction != null) ? rightFunction.getName() : "No Function"; - Msg.error(this, "Failed to load functions, " + leftName + " and " + rightName + - " , into dual listing panel. " + e.getMessage(), e); - } - loadCursorArrow(); - updateActionEnablement(); - } - - /** - * Establishes the location and display of the arrow cursor. This method should be called after - * the function comparison window is loaded with functions, data, etc. - */ - private void loadCursorArrow() { - int focusedSide = currProgramIndex; - boolean leftHasFocus = (focusedSide == LEFT); - int nonFocusedSide = leftHasFocus ? RIGHT : LEFT; - ProgramLocation focusedProgramLocation = listingPanels[focusedSide].getProgramLocation(); - ProgramLocation nonFocusedProgramLocation = null; - if (focusedProgramLocation != null) { - nonFocusedProgramLocation = - getProgramLocation((leftHasFocus ? RIGHT : LEFT), focusedProgramLocation); - } - - if (focusedProgramLocation != null) { - setCursorMarkers(focusedSide, focusedProgramLocation); - } - else { - //remove obsolete cursor background highlight for cursor on focused side - listingPanels[focusedSide].getFieldPanel().repaint(); - } - - if (nonFocusedProgramLocation != null) { - setCursorMarkers(nonFocusedSide, nonFocusedProgramLocation); - } - else { - //remove obsolete cursor background highlight for cursor on non-focused side - listingPanels[nonFocusedSide].getFieldPanel().repaint(); - } - } - - /** - * Gets an equivalent left side program location when given a right side program location or - * vice versa. The intent of this method is to translate a location from one side of the dual - * listing to an equivalent location for the other side if possible. - * - * @param leftOrRight LEFT or RIGHT indicating which side's program location is needed. - * @param programLocation the program location for the other side. - * @return a program location for the desired side. Otherwise, null. - */ - private ProgramLocation getProgramLocation(int leftOrRight, ProgramLocation programLocation) { - if (programLocation == null) { - return null; - } - if (programLocation instanceof VariableLocation) { - return getVariableLocation(leftOrRight, (VariableLocation) programLocation); - } - - SaveState saveState = new SaveState(); - programLocation.saveState(saveState); - Address address = programLocation.getAddress(); - - // Try to get the indicated side's address using one of the address correlators. - Address desiredAddress = getAddress(leftOrRight, address); - if (desiredAddress == null || desiredAddress == Address.NO_ADDRESS) { - return null; // Couldn't determine the indicated side's address. - } - - saveState.remove("_ADDRESS"); - saveState.putString("_ADDRESS", desiredAddress.toString()); - - Address byteAddress = programLocation.getByteAddress(); - saveState.remove("_BYTE_ADDR"); - Address desiredByteAddress = null; - if (byteAddress != null) { - // Try to get the indicated side's byte address using one of the address - // correlators or by inferring it. - desiredByteAddress = inferDesiredByteAddress(address, desiredAddress, byteAddress, - programLocation.getProgram(), programs[leftOrRight]); - if (desiredByteAddress != null) { - saveState.putString("_BYTE_ADDR", desiredByteAddress.toString()); - } - } - - // Adjust symbol path for labels if it is part of the location. - adjustSymbolPath(saveState, address, desiredAddress, byteAddress, desiredByteAddress, - programLocation.getProgram(), programs[leftOrRight]); - - // ref address can't be used with indicated side so remove it. - saveState.remove("_REF_ADDRESS"); - // Don't know how to find equivalent referenced address for the indicated side, - // so don't put any _REF_ADDRESS back. - - return ProgramLocation.getLocation(programs[leftOrRight], saveState); - } - - private void 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. - } - Symbol[] symbols = program.getSymbolTable().getSymbols(symbolAddress); - if (symbols.length == 0) { - return; // no symbols in program for matching. - } - Symbol[] desiredSymbols = desiredProgram.getSymbolTable().getSymbols(desiredSymbolAddress); - if (desiredSymbols.length == 0) { - return; // no symbols in desiredProgram for matching. - } - - int desiredRow = adjustSymbolRow(saveState, symbols, desiredSymbols); - - int desiredIndex = getDesiredSymbolIndex(desiredSymbols, desiredRow); - - // Now get the desired symbol. - Symbol desiredSymbol = desiredSymbols[desiredIndex]; - SymbolPath symbolPath = getSymbolPath(desiredSymbol); - // Set symbol path for desiredProgram in the save state. - saveState.putStrings("_SYMBOL_PATH", symbolPath.asArray()); - } - - private int adjustSymbolRow(SaveState saveState, Symbol[] symbols, Symbol[] desiredSymbols) { - // For now just try to choose the same label index if more than one. - int row = saveState.getInt("_ROW", 0); - int desiredRow = row; - if ((desiredRow >= desiredSymbols.length) || // desiredRow is beyond last one so set to last row. - (isFunctionCompare() && (row == (symbols.length - 1)))) { // row is function so set to last row. - - desiredRow = desiredSymbols.length - 1; - } - saveState.remove("_ROW"); - saveState.putInt("_ROW", desiredRow); - return desiredRow; - } - - private int getDesiredSymbolIndex(Symbol[] desiredSymbols, int desiredRow) { - - boolean hasFunction = desiredSymbols[0].getSymbolType().equals(SymbolType.FUNCTION); - - // Get the array index of the desired symbol. - int desiredIndex = 0; // Default to first entry in array. - if (desiredRow >= 0 && desiredRow < desiredSymbols.length) { - desiredIndex = desiredRow; - } - if (hasFunction) { - // Last row in GUI is also first entry in array. - if (desiredIndex == desiredSymbols.length - 1) { - desiredIndex = 0; // Set to function element. - } - else { - desiredIndex++; // Adjust for function element at start of array. - } - } - return desiredIndex; - } - - private SymbolPath getSymbolPath(Symbol desiredSymbol) { - String label = desiredSymbol.getName(); - Namespace namespace = desiredSymbol.getParentNamespace(); - SymbolPath symbolPath; - if (namespace == null || namespace.isGlobal()) { - symbolPath = new SymbolPath(label); - } - else { - symbolPath = new SymbolPath(new SymbolPath(namespace.getSymbol()), label); - } - return symbolPath; - } - - /** - * Infers a desired byte address based on the specified byteAddress as well as the - * address and desiredAddress that were matched. - * - * @param address matches up with the desiredAddress from the other function/data. - * @param desiredAddress matches up with the address from the other function/data. - * @param byteAddress the byte address that is associated with address - * @param program the program for the address and byteAddress. - * @param desiredProgram the program for the desiredAddress and - * desiredByteAddress. - * @return the desired byte address that matches up with the indicated byteAddress - * or null if it can't be determined. - */ - private Address inferDesiredByteAddress(Address address, Address desiredAddress, - Address byteAddress, Program program, Program desiredProgram) { - - // Functions and Data have their addresses inferred differently. - // Functions can use the address correlator for code units, while internal addresses - // may get matched for Data. - if (isFunctionCompare()) { - return inferDesiredFunctionAddress(address, desiredAddress, byteAddress, program, - desiredProgram); - } - if (isDataCompare()) { - return inferDesiredDataAddress(address, desiredAddress, byteAddress, program, - desiredProgram); - } - return null; - } - - /** - * This infers the desired byte address within Data based on the code units at - * codeUnitAddress and desiredCodeUnitAddress. The inferred address - * will be at an offset from the desiredCodeUnitAddress that is the same distance - * the byteAddress is from the codeUnitAddress. - * - * @param codeUnitAddress matches up with the desiredCodeUnitAddress from the other - * data. - * @param desiredCodeUnitAddress matches up with the codeUnitAddress from the other - * data. - * @param byteAddress the byte address that is associated with codeUnitAddress - * @param program the program for the codeUnitAddress and byteAddress. - * @param desiredProgram the program for the desiredCodeUnitAddress and - * desiredByteAddress. - * @return the desired byte address within the data that matches up with the indicated - * byteAddress or null if it can't be determined. - */ - private Address inferDesiredDataAddress(Address codeUnitAddress, Address desiredCodeUnitAddress, - Address byteAddress, Program program, Program desiredProgram) { - - long offset = byteAddress.subtract(codeUnitAddress); - if (offset == 0) { - return desiredCodeUnitAddress; - } - if (offset > 0) { - CodeUnit codeUnit = program.getListing().getCodeUnitContaining(codeUnitAddress); - CodeUnit desiredCodeUnit = - desiredProgram.getListing().getCodeUnitContaining(desiredCodeUnitAddress); - if (codeUnit != null && desiredCodeUnit != null) { - try { - return desiredCodeUnitAddress.add(offset); - } - catch (AddressOutOfBoundsException e) { - return null; // No matching address. - } - } - } - return null; // No matching address. - } - - /** - * This infers the desired byte address within a function based on the code units at - * address and desiredAddress. If the inferred address would be beyond - * the last byte of the code unit then it will get set to the last byte of the code unit at the - * desiredAddress. - * - * @param address matches up with the desiredAddress from the other function. - * @param desiredAddress matches up with the address from the other function. - * @param byteAddress the byte address that is associated with address - * @param program the program for the address and byteAddress. - * @param desiredProgram the program for the desiredAddress and - * desiredByteAddress. - * @return the desired byte address within the data that matches up with the indicated - * byteAddress or null if it can't be determined. - */ - private Address inferDesiredFunctionAddress(Address address, Address desiredAddress, - Address byteAddress, Program program, Program desiredProgram) { - - long numBytesIntoCodeUnit = byteAddress.subtract(address); - if (numBytesIntoCodeUnit == 0) { - return desiredAddress; - } - if (numBytesIntoCodeUnit > 0) { - CodeUnit codeUnit = program.getListing().getCodeUnitAt(address); - CodeUnit desiredCodeUnit = desiredProgram.getListing().getCodeUnitAt(desiredAddress); - if (codeUnit != null && desiredCodeUnit != null) { - int desiredCodeUnitLength = desiredCodeUnit.getLength(); - if (numBytesIntoCodeUnit < desiredCodeUnitLength) { - // Position at byte within code unit. - return desiredAddress.add(numBytesIntoCodeUnit); - } - // Otherwise position at last byte of code unit. - return desiredAddress.add(desiredCodeUnitLength - 1); - } - } - return null; - } - - /** - * Gets an equivalent left side variable location when given a right side variable location or - * vice versa. The intent of this method is to translate a variable location from one side of - * the dual listing to an equivalent variable location for the other side if possible. - * - * @param leftOrRight LEFT or RIGHT indicating which side's variable location is needed. - * @param variableLocation the variable location for the other side. - * @return a variable location for the desired side. Otherwise, null. - */ - private ProgramLocation getVariableLocation(int leftOrRight, - VariableLocation variableLocation) { - if (variableLocation == null) { - return null; - } - SaveState saveState = new SaveState(); - variableLocation.saveState(saveState); - Address address = variableLocation.getAddress(); - Address byteAddress = variableLocation.getByteAddress(); - Address functionAddress = variableLocation.getFunctionAddress(); - - // Try to get the indicated side's address using one of the address correlators. - Address desiredAddress = getAddress(leftOrRight, address); - if (desiredAddress == null || desiredAddress == Address.NO_ADDRESS) { - return null; // Couldn't determine the indicated side's address. - } - - // Try to use a byte address. - Address desiredByteAddress = null; - if (byteAddress != null) { - desiredByteAddress = getAddress(leftOrRight, byteAddress); - } - - Address desiredFunctionAddress = null; - if (functionAddress != null) { - desiredFunctionAddress = getAddress(leftOrRight, functionAddress); - } - if ((desiredFunctionAddress == null) && (functions[leftOrRight] != null)) { - // If this is a thunk function get the thunked address. - Function thunkedFunction = functions[leftOrRight].getThunkedFunction(true); - if (thunkedFunction != null) { - desiredFunctionAddress = thunkedFunction.getEntryPoint(); - } - } - - saveState.remove("_ADDRESS"); - saveState.putString("_ADDRESS", desiredAddress.toString()); - - saveState.remove("_BYTE_ADDR"); - if (desiredByteAddress != null) { - saveState.putString("_BYTE_ADDR", desiredByteAddress.toString()); - } - - saveState.remove("_FUNC_ADDRESS"); - if (desiredFunctionAddress != null) { - saveState.putString("_FUNC_ADDRESS", desiredFunctionAddress.toString()); - } - - // ref address can't be used with indicated side so remove it. - saveState.remove("_REF_ADDRESS"); - // Don't know how to find equivalent referenced address for the indicated side, - // so don't put any _REF_ADDRESS back. - - return ProgramLocation.getLocation(programs[leftOrRight], saveState); - } - - private void clearMarkers() { - clearUnmatchedCodeMarkers(); - clearDiffMarkers(); - clearCursorMarkers(); - } - - private void clearUnmatchedCodeMarkers() { - if (unmatchedCodeMarkers[LEFT] != null) { - unmatchedCodeMarkers[LEFT].clearAll(); - } - if (unmatchedCodeMarkers[RIGHT] != null) { - unmatchedCodeMarkers[RIGHT].clearAll(); - } - } - - private void clearDiffMarkers() { - if (diffMarkers[LEFT] != null) { - diffMarkers[LEFT].clearAll(); - } - if (diffMarkers[RIGHT] != null) { - diffMarkers[RIGHT].clearAll(); - } - } - - private void setCursorMarkers(int leftOrRight, ProgramLocation location) { - if (currentCursorMarkers[leftOrRight] != null) { - currentCursorMarkers[leftOrRight].clearAll(); - if (location != null) { - currentCursorMarkers[leftOrRight].add(location.getAddress()); - } - } - } - - private void clearCursorMarkers() { - if (currentCursorMarkers[LEFT] != null) { - currentCursorMarkers[LEFT].clearAll(); - - } - if (currentCursorMarkers[RIGHT] != null) { - currentCursorMarkers[RIGHT].clearAll(); - } - } - - private void setFunctionTitles() { - setLeftTitle(getFunctionTitle(functions[LEFT])); - setRightTitle(getFunctionTitle(functions[RIGHT])); - } - - private String getFunctionTitle(Function function) { - if (function == null) { - return "none"; - } - StringBuffer buf = new StringBuffer(); - String padStr = HTMLUtilities.spaces(4); - buf.append(padStr); - - String functionStr = HTMLUtilities.friendlyEncodeHTML(function.getName(true) + "()"); - String specialFunctionStr = HTMLUtilities.bold(functionStr); - buf.append(specialFunctionStr); - - Program program = function.getProgram(); - if (program != null) { - buf.append(" in "); - - String programStr = - HTMLUtilities.friendlyEncodeHTML(program.getDomainFile().getPathname()); - String specialProgramStr = HTMLUtilities.colorString(FG_COLOR_TITLE, programStr); - buf.append(specialProgramStr); - buf.append(padStr); - } - return HTMLUtilities.wrapAsHTML(buf.toString()); - } - - private void setDataTitles() { - setLeftTitle(getDataTitle(data[LEFT])); - setRightTitle(getDataTitle(data[RIGHT])); - } - - private String getDataTitle(Data currentData) { - if (currentData == null) { - return "none"; - } - StringBuffer buf = new StringBuffer(); - String padStr = HTMLUtilities.spaces(4); - buf.append(padStr); - - String dataLabel = currentData.getLabel(); - if (dataLabel == null) { // If we can't get a label for the data then use the address . - Address address = currentData.getAddress(); - dataLabel = address.toString(); - } - String dataStr = HTMLUtilities.friendlyEncodeHTML(dataLabel); - String specialDataStr = HTMLUtilities.bold(dataStr); - buf.append(specialDataStr); - - Program program = currentData.getProgram(); - if (program != null) { - buf.append(" in "); - - String programStr = - HTMLUtilities.friendlyEncodeHTML(program.getDomainFile().getPathname()); - String specialProgramStr = HTMLUtilities.colorString(FG_COLOR_TITLE, programStr); - buf.append(specialProgramStr); - buf.append(padStr); - } - return HTMLUtilities.wrapAsHTML(buf.toString()); - } - - private void setAddressesTitles() { - setLeftTitle(getAddressesTitle(programs[LEFT], addressSets[LEFT])); - setRightTitle(getAddressesTitle(programs[RIGHT], addressSets[RIGHT])); - } - - private String getAddressesTitle(Program program, AddressSetView addresses) { - if (program == null) { - return "none"; - } - StringBuffer buf = new StringBuffer(); - String padStr = HTMLUtilities.spaces(4); - buf.append(padStr); - String programStr = HTMLUtilities.friendlyEncodeHTML(program.getDomainFile().getPathname()); - String specialProgramStr = HTMLUtilities.colorString(FG_COLOR_TITLE, programStr); - buf.append(specialProgramStr); - buf.append(padStr); - return HTMLUtilities.wrapAsHTML(buf.toString()); - } - - private void setDiffHighlights() { - setFunctionComparisonDiffHighlights(); - setUnmatchedCodeUnitAreaMarkers(); - setDiffAreaMarkers(); - } - - private void setFunctionComparisonDiffHighlights() { - // Remove old highlight providers if they exist. - removeHighlightProviders(leftDiffHighlightProvider, rightDiffHighlightProvider); - - leftDiffHighlightProvider = - new ListingDiffHighlightProvider(listingDiff, true, comparisonOptions); - rightDiffHighlightProvider = - new ListingDiffHighlightProvider(listingDiff, false, comparisonOptions); - addHighlightProviders(leftDiffHighlightProvider, rightDiffHighlightProvider); - } - - private void setDiffAreaMarkers() { - Color codeUnitDiffsBackgroundColor = comparisonOptions.getDiffCodeUnitsBackgroundColor(); - AddressSetView listing1Diffs = listingDiff.getListing1Diffs(); - AddressSetView listing2Diffs = listingDiff.getListing2Diffs(); - if (diffMarkers[LEFT] != null) { - diffMarkers[LEFT].setMarkerColor(codeUnitDiffsBackgroundColor); - diffMarkers[LEFT].clearAll(); - diffMarkers[LEFT].add(listing1Diffs); - listingPanels[LEFT].getFieldPanel().repaint(); - } - if (diffMarkers[RIGHT] != null) { - diffMarkers[RIGHT].setMarkerColor(codeUnitDiffsBackgroundColor); - diffMarkers[RIGHT].clearAll(); - diffMarkers[RIGHT].add(listing2Diffs); - listingPanels[RIGHT].getFieldPanel().repaint(); - } - } - - private void setUnmatchedCodeUnitAreaMarkers() { - Color unmatchedCodeUnitsBackgroundColor = - comparisonOptions.getUnmatchedCodeUnitsBackgroundColor(); - AddressSetView listing1UnmatchedCode = listingDiff.getListing1UnmatchedCode(); - AddressSetView listing2UnmatchedCode = listingDiff.getListing2UnmatchedCode(); - if (unmatchedCodeMarkers[LEFT] != null) { - unmatchedCodeMarkers[LEFT].setMarkerColor(unmatchedCodeUnitsBackgroundColor); - unmatchedCodeMarkers[LEFT].clearAll(); - unmatchedCodeMarkers[LEFT].add(listing1UnmatchedCode); - listingPanels[LEFT].getFieldPanel().repaint(); - } - if (unmatchedCodeMarkers[RIGHT] != null) { - unmatchedCodeMarkers[RIGHT].setMarkerColor(unmatchedCodeUnitsBackgroundColor); - unmatchedCodeMarkers[RIGHT].clearAll(); - unmatchedCodeMarkers[RIGHT].add(listing2UnmatchedCode); - listingPanels[RIGHT].getFieldPanel().repaint(); - } - } - - private void setupMarkerManagers() { - setupMarkerManager(LEFT); - setupMarkerManager(RIGHT); - } - - private void setupMarkerManager(int leftOrRight) { - if (markerManagers[leftOrRight] == null) { - markerManagers[leftOrRight] = - new DualListingMarkerManager(owner, tool, dualListingServiceProviders[leftOrRight]); - - // setup a marker change listener that can repaint the listing as markers are enabled/disabled. - markerManagers[leftOrRight].addChangeListener(new MarkerChangeListener(leftOrRight)); - - // Set up the marker margin that is on the left side of the listing. - MarginProvider marginProvider = markerManagers[leftOrRight].getMarginProvider(); - JComponent providerComp = marginProvider.getComponent(); - DualListingMouseListener providerMouseListener = - new DualListingMouseListener(leftOrRight); - providerComp.addMouseListener(providerMouseListener); - listingPanels[leftOrRight].addMarginProvider(marginProvider); - - // Set up the overview margin that is on the right side of the listing. - OverviewProvider overviewProvider = markerManagers[leftOrRight].getOverviewProvider(); - JComponent overviewComp = overviewProvider.getComponent(); - DualListingMouseListener overviewMouseListener = - new DualListingMouseListener(leftOrRight); - overviewComp.addMouseListener(overviewMouseListener); - listingPanels[leftOrRight].addOverviewProvider(overviewProvider); - } - } - - private void setupAreaMarkerSets() { - Color diffCodeUnitsBackgroundColor = comparisonOptions.getDiffCodeUnitsBackgroundColor(); - Color unmatchedCodeUnitsBackgroundColor = - comparisonOptions.getUnmatchedCodeUnitsBackgroundColor(); - if (programs[LEFT] != null) { - AddressIndexMap indexMap = listingPanels[LEFT].getAddressIndexMap(); - listingPanels[LEFT].getFieldPanel() - .setBackgroundColorModel(new MarkerServiceBackgroundColorModel( - markerManagers[LEFT], programs[LEFT], indexMap)); - unmatchedCodeMarkers[LEFT] = - markerManagers[LEFT].createAreaMarker("Listing1 Unmatched Code", - "Instructions that are not matched to an instruction in the other function.", - programs[LEFT], MarkerService.DIFF_PRIORITY, true, true, true, - unmatchedCodeUnitsBackgroundColor); - diffMarkers[LEFT] = markerManagers[LEFT].createAreaMarker("Listing1 Diffs", - "Instructions that have a difference.", programs[LEFT], MarkerService.DIFF_PRIORITY, - true, true, true, diffCodeUnitsBackgroundColor); - } - if (programs[RIGHT] != null) { - AddressIndexMap rightIndexMap = listingPanels[RIGHT].getAddressIndexMap(); - listingPanels[RIGHT].getFieldPanel() - .setBackgroundColorModel(new MarkerServiceBackgroundColorModel( - markerManagers[RIGHT], programs[RIGHT], rightIndexMap)); - unmatchedCodeMarkers[RIGHT] = - markerManagers[RIGHT].createAreaMarker("Listing2 Unmatched Code", - "Instructions that are not matched to an instruction in the other function.", - programs[RIGHT], MarkerService.DIFF_PRIORITY, true, true, true, - unmatchedCodeUnitsBackgroundColor); - diffMarkers[RIGHT] = markerManagers[RIGHT].createAreaMarker("Listing2 Diffs", - "Instructions that have a difference.", programs[RIGHT], - MarkerService.DIFF_PRIORITY, true, true, true, diffCodeUnitsBackgroundColor); - - } - } - - private void setupCursorMarkerSets() { - if (programs[LEFT] != null) { - currentCursorMarkers[LEFT] = markerManagers[LEFT].createPointMarker("Cursor", - "Cursor Location", programs[LEFT], MarkerService.FUNCTION_COMPARE_CURSOR_PRIORITY, - true, true, true, cursorHighlightColor, CURSOR_LOC_ICON, false); - } - if (programs[RIGHT] != null) { - currentCursorMarkers[RIGHT] = markerManagers[RIGHT].createPointMarker("Cursor", - "Cursor Location", programs[RIGHT], MarkerService.FUNCTION_COMPARE_CURSOR_PRIORITY, - true, true, true, cursorHighlightColor, CURSOR_LOC_ICON, false); - } - } - - private void doLoadFunctions(Function leftFunction, Function rightFunction, - TaskMonitor monitor) { - try { - fieldLocationChanging = true; - updateLeftAddressSet(leftFunction); - updateRightAddressSet(rightFunction); - try { - correlator = - new HashedFunctionAddressCorrelation(leftFunction, rightFunction, monitor); - } - catch (CancelledException e) { - correlator = null; - } - catch (MemoryAccessException e) { - correlator = null; - } - if (isShowingEntireListing) { - loadEntirePrograms(); - } - else { - loadLimitedAddresses(); - } - goToLeftFunction(leftFunction); - goToRightFunction(rightFunction); - validate(); - } - finally { - fieldLocationChanging = false; - } - } - - private void goToLeftFunction(Function leftFunction) { - if (leftFunction != null && !adjustingLeftLocation) { - try { - adjustingLeftLocation = true; - - listingPanels[LEFT].goTo(new FunctionSignatureFieldLocation( - leftFunction.getProgram(), leftFunction.getEntryPoint(), null, 0, - leftFunction.getPrototypeString(false, false))); - } - finally { - adjustingLeftLocation = false; - } - } - } - - private void goToRightFunction(Function rightFunction) { - if (rightFunction != null && !adjustingRightLocation) { - try { - adjustingRightLocation = true; - - listingPanels[RIGHT].goTo(new FunctionSignatureFieldLocation( - rightFunction.getProgram(), rightFunction.getEntryPoint(), null, 0, - rightFunction.getPrototypeString(false, false))); - } - finally { - adjustingRightLocation = false; - } - } - } - - private void updateLeftAddressSet(Function leftFunction) { - addressSets[LEFT] = (leftFunction != null) ? leftFunction.getBody() : EMPTY_ADDRESS_SET; - - // If we have a function and no addresses, then set the entry point as the address set. - // This allows external functions to be displayed. - if (leftFunction != null && addressSets[LEFT].isEmpty()) { - Address entryPoint = leftFunction.getEntryPoint(); - addressSets[LEFT] = new AddressSet(leftFunction.getProgram(), entryPoint, entryPoint); - } - - indexMaps[LEFT] = new AddressIndexMap(addressSets[LEFT]); - markerManagers[LEFT].getOverviewProvider().setProgram(getLeftProgram(), indexMaps[LEFT]); - listingPanels[LEFT].getFieldPanel() - .setBackgroundColorModel(new MarkerServiceBackgroundColorModel(markerManagers[LEFT], - programs[LEFT], indexMaps[LEFT])); - } - - private void updateRightAddressSet(Function rightFunction) { - addressSets[RIGHT] = (rightFunction != null) ? rightFunction.getBody() : EMPTY_ADDRESS_SET; - - // If we have a function and no addresses, then set the entry point as the address set. - // This allows external functions to be displayed. - if (rightFunction != null && addressSets[RIGHT].isEmpty()) { - Address entryPoint = rightFunction.getEntryPoint(); - addressSets[RIGHT] = new AddressSet(rightFunction.getProgram(), entryPoint, entryPoint); - } - - indexMaps[RIGHT] = new AddressIndexMap(addressSets[RIGHT]); - markerManagers[RIGHT].getOverviewProvider().setProgram(getRightProgram(), indexMaps[RIGHT]); - listingPanels[RIGHT].getFieldPanel() - .setBackgroundColorModel(new MarkerServiceBackgroundColorModel( - markerManagers[RIGHT], programs[RIGHT], indexMaps[RIGHT])); - } - - @Override - public void loadAddresses(Program leftProgram, Program rightProgram, - AddressSetView leftAddresses, AddressSetView rightAddresses) { - - setPrograms(leftProgram, rightProgram); - try { - fieldLocationChanging = true; - addressSets[LEFT] = - (leftProgram != null && leftAddresses != null) ? leftAddresses : EMPTY_ADDRESS_SET; - addressSets[RIGHT] = (rightProgram != null && rightAddresses != null) ? rightAddresses - : EMPTY_ADDRESS_SET; - clearCorrelation(); - if (isShowingEntireListing) { - loadEntirePrograms(); - } - else { - loadLimitedAddresses(); - } - setAddressesTitles(); - if (programs[LEFT] != null && !addressSets[LEFT].isEmpty()) { - listingPanels[LEFT].goTo( - new ProgramLocation(programs[LEFT], addressSets[LEFT].getMinAddress())); - } - if (programs[RIGHT] != null && !addressSets[RIGHT].isEmpty()) { - listingPanels[RIGHT].goTo( - new ProgramLocation(programs[RIGHT], addressSets[RIGHT].getMinAddress())); - } - } - finally { - loadCursorArrow(); - updateActionEnablement(); - fieldLocationChanging = false; - } - } - - /** - * Sets the cursor location in the left and right listing at the specified functions. - * - * @param leftFunction the function in the left listing panel. - * @param rightFunction the function in the right listing panel. - */ - public void setLocation(Function leftFunction, Function rightFunction) { - goToLeftFunction(leftFunction); - goToRightFunction(rightFunction); - } - - /** - * Sets the cursor in the left side's listing to the specified location. - * - * @param program the left side's program + * Sets the cursor for the side to the given location + * @param side The side to goto + * @param program the side's program * @param location the location */ - public void setLeftLocation(Program program, ProgramLocation location) { + public void setLocation(Side side, Program program, ProgramLocation location) { if (isShowing()) { - goToLeftLocation(location); + displays.get(side).goTo(location); } } - /** - * Sets the cursor in the right side's listing to the specified location. - * - * @param program the right side's program - * @param location the location - */ - public void setRightLocation(Program program, ProgramLocation location) { - if (isShowing()) { - goToRightLocation(location); - } - } - - private void goToLeftLocation(ProgramLocation location) { - if (adjustingLeftLocation || location == null) { - return; - } - try { - adjustingLeftLocation = true; - - Address address = location.getAddress(); - if (location instanceof CodeUnitLocation) { - CodeUnit leftCodeUnit = programs[LEFT].getListing().getCodeUnitContaining(address); - if (leftCodeUnit == null) { - return; - } - } - - // Now that we have the correct addresses, position the cursor location. - listingPanels[LEFT].goTo(location, false); - } - finally { - adjustingLeftLocation = false; - } - } - - private void goToRightLocation(ProgramLocation location) { - if (adjustingRightLocation || location == null) { - return; - } - try { - adjustingRightLocation = true; - - Address address = location.getAddress(); - if (location instanceof CodeUnitLocation) { - CodeUnit rightCodeUnit = - programs[RIGHT].getListing().getCodeUnitContaining(address); - if (rightCodeUnit == null) { - return; - } - } - - // Now that we have the correct addresses, position the cursor location. - listingPanels[RIGHT].goTo(location, false); - } - finally { - adjustingRightLocation = false; - } - } - - private void buildPanel() { - setName(DUAL_LISTING_ACTION_GROUP); - setLayout(new BorderLayout()); - // cleanup remnants - if (splitPane != null) { - remove(splitPane); - listingPanels[LEFT].dispose(); - listingPanels[RIGHT].dispose(); - } - - FormatManager leftFormatManager = createFormatManager(LEFT); - leftFormatManager.addFormatModelListener(this); - listingPanels[LEFT] = new ListingPanel(leftFormatManager, programs[LEFT]); - - FormatManager rightFormatManager = createFormatManager(RIGHT); - listingPanels[RIGHT] = new ListingPanel(rightFormatManager, programs[RIGHT]); - - listingPanels[LEFT].setBorder(FOCUS_BORDER); - listingPanels[RIGHT].setBorder(NON_FOCUS_BORDER); - - // Turn off selection in the listings so it can be set up s desired elsewhere. - listingPanels[LEFT].getFieldPanel().enableSelection(false); - listingPanels[RIGHT].getFieldPanel().enableSelection(false); - - String leftProgramName = - (programs[LEFT] != null) ? programs[LEFT].getDomainFile().toString() : "none"; - String rightProgramName = - (programs[RIGHT] != null) ? programs[RIGHT].getDomainFile().toString() : "none"; - - titlePanels[LEFT] = new TitledPanel(leftProgramName, listingPanels[LEFT], 5); - titlePanels[RIGHT] = new TitledPanel(rightProgramName, listingPanels[RIGHT], 5); - - // Set the MINIMUM_PANEL_WIDTH for the left and right panel to prevent the split pane's - // divider from becoming locked (can't be moved) due to extra long title names. - titlePanels[LEFT].setMinimumSize( - new Dimension(MINIMUM_PANEL_WIDTH, titlePanels[LEFT].getMinimumSize().height)); - titlePanels[RIGHT].setMinimumSize( - new Dimension(MINIMUM_PANEL_WIDTH, titlePanels[RIGHT].getMinimumSize().height)); - - splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, titlePanels[LEFT], - titlePanels[RIGHT]); - splitPane.setResizeWeight(0.5); - splitPane.setDividerSize(4); - splitPane.setBorder(BorderFactory.createEmptyBorder()); - add(splitPane, BorderLayout.CENTER); - } - - private void setTitle(TitledPanel titlePanel, String titlePrefix, String title) { - if (!titlePrefix.isEmpty()) { - titlePrefix += " "; // Add a space between prefix and title. - } - String htmlPrefix = ""; - if (title.startsWith(htmlPrefix)) { - titlePanel.setTitleName(htmlPrefix + HTMLUtilities.friendlyEncodeHTML(titlePrefix) + - title.substring(htmlPrefix.length())); - } - else { - titlePanel.setTitleName(titlePrefix + title); - } - } - - /** - * Sets the title for the left side's listing. - * - * @param leftTitle the title - */ - public void setLeftTitle(String leftTitle) { - this.leftTitle = leftTitle; - setTitle(titlePanels[LEFT], leftTitlePrefix, leftTitle); - } - - /** - * Sets the title for the right side's listing. - * - * @param rightTitle the title - */ - public void setRightTitle(String rightTitle) { - this.rightTitle = rightTitle; - setTitle(titlePanels[RIGHT], rightTitlePrefix, rightTitle); - } - - /** - * Sets the component displayed in the top of this panel. - * - * @param comp the component. - */ - public void setTopComponent(JComponent comp) { - if (topComp == comp) { - return; - } - if (topComp != null) { - remove(topComp); - } - topComp = comp; - if (topComp != null) { - add(topComp, BorderLayout.NORTH); - } - validate(); - } - - /** - * Sets the component displayed in the bottom of this panel. - * - * @param comp the component. - */ - public void setBottomComponent(JComponent comp) { - if (bottomComp == comp) { - return; - } - if (bottomComp != null) { - remove(bottomComp); - } - validate(); // Since we are removing this while the panel is on the screen. - bottomComp = comp; - if (bottomComp != null) { - add(bottomComp, BorderLayout.SOUTH); - } - validate(); // Since we are adding this while the panel is on the screen. - } - - /** - * Gets the program from the left or right side that has or last had focus. - * - * @return the program from the side of this panel with focus or null - */ - public Program getFocusedProgram() { - return programs[currProgramIndex]; - } - - /** - * Gets the program in the left listing panel. - * - * @return the left program or null - */ - @Override - public Program getLeftProgram() { - return programs[LEFT]; - } - - /** - * Gets the program in the right listing panel. - * - * @return the right program or null - */ - @Override - public Program getRightProgram() { - return programs[RIGHT]; - } - - /** - * Gets the addresses in the left listing panel. - * - * @return the addresses - */ - @Override - public AddressSetView getLeftAddresses() { - return addressSets[LEFT]; - } - - /** - * Gets the addresses in the right listing panel. - * - * @return the addresses - */ - @Override - public AddressSetView getRightAddresses() { - return addressSets[RIGHT]; - } - - /** - * Get the left or right listing panel that has or last had focus. - * - * @return the listing panel with focus. - */ - public ListingPanel getFocusedListingPanel() { - return listingPanels[currProgramIndex]; - } - - /** - * Get the left side's listing panel. - * - * @return the left panel - */ - public ListingPanel getLeftPanel() { - return listingPanels[LEFT]; - } - - /** - * Get the right side's listing panel. - * - * @return the right panel - */ - public ListingPanel getRightPanel() { - return listingPanels[RIGHT]; - } - - /** - * Go to the indicated address in the listing that last had focus. - * - * @param addr the cursor should go to this address - * @return true if the location changed - */ - public boolean goTo(Address addr) { - return listingPanels[currProgramIndex].goTo(addr); - } - - /** - * Go to the indicated location in the listing that last had focus. - * - * @param loc the cursor should go to this location. - * @param centerOnScreen true indicates that the location should be centered in the listing's - * viewport. - * @return true if the location changed - */ - public boolean goTo(ProgramLocation loc, boolean centerOnScreen) { - return listingPanels[currProgramIndex].goTo(loc, centerOnScreen); + public ListingPanel getActiveListingPanel() { + return displays.get(activeSide).getListingPanel(); } @Override public void dispose() { - setFieldPanelCoordinator(null); - listingDiff.removeListingDiffChangeListener(this); - markerManagers[LEFT].dispose(); - markerManagers[RIGHT].dispose(); - listingPanels[LEFT].getFieldPanel().removeFieldLocationListener(leftLocationListener); - listingPanels[RIGHT].getFieldPanel().removeFieldLocationListener(rightLocationListener); - for (int i = 0; i < 2; i++) { - listingPanels[i].dispose(); - } - } - - @Override - public void focusGained(FocusEvent e) { - Component comp = e.getComponent(); - for (int i = 0; i < listingPanels.length; i++) { - if (listingPanels[i].getFieldPanel() == comp) { - setDualPanelFocus(i); - } - } - - // Kick the tool so action buttons will be updated - if (tool.getActiveComponentProvider() != null) { - tool.getActiveComponentProvider().contextChanged(); - } - } - - private void setDualPanelFocus(int leftOrRight) { - currProgramIndex = leftOrRight; - listingPanels[leftOrRight].setBorder(FOCUS_BORDER); - listingPanels[((leftOrRight == LEFT) ? RIGHT : LEFT)].setBorder(NON_FOCUS_BORDER); + setSynchronizedScrolling(false); + displays.each(ListingDisplay::dispose); } @Override public ActionContext getActionContext(ComponentProvider provider, MouseEvent event) { - ListingCodeComparisonPanel dualListingPanel = this; - if (event == null) { - DualListingActionContext myActionContext = new DualListingActionContext(provider); - myActionContext.setContextObject(this); - myActionContext.setCodeComparisonPanel(this); - return myActionContext; + DualListingActionContext context = new DualListingActionContext(provider, this); + return context; } - ListingPanel leftPanel = dualListingPanel.getLeftPanel(); - ListingPanel rightPanel = dualListingPanel.getRightPanel(); + ListingPanel leftPanel = displays.get(LEFT).getListingPanel(); + ListingPanel rightPanel = displays.get(RIGHT).getListingPanel(); Object leftMarginContext = getContextForMarginPanels(leftPanel, event); if (leftMarginContext != null) { @@ -2103,11 +273,290 @@ public class ListingCodeComparisonPanel return new DefaultActionContext(provider).setContextObject(fieldHeaderLocation); } - DualListingActionContext myActionContext = new DualListingActionContext(provider); - myActionContext.setContextObject(this); - myActionContext.setCodeComparisonPanel(this); - myActionContext.setSourceObject(source); - return myActionContext; + return new DualListingActionContext(provider, this); + } + + /** + * Repaints both the left and right listing panels if they are visible. + */ + public void updateListings() { + if (!isVisible()) { + return; + } + displays.each(ListingDisplay::repaint); + } + + @Override + public void formatModelChanged(FieldFormatModel model) { + changeRightToMatchLeftFormat(model); + } + + /** + * Gets the left or right listing panel that contains the indicated field panel. + * + * @param fieldPanel the field panel + * @return the listing panel or null. + */ + public ListingPanel getListingPanel(FieldPanel fieldPanel) { + ListingPanel listingPanel = displays.get(LEFT).getListingPanel(); + if (listingPanel.getFieldPanel() == fieldPanel) { + return listingPanel; + } + + listingPanel = displays.get(RIGHT).getListingPanel(); + if (listingPanel.getFieldPanel() == fieldPanel) { + return listingPanel; + } + + return null; + } + + /** + * Displays the indicated text int the tool's status area. + * + * @param text the message to display + */ + public void setStatusInfo(String text) { + tool.setStatusInfo(text); + } + + /** + * Gets a marker margin or overview margin context object if the mouse event occurred on one of + * the GUI components for the indicated listing panel's marker margin (left edge of listing) or + * overview margin (right edge of listing). + * + * @param panel The listing panel to check + * @param event the mouse event + * @return a marker margin context object if the event was on a margin. + */ + public Object getContextObjectForMarginPanels(ListingPanel panel, MouseEvent event) { + Object source = event.getSource(); + // Is event source a marker margin provider on the left side of the listing? + List marginProviders = panel.getMarginProviders(); + for (MarginProvider marginProvider : marginProviders) { + JComponent c = marginProvider.getComponent(); + if (c == source) { + MarkerLocation loc = marginProvider.getMarkerLocation(event.getX(), event.getY()); + if (loc != null) { + return loc; // Return the marker margin location that was clicked. + } + return source; // Return the margin provider that was clicked. + } + } + // Is event source an overview provider on the right side of the listing? + List overviewProviders = panel.getOverviewProviders(); + for (OverviewProvider overviewProvider : overviewProviders) { + JComponent c = overviewProvider.getComponent(); + if (c == source) { + return source; // Return the overview provider that was clicked. + } + } + return null; // Not one of the listing panel's margin panels. + } + + @Override + public void setSynchronizedScrolling(boolean synchronize) { + listingsLocked = synchronize; + updateCoordinator(); + } + + /** + * Notification that a program location changed on one side. + * @param side the side that changed + * @param location the new location for the given side + * @param trigger the trigger for the change + */ + private void programLocationChanged(Side side, ProgramLocation location, EventTrigger trigger) { + + // only respond to GUI actions to avoid bouncing + if (trigger != EventTrigger.GUI_ACTION) { + return; + } + displays.get(side).updateCursorMarkers(location); + displays.get(side.otherSide()).updateCursorMarkers(null); + + if (coordinator != null) { + coordinator.setLocation(side, location); + } + + } + + private void createActions() { + nextDiffAction = new ActionBuilder("Dual Listing Go To Next Area Marker", owner) + .description("Go to the next highlighted area.") + .helpLocation( + new HelpLocation(HELP_TOPIC, "Dual Listing Go To Next Highlighted Area")) + .popupMenuPath("Go To Next Highlighted Area") + .popupMenuIcon(NEXT_DIFF_ICON) + .popupMenuGroup(DIFF_NAVIGATE_GROUP) + .toolBarIcon(NEXT_DIFF_ICON) + .toolBarGroup(DIFF_NAVIGATE_GROUP) + .keyBinding("ctrl alt N") + .validContextWhen(c -> isValidPanelContext(c)) + .enabledWhen(c -> isShowing() && listingDiff.hasCorrelation()) + .onAction(c -> nextAreaDiff(true)) + .build(); + + previousDiffAction = new ActionBuilder("Dual Listing Go To Previous Area Marker", owner) + .description("Go to the previous highlighted area.") + .helpLocation( + new HelpLocation(HELP_TOPIC, "Dual Listing Go To Previous Highlighted Area")) + .popupMenuPath("Go To Previous Highlighted Area") + .popupMenuIcon(PREVIOUS_DIFF_ICON) + .popupMenuGroup(DIFF_NAVIGATE_GROUP) + .toolBarIcon(PREVIOUS_DIFF_ICON) + .toolBarGroup(DIFF_NAVIGATE_GROUP) + .keyBinding("ctrl alt P") + .validContextWhen(c -> isValidPanelContext(c)) + .enabledWhen(c -> isShowing() && listingDiff.hasCorrelation()) + .onAction(c -> nextAreaDiff(false)) + .build(); + + toggleHeaderAction = new ToggleActionBuilder("Dual Listing Toggle Header", owner) + .description("Toggle Format Header") + .menuPath("Show Listing Format Header") + .menuGroup("Listing Group") + .helpLocation(new HelpLocation(HELP_TOPIC, "Dual Listing Toggle Format Header")) + .onAction(c -> displays.get(LEFT).showHeader(toggleHeaderAction.isSelected())) + .build(); + + toggleHoverAction = new ToggleActionBuilder("Dual Listing Toggle Mouse Hover Popups", owner) + .description("Toggles Mouse Hover Popups") + .toolBarIcon(HOVER_ON_ICON) + .helpLocation( + new HelpLocation(HELP_TOPIC, "Dual Listing Toggle Mouse Hover Popups")) + .enabledWhen(c -> isShowing()) + .selected(true) + .onAction(c -> setHover(toggleHoverAction.isSelected())) + .build(); + nextPreviousAreaTypeAction = + new MultiStateActionBuilder("Dual Listing Next/Previous Area Marker", + owner) + .description("Set Navigate Next/Previous Area Marker options") + .helpLocation( + new HelpLocation(HELP_TOPIC, "Dual Listing Next/Previous Area Marker")) + .toolBarIcon(DIFF_ICON) + .toolBarGroup(DIFF_NAVIGATE_GROUP) + .addState("All Area Markers", BOTH_VIEWS_ICON, NavigateType.ALL) + .addState("Unmatched Area Markers", UNMATCHED_ICON, NavigateType.UNMATCHED) + .addState("Diff AreaMarkers", DIFF_ICON, NavigateType.DIFF) + .enabledWhen(c -> isShowing() && listingDiff.hasCorrelation()) + .onActionStateChanged((s, t) -> adjustNextPreviousAreaType(s.getUserData())) + .build(); + + optionsAction = new ActionBuilder("Listing Code Comparison Options", owner) + .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)) + .enabledWhen(c -> isShowing() && listingDiff.hasCorrelation()) + .onAction(c -> showOptionsDialog()) + .build(); + + applyFunctionNameAction = new FunctionNameApplyAction(owner); + applyEmptySignatureAction = new EmptySignatureApplyAction(owner); + applySignatureAction = new SignatureWithDatatypesApplyAction(owner); + + } + + private void showOptionsDialog() { + OptionsService service = tool.getService(OptionsService.class); + service.showOptionsDialog(ListingCodeComparisonOptions.OPTIONS_CATEGORY_NAME, + "Listing Code Comparison"); + } + + private void adjustNextPreviousAreaType(NavigateType type) { + String typeString = getTypeName(type); + nextDiffAction.getPopupMenuData() + .setMenuPath(new String[] { "Go to Next " + typeString + " Area" }); + nextDiffAction.setDescription("Go to the next " + typeString + " area"); + previousDiffAction.getPopupMenuData() + .setMenuPath(new String[] { "Go to Previous " + typeString + " Area" }); + previousDiffAction.setDescription("Go to the previous " + typeString + " area"); + + } + + private String getTypeName(NavigateType type) { + switch (type) { + case DIFF: + return "Difference"; + case UNMATCHED: + return "Unmatched"; + case ALL: + return "Highlighted"; + default: + throw new AssertException("Unexpected navigate type" + type); + } + } + + private void setHover(boolean enabled) { + toggleHoverAction.getToolBarData().setIcon(enabled ? HOVER_ON_ICON : HOVER_OFF_ICON); + displays.each(d -> d.setHoverMode(enabled)); + } + + private boolean isValidPanelContext(ActionContext context) { + if (!(context instanceof CodeComparisonPanelActionContext comparisonContext)) { + return false; + } + CodeComparisonPanel comparisonPanel = comparisonContext.getCodeComparisonPanel(); + return comparisonPanel == this; + } + + @Override + protected void comparisonDataChanged() { + addressCorrelator = createCorrelator(); + updateProgramViews(); + updateCoordinator(); + updateListingDiff(); + initializeCursorMarkers(); + updateActionEnablement(); + validate(); + } + + private void updateCoordinator() { + if (coordinator != null) { + coordinator.dispose(); + coordinator = null; + } + if (listingsLocked) { + coordinator = new ListingCoordinator(displays, addressCorrelator); + coordinator.sync(activeSide); + } + } + + 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 updateListingDiff() { + try { + listingDiff.setCorrelation(addressCorrelator); + } + catch (MemoryAccessException e) { + Msg.error(this, "Failed to load correlator", e); + } + } + + /** + * Establishes the location and display of the arrow cursor. This method should be called after + * the function comparison window is loaded with functions, data, etc. + */ + private void initializeCursorMarkers() { + ProgramLocation activeProgramLocation = displays.get(activeSide).getProgramLocation(); + programLocationChanged(activeSide, activeProgramLocation, null); } private Object getContextForMarginPanels(ListingPanel lp, MouseEvent event) { @@ -2133,722 +582,75 @@ public class ListingCodeComparisonPanel return null; } - /** - * Adds the indicated button press listener to both listing panels in this code comparison - * panel. - * - * @param listener the listener - */ - public void addButtonPressedListener(ButtonPressedListener listener) { - for (ListingPanel listingPanel : listingPanels) { - listingPanel.addButtonPressedListener(listener); - } - } - - /** - * Repaints both the left and right listing panels if they are visible. - */ - public void updateListings() { - if (!isVisible()) { - return; - } - listingPanels[LEFT].repaint(); - listingPanels[RIGHT].repaint(); - } - - /** - * Gets the indicated (LEFT or RIGHT) side's address that is equivalent to the other side's - * address. - * - * @param leftOrRight LEFT or RIGHT indicating which side's address is needed. - * @param otherSidesAddress the address for the other side. If leftOrRight = LEFT, then this - * should be a right side address. If leftOrRight = RIGHT, then this should be a left - * side address. - * @return an address for the indicated side (LEFT or RIGHT) that is equivalent to the other - * side's address that is specified. Otherwise, null. - */ - private Address getAddress(int leftOrRight, Address otherSidesAddress) { - if (isFunctionCompare()) { - return getFunctionAddress(leftOrRight, otherSidesAddress); - } - if (isDataCompare()) { - return getDataAddress(leftOrRight, otherSidesAddress); - } - return null; - } - - /** - * Gets an address in the program indicated by leftOrRight that matches the - * otherSidesAddress that is an address in a function in the other program. - * - * @param leftOrRight indicates whether to get the address from the LEFT or RIGHT program. - * @param otherSidesAddress address in the other program that is equivalent to the desired - * address. - * @return the matching address in the indicated program or null. - */ - private Address getFunctionAddress(int leftOrRight, Address otherSidesAddress) { - // Try to get the address using the correlator. - // If the correlator couldn't determine it, then try to infer it. - int otherSide = (leftOrRight == RIGHT) ? LEFT : RIGHT; - // Finding desired side's address. - Address desiredSidesAddress = - (leftOrRight == RIGHT) ? getRightCorrelatedAddress(otherSidesAddress) - : getLeftCorrelatedAddress(otherSidesAddress); - if (desiredSidesAddress != null) { - return desiredSidesAddress; - } - // Couldn't directly correlate the address. - CodeUnit otherCodeUnit = - programs[otherSide].getListing().getCodeUnitContaining(otherSidesAddress); - if (otherCodeUnit == null) { - return null; // Can't get the code unit's address. - } - Address otherCodeUnitAddress = otherCodeUnit.getMinAddress(); - Address desiredCodeUnitAddress = - (leftOrRight == RIGHT) ? getRightCorrelatedAddress(otherCodeUnitAddress) - : getLeftCorrelatedAddress(otherCodeUnitAddress); - if (desiredCodeUnitAddress == null) { - return null; // Can't match the code unit address either. - } - return inferDesiredFunctionAddress(otherCodeUnitAddress, desiredCodeUnitAddress, - otherSidesAddress, programs[otherSide], programs[leftOrRight]); - } - - private Address getDataAddress(int leftOrRight, Address otherSidesAddress) { - // Correlator doesn't handle data compare, so associate beginning of data and - // infer the others based on relative position. - Address leftDataAddress = getLeftDataAddress(); - Address rightDataAddress = getRightDataAddress(); - if (leftOrRight == RIGHT) { - // Finding right side address. - return inferDesiredDataAddress(leftDataAddress, rightDataAddress, otherSidesAddress, - programs[LEFT], programs[RIGHT]); - } - // Finding left side address. - return inferDesiredDataAddress(rightDataAddress, leftDataAddress, otherSidesAddress, - programs[RIGHT], programs[LEFT]); - } - - /** - * Is this panel currently comparing a function match? - * - * @return true if comparing functions. - */ - private boolean isFunctionCompare() { - Address leftFunctionAddress = getLeftFunctionAddress(); - Address rightFunctionAddress = getRightFunctionAddress(); - return (leftFunctionAddress != null && rightFunctionAddress != null); - } - - /** - * Is this panel currently comparing a data match? - * - * @return true if comparing data. - */ - private boolean isDataCompare() { - Address leftDataAddress = getLeftDataAddress(); - Address rightDataAddress = getRightDataAddress(); - return (leftDataAddress != null && rightDataAddress != null); - } - - /** - * Gets the left side address that is equivalent to the indicated right side address. - * - * @param rightByteAddress the right side address - * @return the left side address or null. - */ - private Address getLeftCorrelatedAddress(Address rightByteAddress) { - if (correlator != null) { - return correlator.getAddressInFirst(rightByteAddress); - } - return null; - } - - /** - * Gets the right side address that is equivalent to the indicated left side address. - * - * @param leftByteAddress the left side address - * @return the right side address or null. - */ - private Address getRightCorrelatedAddress(Address leftByteAddress) { - if (correlator != null) { - return correlator.getAddressInSecond(leftByteAddress); - } - return null; - } - - /** - * Gets the left side function's entry point address. - * - * @return the left side function's entry point address or null. - */ - private Address getLeftFunctionAddress() { - if (functions[LEFT] != null) { - return functions[LEFT].getEntryPoint(); - } - return null; - } - - /** - * Gets the right side function's entry point address. - * - * @return the right side function's entry point address or null. - */ - private Address getRightFunctionAddress() { - if (functions[RIGHT] != null) { - return functions[RIGHT].getEntryPoint(); - } - return null; - } - - /** - * Gets the left side data's minimum address. - * - * @return the left side data's minimum address or null. - */ - private Address getLeftDataAddress() { - if (data[LEFT] != null) { - return data[LEFT].getMinAddress(); - } - return null; - } - - /** - * Gets the right side data's minimum address. - * - * @return the right side data's minimum address or null. - */ - private Address getRightDataAddress() { - if (data[RIGHT] != null) { - return data[RIGHT].getMinAddress(); - } - return null; - } - - private class LeftLocationListener implements FieldLocationListener { - - @Override - public void fieldLocationChanged(FieldLocation location, Field field, - EventTrigger trigger) { - if (fieldLocationChanging) { - return; - } - try { - fieldLocationChanging = true; - - ProgramLocation leftProgramLocation = listingPanels[LEFT].getProgramLocation(); - ListingComparisonFieldPanelCoordinator fieldPanelCoordinator = - getFieldPanelCoordinator(); - // Only set other side's cursor if we are coordinating right now. - ProgramLocation rightProgramLocation = - (fieldPanelCoordinator != null) ? getProgramLocation(RIGHT, leftProgramLocation) - : null; - - setCursorMarkers(LEFT, leftProgramLocation); - setCursorMarkers(RIGHT, rightProgramLocation); - if (rightProgramLocation == null) { - //remove obsolete cursor background highlight for right-hand cursor - listingPanels[RIGHT].getFieldPanel().repaint(); - return; - } - - if (fieldPanelCoordinator != null) { - fieldPanelCoordinator.leftLocationChanged(leftProgramLocation); - setRightLocation(getRightProgram(), rightProgramLocation); - } - } - finally { - fieldLocationChanging = false; - } - } - } - - private class RightLocationListener implements FieldLocationListener { - - @Override - public void fieldLocationChanged(FieldLocation location, Field field, - EventTrigger trigger) { - if (fieldLocationChanging) { - return; - } - try { - fieldLocationChanging = true; - - ProgramLocation rightProgramLocation = listingPanels[RIGHT].getProgramLocation(); - ListingComparisonFieldPanelCoordinator fieldPanelCoordinator = - getFieldPanelCoordinator(); - // Only set other side's cursor if we are coordinating right now. - ProgramLocation leftProgramLocation = - (fieldPanelCoordinator != null) ? getProgramLocation(LEFT, rightProgramLocation) - : null; - - setCursorMarkers(RIGHT, rightProgramLocation); - setCursorMarkers(LEFT, leftProgramLocation); - if (leftProgramLocation == null) { - //remove obsolete cursor background highlight for left-hand cursor - listingPanels[LEFT].getFieldPanel().repaint(); - return; - } - - if (fieldPanelCoordinator != null) { - fieldPanelCoordinator.rightLocationChanged(rightProgramLocation); - setLeftLocation(getLeftProgram(), leftProgramLocation); - } - } - finally { - fieldLocationChanging = false; - } - } - } - - @Override - public void formatModelAdded(FieldFormatModel model) { - changeRightToMatchLeftFormat(model); - } - - @Override - public void formatModelChanged(FieldFormatModel model) { - changeRightToMatchLeftFormat(model); - } - - @Override - public void formatModelRemoved(FieldFormatModel model) { - changeRightToMatchLeftFormat(model); + private void initializeOptions() { + comparisonOptions = new ListingCodeComparisonOptions(); + ToolOptions options = tool.getOptions(ListingCodeComparisonOptions.OPTIONS_CATEGORY_NAME); + options.addOptionsChangeListener(this); + comparisonOptions.initializeOptions(options); + comparisonOptions.loadOptions(options); } private void changeRightToMatchLeftFormat(FieldFormatModel model) { SaveState saveState = new SaveState(); - listingPanels[LEFT].getFormatManager().saveState(saveState); - listingPanels[RIGHT].getFormatManager().readState(saveState); + displays.get(LEFT).getFormatManager().saveState(saveState); + displays.get(RIGHT).getFormatManager().readState(saveState); } - /** - * Gets the left or right listing panel that contains the indicated field panel. - * - * @param fieldPanel the field panel - * @return the listing panel or null. - */ - public ListingPanel getListingPanel(FieldPanel fieldPanel) { - if (listingPanels[LEFT].getFieldPanel() == fieldPanel) { - return listingPanels[LEFT]; - } - if (listingPanels[RIGHT].getFieldPanel() == fieldPanel) { - return listingPanels[RIGHT]; - } - return null; + private void updateProgramViews() { + displays.get(LEFT).setProgramView(getProgram(LEFT), getAddresses(LEFT), "listing1"); + displays.get(RIGHT).setProgramView(getProgram(RIGHT), getAddresses(RIGHT), "listing2"); } - @Override - public FormatManager getFormatManager() { - return listingPanels[LEFT].getFormatManager(); - } - - /** - * Disable mouse navigation from within this dual listing panel. - * - * @param enabled false disables navigation - */ - @Override - public void setMouseNavigationEnabled(boolean enabled) { - // always remove before adding to avoid double adding - listingPanels[LEFT].removeButtonPressedListener(fieldNavigators[LEFT]); - listingPanels[RIGHT].removeButtonPressedListener(fieldNavigators[RIGHT]); - - if (enabled) { - listingPanels[LEFT].addButtonPressedListener(fieldNavigators[LEFT]); - listingPanels[RIGHT].addButtonPressedListener(fieldNavigators[RIGHT]); - } - } - - @Override - public void loadData(Data leftData, Data rightData) { - clearCorrelation(); - Program leftProgram = (leftData != null) ? leftData.getProgram() : null; - Program rightProgram = (rightData != null) ? rightData.getProgram() : null; - // Try to show matching code units based on the size of the larger data item. - long maxOffset = getMaxOffset(leftData, rightData); - AddressSetView leftAddressSet = EMPTY_ADDRESS_SET; - if (leftData != null) { - Address leftMinAddress = leftData.getMinAddress(); - Address leftEndAddress = getEndAddress(leftProgram, maxOffset, leftMinAddress); - leftAddressSet = new AddressSet(leftMinAddress, leftEndAddress); - } - AddressSetView rightAddressSet = EMPTY_ADDRESS_SET; - if (rightData != null) { - Address rightMinAddress = rightData.getMinAddress(); - Address rightEndAddress = getEndAddress(rightProgram, maxOffset, rightMinAddress); - rightAddressSet = new AddressSet(rightMinAddress, rightEndAddress); - } - setPrograms(leftProgram, rightProgram); - // Adjust the data and functions only after the correct programs are set. - functions[LEFT] = null; - functions[RIGHT] = null; - data[LEFT] = leftData; - data[RIGHT] = rightData; - // Adjust the addresses only after the Data is set. - loadAddresses(leftProgram, rightProgram, leftAddressSet, rightAddressSet); - setDataTitles(); - updateActionEnablement(); - } - - /** - * Gets the maximum offset based on the larger data that is passed to this method. - * - * @param leftData the left view's data - * @param rightData the right view's data - * @return the maximum offset (one less than the larger data item's size). - */ - private long getMaxOffset(Data leftData, Data rightData) { - long leftOffset = 0; - if (leftData != null) { - leftOffset = leftData.getMaxAddress().subtract(leftData.getMinAddress()); - } - long rightOffset = 0; - if (rightData != null) { - rightOffset = rightData.getMaxAddress().subtract(rightData.getMinAddress()); - } - long maxOffset = Math.max(leftOffset, rightOffset); - return maxOffset; - } - - /** - * Gets the ending address to be displayed. It tries to get an ending address that is maxOffset - * number of bytes beyond the minAddress without leaving the memory block that contains the - * minAddress. If the maxOffset is beyond the end of the block then the end of the block is - * returned. For an externalAddress the minAddress is returned. - * - * @param program the program containing the data - * @param maxOffset the max offset - * @param minAddress the minimum address of the data - * @return the end address to display - */ - private Address getEndAddress(Program program, long maxOffset, Address minAddress) { - if (minAddress.isExternalAddress()) { - return minAddress; // Begin and end address are same for external data. - } - MemoryBlock block = program.getMemory().getBlock(minAddress); - Address blockEnd = block.getEnd(); - Address endAddress; - try { - endAddress = minAddress.add(maxOffset); - if (endAddress.compareTo(blockEnd) > 0) { - endAddress = blockEnd; - } - } - catch (AddressOutOfBoundsException e) { - endAddress = blockEnd; - } - return endAddress; - } - - /** - * Clears the address correlation being used with the ListingDiff and the dual listing field - * panel coordinator. - */ - private void clearCorrelation() { - correlator = null; - try { - listingDiff.setCorrelation(correlator); - // Setting the correlation will also reset the locked line numbers. - ListingComparisonFieldPanelCoordinator fieldPanelCoordinator = - getFieldPanelCoordinator(); - if (fieldPanelCoordinator != null) { - fieldPanelCoordinator.setCorrelation(correlator); - } - } - catch (MemoryAccessException e) { - Msg.error(this, "Couldn't clear the address correlator for the dual listing.", e); - } - } - - /** - * Gets the data loaded in the left listing panel. - * - * @return the data or null - */ - @Override - public Data getLeftData() { - return data[LEFT]; - } - - /** - * Gets the data loaded in the right listing panel. - * - * @return the data or null - */ - @Override - public Data getRightData() { - return data[RIGHT]; - } - - private class DualListingMarkerManager extends MarkerManager { - - private DualListingServiceProvider serviceProvider; - - private DualListingMarkerManager(String Owner, PluginTool tool, - DualListingServiceProvider serviceProvider) { - super(owner, tool); - this.serviceProvider = serviceProvider; - } - - @Override - public GoToService getGoToService() { - return serviceProvider.getService(GoToService.class); - } - } - - @Override - public void listingDiffChanged() { - setDiffHighlights(); - } - - /** - * Displays the indicated text int the tool's status area. - * - * @param text the message to display - */ - void setStatusInfo(String text) { - tool.setStatusInfo(text); - } - - @Override - public void refreshLeftPanel() { - // Listing will update automatically, so just update title. - setLeftTitle(getFunctionTitle(functions[LEFT])); - } - - @Override - public void refreshRightPanel() { - // Listing will update automatically, so just update title. - setRightTitle(getFunctionTitle(functions[RIGHT])); - } - - @Override - public void programRestored(Program program) { - if (getLeftProgram() == program) { - setLeftTitle(getFunctionTitle(functions[LEFT])); - } - if (getRightProgram() == program) { - setRightTitle(getFunctionTitle(functions[RIGHT])); - } - } - - @Override - public boolean leftPanelHasFocus() { - return currProgramIndex == LEFT; - } - - @Override - public void setTitlePrefixes(String leftTitlePrefix, String rightTitlePrefix) { - this.leftTitlePrefix = leftTitlePrefix; - this.rightTitlePrefix = rightTitlePrefix; - setLeftTitle(leftTitle); - setRightTitle(rightTitle); - } - - private class DualListingMouseListener extends MouseAdapter { - - private int leftOrRight; - - DualListingMouseListener(int leftOrRight) { - this.leftOrRight = leftOrRight; - } - - @Override - public void mousePressed(MouseEvent e) { - setDualPanelFocus(leftOrRight); - } - } - - /** - * Gets the GoToService that is used for either the left listing or the right listing. - * - * @param isLeftSide true means get the GoToService for the left side listing. false means get - * it for the right side listing. - * @return the goToService - */ - GoToService getGoToService(boolean isLeftSide) { - return dualListingServiceProviders[isLeftSide ? LEFT : RIGHT].getService(GoToService.class); - } - - public ActionContext getActionContext(MouseEvent event, ComponentProvider provider) { - Object source = (event != null) ? event.getSource() : null; - Component sourceComponent = (source instanceof Component) ? (Component) source : null; - // Is the action being taken on the dual listing. - if (this.isAncestorOf(sourceComponent)) { - ListingPanel sourcePanel = getLeftPanel(); - ListingPanel destinationPanel = getRightPanel(); - - // Are we on a marker margin of the left listing? Return that margin's context. - Object sourceMarginContextObject = getContextObjectForMarginPanels(sourcePanel, event); - if (sourceMarginContextObject != null) { - return new DefaultActionContext(provider) - .setContextObject(sourceMarginContextObject); - } - // Are we on a marker margin of the right listing? Return that margin's context. - Object destinationMarginContextObject = - getContextObjectForMarginPanels(destinationPanel, event); - if (destinationMarginContextObject != null) { - return new DefaultActionContext(provider) - .setContextObject(destinationMarginContextObject); - } - - // If the action is on the Field Header of the left listing panel return an - // appropriate context for the field actions. - if (sourceComponent instanceof FieldHeaderComp) { - FieldHeaderLocation fieldHeaderLocation = - sourcePanel.getFieldHeader().getFieldHeaderLocation(event.getPoint()); - return new DefaultActionContext(provider).setContextObject(fieldHeaderLocation); - } - } - return null; - } - - /** - * Gets a marker margin or overview margin context object if the mouse event occurred on one of - * the GUI components for the indicated listing panel's marker margin (left edge of listing) or - * overview margin (right edge of listing). - * - * @param lp The listing panel to check - * @param event the mouse event - * @return a marker margin context object if the event was on a margin. - */ - public Object getContextObjectForMarginPanels(ListingPanel lp, MouseEvent event) { - Object source = event.getSource(); - // Is event source a marker margin provider on the left side of the listing? - List marginProviders = lp.getMarginProviders(); - for (MarginProvider marginProvider : marginProviders) { - JComponent c = marginProvider.getComponent(); - if (c == source) { - MarkerLocation loc = marginProvider.getMarkerLocation(event.getX(), event.getY()); - if (loc != null) { - return loc; // Return the marker margin location that was clicked. - } - return source; // Return the margin provider that was clicked. - } - } - // Is event source an overview provider on the right side of the listing? - List overviewProviders = lp.getOverviewProviders(); - for (OverviewProvider overviewProvider : overviewProviders) { - JComponent c = overviewProvider.getComponent(); - if (c == source) { - return source; // Return the overview provider that was clicked. - } - } - return null; // Not one of the listing panel's margin panels. - } - - /** - * Change listener that performs a repaint when a marker changes between enabled and disabled. - */ - private class MarkerChangeListener implements ChangeListener { - - private int leftOrRight; - - private MarkerChangeListener(int leftOrRight) { - this.leftOrRight = leftOrRight; - } - - @Override - public void stateChanged(ChangeEvent e) { - listingPanels[leftOrRight].getFieldPanel().repaint(); - } - } - - @Override - public FieldPanel getLeftFieldPanel() { - return getLeftPanel().getFieldPanel(); - } - - @Override - public FieldPanel getRightFieldPanel() { - return getRightPanel().getFieldPanel(); - } - - @Override - protected ListingComparisonFieldPanelCoordinator createFieldPanelCoordinator() { - ListingComparisonFieldPanelCoordinator coordinator = - new ListingComparisonFieldPanelCoordinator(this); - // If we already have an address correlator established then use it. - if (correlator != null) { - coordinator.setCorrelation(correlator); - } - return coordinator; - } - - /** - * Restores this panel to the indicated saved configuration state. - * - * @param prefix identifier to prepend to any save state names to make them unique. - * @param saveState the configuration state to restore - */ - public void readConfigState(String prefix, SaveState saveState) { - showSideBySide(saveState.getBoolean(prefix + DUAL_LISTING_SIDE_BY_SIDE, true)); - setHeaderShowing(saveState.getBoolean(prefix + DUAL_LISTING_HEADER_SHOWING, false)); - } - - /** - * Saves the current configuration state of this panel. - * - * @param prefix identifier to prepend to any save state names to make them unique. - * @param saveState the new configuration state - */ - public void writeConfigState(String prefix, SaveState saveState) { - saveState.putBoolean(prefix + DUAL_LISTING_SIDE_BY_SIDE, isSideBySide()); - saveState.putBoolean(prefix + DUAL_LISTING_HEADER_SHOWING, isHeaderShowing()); - } - - @Override - public void setScrollingSyncState(boolean syncScrolling) { - // Overrides the base method so it can save and restore the coordinator sync state. - if (isScrollingSynced() == syncScrolling) { + private void nextAreaDiff(boolean forward) { + NavigateType type = nextPreviousAreaTypeAction.getCurrentState().getUserData(); + ListingPanel activeListingPanel = getActiveListingPanel(); + ProgramLocation activePanelLocation = activeListingPanel.getProgramLocation(); + if (activePanelLocation == null) { + tool.setStatusInfo( + "The " + (activeSide == LEFT ? "first" : "second") + " listing is empty."); return; } - FieldPanel currentFieldPanel = listingPanels[currProgramIndex].getFieldPanel(); - ViewerPosition viewerPosition = currentFieldPanel.getViewerPosition(); // Save focused side's viewer position. - saveCoordinatorState(); // Saves sync state when disabling synchronized scrolling. - super.setScrollingSyncState(syncScrolling); - // Need to restore sync point if current location can't determine matching one. - if (!hasMatchingLocation()) { - restoreCoordinatorState(); // Restores sync state when enabling synchronized scrolling. - } - currentFieldPanel.setViewerPosition(viewerPosition.getIndex(), viewerPosition.getXOffset(), - viewerPosition.getYOffset()); // Restore the focused side's viewer position. - } + Address activeAddress = activePanelLocation.getAddress(); - private boolean hasMatchingLocation() { - ProgramLocation cursorLocation = listingPanels[currProgramIndex].getCursorLocation(); - if (cursorLocation != null) { - Address address = cursorLocation.getAddress(); - Address otherAddress; - if (currProgramIndex == LEFT) { - otherAddress = getRightCorrelatedAddress(address); + ArrayList iteratorList = new ArrayList<>(); + + if (type == NavigateType.ALL || type == NavigateType.DIFF) { + AddressSetView activeDiffs = listingDiff.getDiffs(activeSide); + iteratorList.add(activeDiffs.getAddressRanges(activeAddress, forward)); + } + if (type == NavigateType.ALL || type == NavigateType.UNMATCHED) { + AddressSetView unmatchedCode = listingDiff.getUnmatchedCode(activeSide); + iteratorList.add(unmatchedCode.getAddressRanges(activeAddress, forward)); + } + + MultiAddressRangeIterator multiIterator = new MultiAddressRangeIterator( + iteratorList.toArray(new AddressRangeIterator[iteratorList.size()]), forward); + + if (multiIterator.hasNext()) { + AddressRange nextRange = multiIterator.next(); + Address minAddress = nextRange.getMinAddress(); + if ((forward ? nextRange.contains(activeAddress) : minAddress.equals(activeAddress)) && + multiIterator.hasNext()) { + nextRange = multiIterator.next(); + minAddress = nextRange.getMinAddress(); } - else { - otherAddress = getLeftCorrelatedAddress(address); + if (minAddress.equals(activeAddress)) { + outputNoNextPreviousMessage(forward, activeSide); + return; } - return (otherAddress != null); + tool.clearStatusInfo(); + activeListingPanel.goTo(minAddress); } - return false; - } - - private void saveCoordinatorState() { - // If we can get the field panel coordinator, save its state. - ListingComparisonFieldPanelCoordinator fieldPanelCoordinator = getFieldPanelCoordinator(); - if (fieldPanelCoordinator != null) { - // Save the coordinator state. - coordinatorLockedAddresses = fieldPanelCoordinator.getLockedAddresses(); + else { + outputNoNextPreviousMessage(forward, activeSide); } } - private void restoreCoordinatorState() { - // If we can get the field panel coordinator, restore its state. - ListingComparisonFieldPanelCoordinator fieldPanelCoordinator = getFieldPanelCoordinator(); - if (fieldPanelCoordinator != null && coordinatorLockedAddresses != null && - coordinatorLockedAddresses.length == 2) { - // Restore the coordinator state. - fieldPanelCoordinator.setLockedAddresses(coordinatorLockedAddresses[LEFT], - coordinatorLockedAddresses[RIGHT]); - } + private void outputNoNextPreviousMessage(boolean forward, Side side) { + NavigateType type = nextPreviousAreaTypeAction.getCurrentState().getUserData(); + String typeName = getTypeName(type).toLowerCase(); + tool.setStatusInfo("There isn't another " + (forward ? "next " : "previous ") + + typeName + " area in the " + + (side == LEFT ? "first" : "second") + " listing."); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingComparisonFieldPanelCoordinator.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingComparisonFieldPanelCoordinator.java deleted file mode 100644 index d8ff25c7fc..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingComparisonFieldPanelCoordinator.java +++ /dev/null @@ -1,149 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.app.util.viewer.listingpanel; - -import java.math.BigInteger; - -import docking.widgets.fieldpanel.FieldPanel; -import docking.widgets.fieldpanel.internal.LayoutLockedFieldPanelCoordinator; -import docking.widgets.fieldpanel.support.ViewerPosition; -import ghidra.app.util.viewer.util.AddressIndexMap; -import ghidra.program.model.address.Address; -import ghidra.program.util.ListingAddressCorrelation; -import ghidra.program.util.ProgramLocation; - -/** - * Coordinates cursor location and scrolling between the two sides of a ListingCodeComparisonPanel. - */ -public class ListingComparisonFieldPanelCoordinator extends LayoutLockedFieldPanelCoordinator - implements DualListingFieldPanelCoordinator { - - private ListingCodeComparisonPanel dualListingPanel; - private ListingAddressCorrelation addressCorrelation; - private Address[] lockLineAddresses = new Address[2]; - - /** - * Constructor for this dual listing field panel coordinator. - * @param dualListingPanel the dual listing to be controlled by this coordinator. - */ - public ListingComparisonFieldPanelCoordinator(ListingCodeComparisonPanel dualListingPanel) { - super(new FieldPanel[] { dualListingPanel.getLeftPanel().getFieldPanel(), - dualListingPanel.getRightPanel().getFieldPanel() }); - this.dualListingPanel = dualListingPanel; - } - - /** - * Sets a new address correlation for associating addresses between the left and right sides. - * The field panels can then be coordinated by locking the layouts together whenever the - * current location on one side can be correlated with a location on the other side. - * @param addressCorrelation the correlation to use for locking the two sides together for - * scrolling. - */ - public void setCorrelation(ListingAddressCorrelation addressCorrelation) { - this.addressCorrelation = addressCorrelation; - resetLockedLines(); - } - - @Override - public void leftLocationChanged(ProgramLocation leftLocation) { - if (addressCorrelation == null) { - return; // Do nothing since no address correlator. - } - Address leftAddress = leftLocation.getAddress(); - if (leftAddress == null) { - return; // Do nothing since can't get a location address. - } - // The correlation only gives a right side address for a left side address that is a code unit minimum. - Address rightAddress = addressCorrelation.getAddressInSecond(leftAddress); - if (rightAddress == null) { - return; // Do nothing since can't get a matching address. - } - // Got an address so let's try to lock the two panels at the indexes for the matching addresses. - setLockedAddresses(leftAddress, rightAddress); - - FieldPanel fp = dualListingPanel.getLeftPanel().getFieldPanel(); - adjustFieldPanel(fp); - } - - @Override - public void rightLocationChanged(ProgramLocation rightLocation) { - if (addressCorrelation == null) { - return; // Do nothing since no address correlator. - } - Address rightAddress = rightLocation.getAddress(); - if (rightAddress == null) { - return; // Do nothing since can't get a location address. - } - // The correlation only gives a left side address for a right side address that is a code unit minimum. - Address leftAddress = addressCorrelation.getAddressInFirst(rightAddress); - if (leftAddress == null) { - return; // Do nothing since can't get a matching address. - } - // Got an address so let's try to lock the two panels at the indexes for the matching addresses. - setLockedAddresses(leftAddress, rightAddress); - - FieldPanel fp = dualListingPanel.getRightPanel().getFieldPanel(); - adjustFieldPanel(fp); - } - - /** - * Kicks the field panels viewChanged() method so that the field panels will realign their - * layouts using the locked line numbers. - * - * @param fp the field panel that has focus. - */ - void adjustFieldPanel(FieldPanel fp) { - ViewerPosition viewerPosition = fp.getViewerPosition(); - BigInteger topIndex = viewerPosition.getIndex(); - int topXOffset = viewerPosition.getXOffset(); - int topYOffset = viewerPosition.getYOffset(); - viewChanged(fp, topIndex, topXOffset, topYOffset); - } - - /** - * Sets the left and right addresses that should currently be locked together for - * synchronized scrolling. - * - * @param leftAddress the address in the left listing. - * @param rightAddress the address in the right listing. - */ - void setLockedAddresses(Address leftAddress, Address rightAddress) { - lockLineAddresses[0] = leftAddress; - lockLineAddresses[1] = rightAddress; - ListingPanel leftListingPanel = dualListingPanel.getLeftPanel(); - ListingPanel rightListingPanel = dualListingPanel.getRightPanel(); - AddressIndexMap leftAddressIndexMap = leftListingPanel.getAddressIndexMap(); - AddressIndexMap rightAddressIndexMap = rightListingPanel.getAddressIndexMap(); - - BigInteger leftIndex = - (leftAddress != null) ? leftAddressIndexMap.getIndex(leftAddress) : null; - BigInteger rightIndex = - (rightAddress != null) ? rightAddressIndexMap.getIndex(rightAddress) : null; - //lockLines will set null args to BigInteger.ZERO - lockLines(leftIndex, rightIndex); - } - - /** - * Gets the left and right addresses that are currently locked together for synchronized - * scrolling. - * - * @return an array containing the left (index 0) and right (index 1) addresses that are - * locked together. - */ - Address[] getLockedAddresses() { - return lockLineAddresses; - } -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingComparisonProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingComparisonProvider.java deleted file mode 100644 index 1d37b0f2e4..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingComparisonProvider.java +++ /dev/null @@ -1,61 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.app.util.viewer.listingpanel; - -import javax.swing.Icon; - -import generic.theme.GIcon; -import ghidra.framework.plugintool.ComponentProviderAdapter; -import ghidra.framework.plugintool.PluginTool; -import ghidra.program.model.address.AddressSetView; -import ghidra.program.model.listing.Program; - -/** - * Provider for displaying a ListingCodeComparisonPanel. - */ -public class ListingComparisonProvider extends ComponentProviderAdapter { - - private static final Icon DUAL_LISTING_ICON = - new GIcon("icon.base.util.listingcompare.provider"); - private ListingCodeComparisonPanel dualListingPanel; - - /** - * Constructor for a provider that can display a ListingCodeComparisonPanel. - * @param tool the tool that contains this provider. - * @param name the owner of this provider, which is usually a plugin name. - * @param p1 program for the listing displayed in the left side of the panel. - * @param p2 program for the listing displayed in the right side of the panel. - * @param set1 the address set indicating the portion of the listing displayed in the left side - * of the panel. - * @param set2 the address set indicating the portion of the listing displayed in the right side - * of the panel. - */ - public ListingComparisonProvider(PluginTool tool, String name, Program p1, Program p2, - AddressSetView set1, AddressSetView set2) { - super(tool, "Listing Comparison", name); - setIcon(DUAL_LISTING_ICON); - dualListingPanel = new ListingCodeComparisonPanel(name, tool); - dualListingPanel.loadAddresses(p1, p2, set1, set2); - setTransient(); - tool.addComponentProvider(this, true); - } - - @Override - public ListingCodeComparisonPanel getComponent() { - return dualListingPanel; - } - -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingCoordinator.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingCoordinator.java new file mode 100644 index 0000000000..91a63421b1 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingCoordinator.java @@ -0,0 +1,128 @@ +/* ### + * 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.util.viewer.listingpanel; + +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.support.ViewerPosition; +import ghidra.app.util.viewer.util.AddressIndexMap; +import ghidra.program.model.address.Address; +import ghidra.program.util.ListingAddressCorrelation; +import ghidra.program.util.ProgramLocation; +import ghidra.util.datastruct.Duo; +import ghidra.util.datastruct.Duo.Side; + +/** + * Keeps two listing panels synchronized, both the view and cursor location + */ +public class ListingCoordinator { + private Duo displays; + private Duo

lockLineAddresses = new Duo<>(); + + private ProgramLocationTranslator locationTranslator; + private LineLockedFieldPanelCoordinator viewCoordinator; + + ListingCoordinator(Duo displays, ListingAddressCorrelation correlator) { + this.displays = displays; + this.locationTranslator = new ProgramLocationTranslator(correlator); + FieldPanel left = displays.get(LEFT).getListingPanel().getFieldPanel(); + FieldPanel right = displays.get(RIGHT).getListingPanel().getFieldPanel(); + viewCoordinator = new LayoutLockedFieldPanelCoordinator(left, right); + } + + /** + * notification that the given side change to the given location + * @param side the side that changed + * @param location the location from the given side + */ + 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) { + updateViewCoordinator(side, location, otherLocation); + displays.get(otherSide).goTo(otherLocation); + displays.get(side.otherSide()).updateCursorMarkers(otherLocation); + } + + } + + void dispose() { + viewCoordinator.dispose(); + } + + /** + * synchronized the two listings using the given side as the source + * @param side to synchronize from + */ + void sync(Side side) { + adjustFieldPanel(displays.get(side).getListingPanel().getFieldPanel()); + ProgramLocation programLocation = displays.get(side).getProgramLocation(); + if (programLocation != null) { + setLocation(side, programLocation); + } + } + + /** + * Kicks the field panels viewChanged() method so that the field panels will realign their + * layouts using the locked line numbers. + * + * @param fieldPanel the field panel that has focus. + */ + private void adjustFieldPanel(FieldPanel fieldPanel) { + ViewerPosition viewerPosition = fieldPanel.getViewerPosition(); + BigInteger topIndex = viewerPosition.getIndex(); + int topXOffset = viewerPosition.getXOffset(); + int topYOffset = viewerPosition.getYOffset(); + viewCoordinator.viewChanged(fieldPanel, topIndex, topXOffset, topYOffset); + } + + /** + * Sets the left and right addresses that should currently be locked together for + * synchronized scrolling. + * + * @param leftAddress the address in the left listing. + * @param rightAddress the address in the right listing. + */ + private void setLockedAddresses(Address leftAddress, Address rightAddress) { + 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(); + + BigInteger leftIndex = leftMap.getIndex(leftAddress); + BigInteger rightIndex = rightMap.getIndex(rightAddress); + viewCoordinator.lockLines(leftIndex, rightIndex); + } + + private void updateViewCoordinator(Side side, ProgramLocation location, + ProgramLocation otherLocation) { + Address leftAddress = side == LEFT ? location.getAddress() : otherLocation.getAddress(); + Address rightAddress = side == LEFT ? otherLocation.getAddress() : location.getAddress(); + setLockedAddresses(leftAddress, rightAddress); + FieldPanel fp = displays.get(side).getListingPanel().getFieldPanel(); + adjustFieldPanel(fp); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingDiffActionManager.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingDiffActionManager.java index 749dd7f864..cb226dc422 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingDiffActionManager.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingDiffActionManager.java @@ -15,6 +15,9 @@ */ package ghidra.app.util.viewer.listingpanel; +import java.util.Arrays; +import java.util.List; + import javax.swing.Icon; import docking.ActionContext; @@ -71,9 +74,9 @@ public class ListingDiffActionManager { * Gets the actions. * @return the docking actions. */ - public DockingAction[] getActions() { - return new DockingAction[] { toggleIgnoreByteDiffsAction, toggleIgnoreConstantsAction, - toggleIgnoreRegisterNamesAction }; + public List getActions() { + return Arrays.asList(toggleIgnoreByteDiffsAction, toggleIgnoreConstantsAction, + toggleIgnoreRegisterNamesAction); } /** diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingDiffHighlightProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingDiffHighlightProvider.java index d4e2711648..17315b1497 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingDiffHighlightProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingDiffHighlightProvider.java @@ -16,7 +16,7 @@ package ghidra.app.util.viewer.listingpanel; import java.awt.Color; -import java.util.ArrayList; +import java.util.*; import docking.widgets.fieldpanel.support.Highlight; import ghidra.app.util.ListingHighlightProvider; @@ -26,6 +26,7 @@ import ghidra.program.model.address.*; import ghidra.program.model.listing.CodeUnit; import ghidra.program.model.listing.Instruction; import ghidra.program.util.ListingDiff; +import ghidra.util.datastruct.Duo.Side; public class ListingDiffHighlightProvider implements ListingHighlightProvider { @@ -33,22 +34,22 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider { private static final String DEFAULT_OPERAND_SEPARATOR = ","; private ListingDiff listingDiff; - private boolean isListing1; + private Side side; private ListingCodeComparisonOptions comparisonOptions; /** * Constructor for this highlight provider. * @param listingDiff the ListingDiff to use to determine where there are differences that * need highlighting. - * @param isListing1 true means that these are the highlights for the first listing. + * @param side LEFT or RIGHT * false means the highlights are for the second listing. * @param comparisonOptions the tool options that indicate the current * background colors for the Listing code comparison panel. */ - public ListingDiffHighlightProvider(ListingDiff listingDiff, boolean isListing1, + public ListingDiffHighlightProvider(ListingDiff listingDiff, Side side, ListingCodeComparisonOptions comparisonOptions) { this.listingDiff = listingDiff; - this.isListing1 = isListing1; + this.side = side; this.comparisonOptions = comparisonOptions; } @@ -77,14 +78,12 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider { private Highlight[] getByteDiffHighlights(String text, CodeUnit codeUnit, int cursorTextOffset) { Address minAddress = codeUnit.getMinAddress(); - AddressSetView unmatchedDiffs = (isListing1) ? listingDiff.getListing1UnmatchedCode() - : listingDiff.getListing2UnmatchedCode(); + AddressSetView unmatchedDiffs = listingDiff.getUnmatchedCode(side); if (unmatchedDiffs.contains(minAddress)) { return NO_HIGHLIGHTS; } Color byteDiffsBackgroundColor = comparisonOptions.getByteDiffsBackgroundColor(); - AddressSetView byteDiffs = - (isListing1) ? listingDiff.getListing1ByteDiffs() : listingDiff.getListing2ByteDiffs(); + AddressSetView byteDiffs = listingDiff.getByteDiffs(side); // Get intersection of Byte Diff addresses and this code unit's addresses AddressSet diffSet = new AddressSet(codeUnit.getMinAddress(), codeUnit.getMaxAddress()); diffSet = diffSet.intersect(byteDiffs); @@ -108,19 +107,17 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider { private Highlight[] getMnemonicDiffHighlights(String text, CodeUnit codeUnit, int cursorTextOffset) { Address minAddress = codeUnit.getMinAddress(); - AddressSetView unmatchedDiffs = (isListing1) ? listingDiff.getListing1UnmatchedCode() - : listingDiff.getListing2UnmatchedCode(); + AddressSetView unmatchedDiffs = listingDiff.getUnmatchedCode(side); if (unmatchedDiffs.contains(minAddress)) { return NO_HIGHLIGHTS; } Color mnemonicDiffsBackgroundColor = comparisonOptions.getMnemonicDiffsBackgroundColor(); - AddressSetView codeUnitDiffs = (isListing1) ? listingDiff.getListing1CodeUnitDiffs() - : listingDiff.getListing2CodeUnitDiffs(); + AddressSetView codeUnitDiffs = listingDiff.getCodeUnitDiffs(side); // Get intersection of Code Unit Diff addresses and this code unit's addresses AddressSet diffSet = new AddressSet(codeUnit.getMinAddress(), codeUnit.getMaxAddress()); diffSet = diffSet.intersect(codeUnitDiffs); if (!diffSet.isEmpty()) { - CodeUnit otherCodeUnit = listingDiff.getMatchingCodeUnit(codeUnit, isListing1); + CodeUnit otherCodeUnit = listingDiff.getMatchingCodeUnit(codeUnit, side); if (otherCodeUnit == null) { return entireTextHighlight(text, cursorTextOffset, mnemonicDiffsBackgroundColor); } @@ -137,33 +134,33 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider { private Highlight[] getOperandDiffHighlights(String text, CodeUnit codeUnit, int cursorTextOffset) { Address minAddress = codeUnit.getMinAddress(); - AddressSetView unmatchedDiffs = (isListing1) ? listingDiff.getListing1UnmatchedCode() - : listingDiff.getListing2UnmatchedCode(); + AddressSetView unmatchedDiffs = listingDiff.getUnmatchedCode(side); if (unmatchedDiffs.contains(minAddress)) { return NO_HIGHLIGHTS; } Color operandDiffsBackgroundColor = comparisonOptions.getOperandDiffsBackgroundColor(); - AddressSetView codeUnitDiffs = (isListing1) ? listingDiff.getListing1CodeUnitDiffs() - : listingDiff.getListing2CodeUnitDiffs(); + AddressSetView codeUnitDiffs = listingDiff.getCodeUnitDiffs(side); // Get intersection of Code Unit Diff addresses and this code unit's addresses AddressSet diffSet = new AddressSet(codeUnit.getMinAddress(), codeUnit.getMaxAddress()); diffSet = diffSet.intersect(codeUnitDiffs); if (!diffSet.isEmpty()) { - CodeUnit matchingCodeUnit = listingDiff.getMatchingCodeUnit(codeUnit, isListing1); + CodeUnit matchingCodeUnit = listingDiff.getMatchingCodeUnit(codeUnit, side); if (listingDiff.doesEntireOperandSetDiffer(codeUnit, matchingCodeUnit)) { return entireTextHighlight(text, cursorTextOffset, operandDiffsBackgroundColor); } - Pair[] pairs = getOperandPairs(text, codeUnit); + List operandRanges = getOperandRanges(text, codeUnit); int numOperands = codeUnit.getNumOperands(); - if (pairs.length != numOperands) { + if (operandRanges.size() != numOperands) { return entireTextHighlight(text, cursorTextOffset, operandDiffsBackgroundColor); } int[] diffOpIndices = listingDiff.getOperandsThatDiffer(codeUnit, matchingCodeUnit); ArrayList highlights = new ArrayList<>(); for (int diffOpIndex : diffOpIndices) { // Highlight each operand that differs. - highlights.add(new Highlight(pairs[diffOpIndex].start, pairs[diffOpIndex].end, - operandDiffsBackgroundColor)); + highlights.add( + new Highlight(operandRanges.get(diffOpIndex).start, + operandRanges.get(diffOpIndex).end, + operandDiffsBackgroundColor)); } return highlights.toArray(new Highlight[highlights.size()]); } @@ -174,15 +171,15 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider { * Gets an array of start/end positions for each operand within the operand field's full text. * @param text the full text from the operand field * @param codeUnit the code unit whose operand text is provided - * @return the operand pairs indicating the start and end offsets for each individual operand + * @return the operand ranges indicating the start and end offsets for each individual operand * within the text. */ - private Pair[] getOperandPairs(String text, CodeUnit codeUnit) { + private List getOperandRanges(String text, CodeUnit codeUnit) { if (text == null || text.isEmpty()) { - return new Pair[0]; + return Collections.emptyList(); } Instruction instruction = (codeUnit instanceof Instruction) ? (Instruction) codeUnit : null; - ArrayList list = new ArrayList<>(); + ArrayList list = new ArrayList<>(); int opIndex = 0; int textLength = text.length(); int start = 0; // Start index in the text for the current operand. @@ -197,7 +194,7 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider { separatorIndex = text.indexOf(separator, start); start = separatorIndex + separator.length(); } - // Get a start/end Pair of indexes for each operand. + // Get a start/end range of indexes for each operand. while (start < textLength) { ++opIndex; // Increment the opIndex since we find the separator before an operand. separator = DEFAULT_OPERAND_SEPARATOR; // default separator @@ -209,17 +206,15 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider { separatorIndex = (separator != null && !separator.isEmpty()) ? text.indexOf(separator, start) : -1; if (separatorIndex == -1) { - // Add the last operand's index Pair to the list. - list.add(new Pair(start, textLength - 1)); + list.add(new Range(start, textLength - 1)); start = textLength; continue; } - // Add the current operand's index Pair to the list. - list.add(new Pair(start, separatorIndex - 1)); + list.add(new Range(start, separatorIndex - 1)); // Move start to the beginning of the next operand. start = separatorIndex + separator.length(); } - return list.toArray(new Pair[list.size()]); + return list; } private Highlight[] entireTextHighlight(String text, int cursorTextOffset, Color color) { @@ -229,21 +224,12 @@ public class ListingDiffHighlightProvider implements ListingHighlightProvider { return new Highlight[] { highlight }; } - /** - * Determines if this highlight provider is for the first listing of the ListingDiff. - * @return true if this provider's highlights are for the first listing. false if the - * highlights are for the second listing. - */ - public boolean isListing1() { - return isListing1; - } - - private class Pair { + private class Range { private int start; private int end; - private Pair(int start, int end) { + private Range(int start, int end) { this.start = start; this.end = end; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingDisplay.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingDisplay.java new file mode 100644 index 0000000000..f08a2bb60c --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingDisplay.java @@ -0,0 +1,345 @@ +/* ### + * 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.util.viewer.listingpanel; + +import static ghidra.GhidraOptions.*; + +import java.awt.Color; + +import javax.swing.Icon; + +import docking.widgets.fieldpanel.support.ViewerPosition; +import generic.theme.GIcon; +import ghidra.GhidraOptions; +import ghidra.app.plugin.core.codebrowser.MarkerServiceBackgroundColorModel; +import ghidra.app.plugin.core.codebrowser.hover.*; +import ghidra.app.plugin.core.marker.MarkerManager; +import ghidra.app.services.*; +import ghidra.app.util.ListingHighlightProvider; +import ghidra.app.util.viewer.format.FormatManager; +import ghidra.app.util.viewer.util.AddressIndexMap; +import ghidra.app.util.viewer.util.FieldNavigator; +import ghidra.framework.options.ToolOptions; +import ghidra.framework.plugintool.PluginTool; +import ghidra.framework.plugintool.ServiceProviderStub; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ListingDiff; +import ghidra.program.util.ProgramLocation; +import ghidra.util.datastruct.Duo.Side; + +/** + * Represents one side of a dual listing compare window. It holds the listing panel and + * related state information for one side. + */ +public class ListingDisplay implements ListingDiffChangeListener { + private static final Icon CURSOR_LOC_ICON = new GIcon("icon.base.util.listingcompare.cursor"); + private ListingPanel listingPanel; + private PluginTool tool; + private ListingDisplayServiceProvider serviceProvider; + private MarkerManager markerManager; + private ListingCodeComparisonOptions comparisonOptions; + private Color cursorHighlightColor; + private MarkerSet unmatchedMarkers; + private MarkerSet diffMarkers; + private MarkerSet currentCursorMarkers; + private ListingDiffHighlightProvider diffHighlights; + private FieldNavigator fieldNavigator; + private ListingDiff listingDiff; + private Side side; + + public ListingDisplay(PluginTool tool, String owner, ListingDiff listingDiff, + ListingCodeComparisonOptions comparsionOptions, Side side) { + this.tool = tool; + this.listingDiff = listingDiff; + this.comparisonOptions = comparsionOptions; + this.side = side; + + FormatManager formatManager = createFormatManager(); + loadOptions(); + + listingPanel = new ListingPanel(formatManager); + // Turn off selection in the listings so it can be set up as desired elsewhere. + listingPanel.getFieldPanel().enableSelection(false); + + serviceProvider = new ListingDisplayServiceProvider(); + formatManager.setServiceProvider(serviceProvider); + fieldNavigator = new FieldNavigator(serviceProvider, null); + setMouseNavigationEnabled(true); + createMarkerManager(owner); + + listingPanel.addHoverService(new ReferenceListingHover(tool, () -> formatManager)); + listingPanel.addHoverService(new DataTypeListingHover(tool)); + listingPanel.addHoverService(new TruncatedTextListingHover(tool)); + listingPanel.addHoverService(new FunctionNameListingHover(tool)); + + listingDiff.addListingDiffChangeListener(this); + setHoverMode(true); + } + + private void createMarkerManager(String owner) { + markerManager = new ListingDisplayMarkerManager(tool, owner); + markerManager.addChangeListener(e -> listingPanel.repaint()); + MarginProvider marginProvider = markerManager.getMarginProvider(); + listingPanel.addMarginProvider(marginProvider); + OverviewProvider overviewProvider = markerManager.getOverviewProvider(); + listingPanel.addOverviewProvider(overviewProvider); + } + + void setProgramLocationListener(ProgramLocationListener listener) { + listingPanel.setProgramLocationListener(listener); + } + + private FormatManager createFormatManager() { + ToolOptions displayOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_DISPLAY); + ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS); + + FormatManager formatManager = new FormatManager(displayOptions, fieldOptions); + + return formatManager; + } + + public void repaint() { + listingPanel.getFieldPanel().repaint(); + } + + public void setDiffHighlightProvider(ListingDiffHighlightProvider newDiffHighlights) { + if (diffHighlights != null) { + removeHighlightProvider(diffHighlights); + } + + diffHighlights = newDiffHighlights; + + if (diffHighlights != null) { + addHighlightProvider(diffHighlights); + } + } + + public void addHighlightProvider(ListingHighlightProvider highlightProvider) { + listingPanel.getFormatManager().addHighlightProvider(highlightProvider); + } + + public void removeHighlightProvider(ListingHighlightProvider highlightProvider) { + if (highlightProvider == null) { + return; + } + listingPanel.getFormatManager().removeHighlightProvider(highlightProvider); + } + + public void addHoverService(ListingHoverService service) { + listingPanel.addHoverService(service); + } + + public void showHeader(boolean show) { + listingPanel.showHeader(show); + listingPanel.validate(); + listingPanel.invalidate(); + } + + public void setHoverMode(boolean enabled) { + listingPanel.setHoverMode(enabled); + } + + public void setView(AddressSetView view) { + ProgramLocation saved = listingPanel.getProgramLocation(); + listingPanel.setView(view); + if (saved != null) { + listingPanel.goTo(saved); + } + } + + public boolean isHeaderShowing() { + return listingPanel.isHeaderShowing(); + } + + public void setProgramView(Program program, AddressSetView view, String name) { + listingPanel.setProgram(program); + markerManager.clearAll(); + listingPanel.setView(view); + AddressIndexMap indexMap = listingPanel.getAddressIndexMap(); + markerManager.getOverviewProvider().setProgram(program, indexMap); + listingPanel.setBackgroundColorModel( + new MarkerServiceBackgroundColorModel(markerManager, program, indexMap)); + setUpAreaMarkerSets(program, name); + if (!view.isEmpty()) { + goTo(new ProgramLocation(program, view.getMinAddress())); + } + repaint(); + } + + void setUpAreaMarkerSets(Program program, String name) { + if (program == null) { + return; + } + Color diffColor = comparisonOptions.getDiffCodeUnitsBackgroundColor(); + Color unmatchedColor = comparisonOptions.getUnmatchedCodeUnitsBackgroundColor(); + + AddressIndexMap indexMap = listingPanel.getAddressIndexMap(); + listingPanel.getFieldPanel().setBackgroundColorModel(new MarkerServiceBackgroundColorModel( + markerManager, program, indexMap)); + + unmatchedMarkers = markerManager.createAreaMarker(name + " Unmatched Code", + "Instructions that are not matched to an instruction in the other function.", + program, MarkerService.DIFF_PRIORITY, true, true, true, + unmatchedColor); + diffMarkers = markerManager.createAreaMarker(name + " Diffs", + "Instructions that have a difference.", program, MarkerService.DIFF_PRIORITY, + true, true, true, diffColor); + + currentCursorMarkers = markerManager.createPointMarker("Cursor", + "Cursor Location", program, MarkerService.FUNCTION_COMPARE_CURSOR_PRIORITY, + true, true, true, cursorHighlightColor, CURSOR_LOC_ICON, false); + + } + + public ProgramLocation getProgramLocation() { + return listingPanel.getProgramLocation(); + } + + private void loadOptions() { + ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS); + // + // Unusual Code Alert! + // In a normal tool, this option is registered by the Code Browse Plugin. In the VT + // tool, nobody registers this option. Our system logs a warning if an option is used + // but not registered. So, when in a real tool, use the registered/managed option. + // Otherwise, just use the default. + // + if (fieldOptions.isRegistered(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR)) { + cursorHighlightColor = fieldOptions.getColor(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR, + DEFAULT_CURSOR_LINE_COLOR); + } + else { + cursorHighlightColor = DEFAULT_CURSOR_LINE_COLOR; + } + + } + + private class ListingDisplayMarkerManager extends MarkerManager { + + private ListingDisplayMarkerManager(PluginTool tool, String owner) { + super(owner, tool); + } + + @Override + public GoToService getGoToService() { + return serviceProvider.getService(GoToService.class); + } + } + + private class ListingDisplayServiceProvider extends ServiceProviderStub { + private GoToService goToService; + + ListingDisplayServiceProvider() { + goToService = new ListingDisplayGoToService(listingPanel); + } + + @SuppressWarnings("unchecked") + @Override + public T getService(Class serviceClass) { + if (serviceClass == GoToService.class) { + return (T) goToService; + } + return null; + } + } + + public void updateCursorMarkers(ProgramLocation location) { + if (currentCursorMarkers != null) { + currentCursorMarkers.clearAll(); + if (location != null) { + currentCursorMarkers.add(location.getAddress()); + } + } + repaint(); + } + + private void setAreaMarkers(MarkerSet markers, AddressSetView diffAddresses, Color color) { + if (markers == null) { + return; + } + markers.setMarkerColor(color); + markers.clearAll(); + markers.add(diffAddresses); + repaint(); + } + + public void goTo(ProgramLocation location) { + if (location != null) { + listingPanel.goTo(location); + } + updateCursorMarkers(location); + } + + public ListingPanel getListingPanel() { + return listingPanel; + } + + void dispose() { + listingDiff.removeListingDiffChangeListener(this); + + setDiffHighlightProvider(null); + markerManager.dispose(); + listingPanel.removeButtonPressedListener(fieldNavigator); + listingPanel.dispose(); + } + + public FormatManager getFormatManager() { + return listingPanel.getFormatManager(); + } + + public ViewerPosition getViewerPosition() { + return listingPanel.getFieldPanel().getViewerPosition(); + } + + public void setViewerPosition(ViewerPosition position) { + listingPanel.getFieldPanel().setViewerPosition(position.getIndex(), position.getXOffset(), + position.getYOffset()); + } + + public void setMouseNavigationEnabled(boolean enabled) { + listingPanel.removeButtonPressedListener(fieldNavigator); + if (enabled) { + listingPanel.addButtonPressedListener(new FieldNavigator(serviceProvider, null)); + } + } + + @Override + public void listingDiffChanged() { + updateFunctionComparisonDiffHighlights(); + setUnmatchedCodeUnitAreaMarkers(); + setDiffAreaMarkers(); + } + + private void updateFunctionComparisonDiffHighlights() { + setDiffHighlightProvider( + new ListingDiffHighlightProvider(listingDiff, side, comparisonOptions)); + } + + private void setDiffAreaMarkers() { + Color color = comparisonOptions.getDiffCodeUnitsBackgroundColor(); + AddressSetView addresses = listingDiff.getDiffs(side); + + setAreaMarkers(diffMarkers, addresses, color); + } + + private void setUnmatchedCodeUnitAreaMarkers() { + Color color = comparisonOptions.getUnmatchedCodeUnitsBackgroundColor(); + AddressSetView addresses = listingDiff.getUnmatchedCode(side); + setAreaMarkers(unmatchedMarkers, addresses, color); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingDisplayGoToService.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingDisplayGoToService.java new file mode 100644 index 0000000000..321f4dfc2c --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingDisplayGoToService.java @@ -0,0 +1,175 @@ +/* ### + * 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.util.viewer.listingpanel; + +import docking.DockingWindowManager; +import ghidra.app.nav.Navigatable; +import ghidra.app.services.*; +import ghidra.app.util.viewer.util.AddressIndexMap; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.Program; +import ghidra.program.model.symbol.ExternalLocation; +import ghidra.program.util.ProgramLocation; +import ghidra.util.Msg; +import ghidra.util.task.TaskMonitor; + +/** + * This is a GoToService for a dual listing panel. It allows the goTo to occur relative to the + * left or right listing panel of a dual listing panel, since the left and right sides can be + * displaying totally different addresses. + */ +class ListingDisplayGoToService implements GoToService { + + private ListingPanel listingPanel; + + /** + * Constructs a goTo service for a dual listing panel. + * @param listingPanel the listing panel to be navigated to + */ + ListingDisplayGoToService(ListingPanel listingPanel) { + this.listingPanel = listingPanel; + } + + @Override + public GoToOverrideService getOverrideService() { + return null; + } + + @Override + public boolean goTo(ProgramLocation loc) { + return doGoTo(loc); + } + + @Override + public boolean goTo(Navigatable navigatable, Program program, Address address, + Address refAddress) { + return doGoTo(new ProgramLocation(program, address)); + } + + @Override + public boolean goTo(ProgramLocation loc, Program program) { + return doGoTo(loc); + } + + @Override + public boolean goTo(Navigatable navigatable, ProgramLocation loc, Program program) { + return doGoTo(loc); + } + + @Override + public boolean goTo(Navigatable navigatable, Address goToAddress) { + return doGoTo(goToAddress); + } + + @Override + public boolean goTo(Address currentAddress, Address goToAddress) { + return doGoTo(goToAddress); + } + + @Override + public boolean goTo(Address goToAddress) { + return doGoTo(goToAddress); + } + + @Override + public boolean goTo(Address goToAddress, Program program) { + return doGoTo(goToAddress); + } + + @Override + public boolean goToExternalLocation(ExternalLocation extLoc, boolean checkNavigationOption) { + Msg.showError(this, null, "Go To Failed!", + "Can't naviagate to an external function from here"); + return false; + } + + @Override + public boolean goToExternalLocation(Navigatable navigatable, ExternalLocation extLoc, + boolean checkNavigationOption) { + Msg.showError(this, null, "Go To Failed!", + "Can't naviagate to an external function from here"); + return false; + } + + @Override + public boolean goToQuery(Address fromAddr, QueryData queryData, GoToServiceListener listener, + TaskMonitor monitor) { + throw new UnsupportedOperationException( + "Go To Address or Label Query is not allowed in a dual listing view."); + } + + @Override + public boolean goToQuery(Navigatable navigatable, Address fromAddr, QueryData queryData, + GoToServiceListener listener, TaskMonitor monitor) { + throw new UnsupportedOperationException( + "Go To Address or Label Query is not allowed in a dual listing view."); + } + + @Override + public void setOverrideService(GoToOverrideService override) { + // ignored + } + + @Override + public Navigatable getDefaultNavigatable() { + return new DualListingNavigator(listingPanel, this); + } + + private boolean doGoTo(Address addr) { + + // Only go if the address is in the listing's current address set. + if (!validateAddress(addr)) { + return false; + } + + return listingPanel.goTo(addr); + } + + private boolean doGoTo(ProgramLocation loc) { + if (loc == null) { + return false; + } + + // Only go if the location address is in the listing's current address set. + if (!validateAddress(loc.getAddress())) { + return false; + } + + return listingPanel.goTo(loc); + } + + /** + * Checks the address to make sure the listing won't navigate outside the addresses + * it currently has loaded. If it is not a valid address it will set a status message + * on the dual listing. + * @param addr the address to check + * @return true if the address is valid for navigation. + */ + private boolean validateAddress(Address addr) { + if (addr == null) { + return false; + } + AddressIndexMap map = listingPanel.getAddressIndexMap(); + AddressSetView addresses = map.getOriginalAddressSet(); + if (!addresses.contains(addr)) { + DockingWindowManager.getActiveInstance().setStatusText( + "\"" + addr.toString() + "\" is outside the current listing's view."); + return false; + } + return true; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java index df91f2956d..9f96decfc6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ListingPanel.java @@ -19,7 +19,6 @@ import java.awt.*; import java.awt.event.*; import java.math.BigInteger; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import javax.swing.*; @@ -68,7 +67,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc private ListingModel listingModel; private FieldHeader headerPanel; - private ButtonPressedListener[] buttonListeners = new ButtonPressedListener[0]; + private List buttonListeners = new ArrayList<>(); private List indexMapChangeListeners = new ArrayList<>(); private ListingHoverProvider listingHoverHandler; @@ -448,9 +447,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc * @param listener the ButtonPressedListener to add. */ public void addButtonPressedListener(ButtonPressedListener listener) { - List list = new ArrayList<>(Arrays.asList(buttonListeners)); - list.add(listener); - buttonListeners = list.toArray(new ButtonPressedListener[list.size()]); + buttonListeners.add(listener); } /** @@ -459,9 +456,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc * @param listener the ButtonPressedListener to remove. */ public void removeButtonPressedListener(ButtonPressedListener listener) { - List list = new ArrayList<>(Arrays.asList(buttonListeners)); - list.remove(listener); - buttonListeners = list.toArray(new ButtonPressedListener[list.size()]); + buttonListeners.remove(listener); } /** @@ -569,7 +564,7 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc layoutModel.dispose(); layoutModel = createLayoutModel(null); layoutModel.dispose(); - buttonListeners = null; + buttonListeners.clear(); fieldPanel.dispose(); } @@ -1225,4 +1220,16 @@ public class ListingPanel extends JPanel implements FieldMouseListener, FieldLoc public void removeDisplayListener(AddressSetDisplayListener listener) { displayListeners.remove(listener); } + + @Override + public synchronized void addFocusListener(FocusListener l) { + // we are not focusable, defer to contained field panel + fieldPanel.addFocusListener(l); + } + + @Override + public synchronized void removeFocusListener(FocusListener l) { + // we are not focusable, defer to contained field panel + fieldPanel.removeFocusListener(l); + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ProgramBigListingModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ProgramBigListingModel.java index edefd53de0..b533a952b7 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ProgramBigListingModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ProgramBigListingModel.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. @@ -513,21 +513,11 @@ public class ProgramBigListingModel implements ListingModel, FormatModelListener } } - @Override - public void formatModelAdded(FieldFormatModel model) { - notifyModelSizeChanged(); - } - @Override public void formatModelChanged(FieldFormatModel model) { notifyModelSizeChanged(); } - @Override - public void formatModelRemoved(FieldFormatModel model) { - notifyModelSizeChanged(); - } - @Override public void addListener(ListingModelListener listener) { listeners.add(listener); 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 new file mode 100644 index 0000000000..69da99ff3f --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/listingpanel/ProgramLocationTranslator.java @@ -0,0 +1,305 @@ +/* ### + * 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.util.viewer.listingpanel; + +import ghidra.app.util.SymbolPath; +import ghidra.framework.options.SaveState; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.*; +import ghidra.program.util.*; +import ghidra.util.datastruct.Duo.Side; + +/** + * Class for converting a program location from one program to another + */ +public class ProgramLocationTranslator { + + private ListingAddressCorrelation correlator; + + /** + * Constructor given a correlator for translating addresses + * @param correlator converts address from one program to another + */ + public ProgramLocationTranslator(ListingAddressCorrelation correlator) { + this.correlator = correlator; + } + + /** + * Converts a program location from the other side to the given side. + * @param side the side to get a location for + * @param otherSideLocation the location from the other side + * @return a program location for the given side that matches the other given location + */ + public ProgramLocation getProgramLocation(Side side, ProgramLocation otherSideLocation) { + if (correlator == null) { + return null; + } + if (otherSideLocation == null) { + return null; + } + if (otherSideLocation instanceof VariableLocation) { + return getVariableLocation(side, (VariableLocation) otherSideLocation); + } + + SaveState saveState = new SaveState(); + otherSideLocation.saveState(saveState); + Address otherSideAddress = otherSideLocation.getAddress(); + + // Try to get the indicated side's address using one of the address correlators. + Address address = getAddress(side, otherSideAddress); + if (address == null || address == Address.NO_ADDRESS) { + return null; // Couldn't determine the indicated side's address. + } + + saveState.remove("_ADDRESS"); + saveState.putString("_ADDRESS", address.toString()); + + Address byteAddress = otherSideLocation.getByteAddress(); + saveState.remove("_BYTE_ADDR"); + Address desiredByteAddress = null; + Program program = correlator.getProgram(side); + if (byteAddress != null) { + // Try to get the indicated side's byte address using one of the address + // correlators or by inferring it. + desiredByteAddress = + inferDesiredByteAddress(otherSideAddress, address, byteAddress, + otherSideLocation.getProgram(), program); + if (desiredByteAddress != null) { + saveState.putString("_BYTE_ADDR", desiredByteAddress.toString()); + } + } + + // Adjust symbol path for labels if it is part of the location. + adjustSymbolPath(saveState, otherSideAddress, address, byteAddress, desiredByteAddress, + otherSideLocation.getProgram(), program); + + // ref address can't be used with indicated side so remove it. + saveState.remove("_REF_ADDRESS"); + // Don't know how to find equivalent referenced address for the indicated side, + // so don't put any _REF_ADDRESS back. + + return ProgramLocation.getLocation(program, saveState); + + } + + /** + * Gets the matching address for the given side given an address from the other side. This + * method first attempts to translate the address directly. If that fails, it then attempts + * to get an address for the start of the code unit containing the given address because the + * correlator may only have translations for code unit starts. + * + * @param side the LEFT or RIGHT side to get an address for + * @param otherSidesAddress address the address from the other side + * @return the match address for the given side given an address from the other side + */ + private Address getAddress(Side side, Address otherSidesAddress) { + Side otherSide = side.otherSide(); + Address address = correlator.getAddress(side, otherSidesAddress); + + if (address != null) { + return address; + } + // Couldn't directly correlate the address. + CodeUnit otherCodeUnit = + correlator.getProgram(otherSide).getListing().getCodeUnitContaining(otherSidesAddress); + if (otherCodeUnit == null) { + return null; // Can't get the code unit's address. + } + Address otherCodeUnitAddress = otherCodeUnit.getMinAddress(); + return correlator.getAddress(side, otherCodeUnitAddress); + } + + /** + * Gets an matching variable location when given a variable location from the other side. + * + * @param side LEFT or RIGHT indicating which side's variable location is needed. + * @param variableLocation the variable location from the other side. + * @return a variable location for the desired side. Otherwise, null. + */ + private ProgramLocation getVariableLocation(Side side, VariableLocation variableLocation) { + if (variableLocation == null) { + return null; + } + SaveState saveState = new SaveState(); + variableLocation.saveState(saveState); + Address address = variableLocation.getAddress(); + Address byteAddress = variableLocation.getByteAddress(); + Address functionAddress = variableLocation.getFunctionAddress(); + + // Try to get the indicated side's address using one of the address correlators. + Address desiredAddress = getAddress(side, address); + if (desiredAddress == null || desiredAddress == Address.NO_ADDRESS) { + return null; // Couldn't determine the indicated side's address. + } + + // Try to use a byte address. + Address desiredByteAddress = null; + if (byteAddress != null) { + desiredByteAddress = getAddress(side, byteAddress); + } + + Address desiredFunctionAddress = null; + if (functionAddress != null) { + desiredFunctionAddress = getAddress(side, functionAddress); + } + Function function = correlator.getFunction(side); + if ((desiredFunctionAddress == null) && (function != null)) { + // If this is a thunk function get the thunked address. + Function thunkedFunction = function.getThunkedFunction(true); + if (thunkedFunction != null) { + desiredFunctionAddress = thunkedFunction.getEntryPoint(); + } + } + + saveState.remove("_ADDRESS"); + saveState.putString("_ADDRESS", desiredAddress.toString()); + + saveState.remove("_BYTE_ADDR"); + if (desiredByteAddress != null) { + saveState.putString("_BYTE_ADDR", desiredByteAddress.toString()); + } + + saveState.remove("_FUNC_ADDRESS"); + if (desiredFunctionAddress != null) { + saveState.putString("_FUNC_ADDRESS", desiredFunctionAddress.toString()); + } + + // ref address can't be used with indicated side so remove it. + saveState.remove("_REF_ADDRESS"); + // Don't know how to find equivalent referenced address for the indicated side, + // so don't put any _REF_ADDRESS back. + + return ProgramLocation.getLocation(correlator.getProgram(side), saveState); + } + + private void 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. + } + Symbol[] symbols = program.getSymbolTable().getSymbols(symbolAddress); + if (symbols.length == 0) { + return; // no symbols in program for matching. + } + Symbol[] desiredSymbols = desiredProgram.getSymbolTable().getSymbols(desiredSymbolAddress); + if (desiredSymbols.length == 0) { + return; // no symbols in desiredProgram for matching. + } + + int desiredRow = adjustSymbolRow(saveState, symbols, desiredSymbols); + + int desiredIndex = getDesiredSymbolIndex(desiredSymbols, desiredRow); + + // Now get the desired symbol. + Symbol desiredSymbol = desiredSymbols[desiredIndex]; + SymbolPath symbolPath = getSymbolPath(desiredSymbol); + // Set symbol path for desiredProgram in the save state. + saveState.putStrings("_SYMBOL_PATH", symbolPath.asArray()); + } + + private int adjustSymbolRow(SaveState saveState, Symbol[] symbols, Symbol[] desiredSymbols) { + // For now just try to choose the same label index if more than one. + int row = saveState.getInt("_ROW", 0); + int desiredRow = row; + if (desiredRow >= desiredSymbols.length) { + desiredRow = desiredSymbols.length - 1; + } + saveState.putInt("_ROW", desiredRow); + return desiredRow; + } + + private SymbolPath getSymbolPath(Symbol desiredSymbol) { + String label = desiredSymbol.getName(); + Namespace namespace = desiredSymbol.getParentNamespace(); + SymbolPath symbolPath; + if (namespace == null || namespace.isGlobal()) { + symbolPath = new SymbolPath(label); + } + else { + symbolPath = new SymbolPath(new SymbolPath(namespace.getSymbol()), label); + } + return symbolPath; + } + + private int getDesiredSymbolIndex(Symbol[] desiredSymbols, int desiredRow) { + + boolean hasFunction = desiredSymbols[0].getSymbolType().equals(SymbolType.FUNCTION); + + // Get the array index of the desired symbol. + int desiredIndex = 0; // Default to first entry in array. + if (desiredRow >= 0 && desiredRow < desiredSymbols.length) { + desiredIndex = desiredRow; + } + if (hasFunction) { + // Last row in GUI is also first entry in array. + if (desiredIndex == desiredSymbols.length - 1) { + desiredIndex = 0; // Set to function element. + } + else { + desiredIndex++; // Adjust for function element at start of array. + } + } + return desiredIndex; + } + + /** + * Infers a desired byte address based on the specified byteAddress as well as the + * address and desiredAddress that were matched. + * + * @param address matches up with the desiredAddress from the other function/data. + * @param desiredAddress matches up with the address from the other function/data. + * @param byteAddress the byte address that is associated with address + * @param program the program for the address and byteAddress. + * @param desiredProgram the program for the desiredAddress and + * desiredByteAddress. + * @return the desired byte address that matches up with the indicated byteAddress + * or null if it can't be determined. + */ + private Address inferDesiredByteAddress(Address address, Address desiredAddress, + Address byteAddress, Program program, Program desiredProgram) { + + long numBytesIntoCodeUnit = byteAddress.subtract(address); + if (numBytesIntoCodeUnit == 0) { + return desiredAddress; + } + if (numBytesIntoCodeUnit > 0) { + CodeUnit codeUnit = program.getListing().getCodeUnitAt(address); + CodeUnit desiredCodeUnit = desiredProgram.getListing().getCodeUnitAt(desiredAddress); + if (codeUnit != null && desiredCodeUnit != null) { + int desiredCodeUnitLength = desiredCodeUnit.getLength(); + if (numBytesIntoCodeUnit < desiredCodeUnitLength) { + // Position at byte within code unit. + return desiredAddress.add(numBytesIntoCodeUnit); + } + // Otherwise position at last byte of code unit. + return desiredAddress.add(desiredCodeUnitLength - 1); + } + } + return null; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/multilisting/MultiListingLayoutModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/multilisting/MultiListingLayoutModel.java index 22073a03c2..52982ac989 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/multilisting/MultiListingLayoutModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/multilisting/MultiListingLayoutModel.java @@ -321,25 +321,6 @@ public class MultiListingLayoutModel implements ListingModelListener, FormatMode } - /** - * @see ghidra.app.util.viewer.format.FormatModelListener#formatModelAdded(ghidra.app.util.viewer.format.FieldFormatModel) - */ - @Override - public void formatModelAdded(FieldFormatModel model) { - dataChanged(true); - } - - /** - * @see ghidra.app.util.viewer.format.FormatModelListener#formatModelRemoved(ghidra.app.util.viewer.format.FieldFormatModel) - */ - @Override - public void formatModelRemoved(FieldFormatModel model) { - dataChanged(true); - } - - /** - * @see ghidra.app.util.viewer.format.FormatModelListener#formatModelChanged(ghidra.app.util.viewer.format.FieldFormatModel) - */ @Override public void formatModelChanged(FieldFormatModel model) { modelSizeChanged(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/AddressSetComparisonData.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/AddressSetComparisonData.java new file mode 100644 index 0000000000..49d5fc13fa --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/AddressSetComparisonData.java @@ -0,0 +1,80 @@ +/* ### + * 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.util.viewer.util; + +import java.util.Objects; + +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.util.HTMLUtilities; + +/** + * ComparisonData for a generic set of addresses. + */ +public class AddressSetComparisonData implements ComparisonData { + + private Program program; + private AddressSetView addresses; + + public AddressSetComparisonData(Program program, AddressSetView addresses) { + this.program = Objects.requireNonNull(program); + this.addresses = Objects.requireNonNull(addresses); + } + + @Override + public Function getFunction() { + return null; + } + + @Override + public AddressSetView getAddressSet() { + return addresses; + } + + @Override + public Program getProgram() { + return program; + } + + @Override + public String getShortDescription() { + Address minAddress = addresses.getMinAddress(); + Address maxAddress = addresses.getMinAddress(); + if (minAddress == null) { + return "Empty"; + } + return minAddress + ":" + maxAddress; + } + + @Override + public String getDescription() { + StringBuffer buf = new StringBuffer(); + String padStr = HTMLUtilities.spaces(4); + buf.append(padStr); + String programStr = HTMLUtilities.friendlyEncodeHTML(program.getDomainFile().getPathname()); + String specialProgramStr = HTMLUtilities.colorString(FG_COLOR_TITLE, programStr); + buf.append(specialProgramStr); + buf.append(padStr); + return HTMLUtilities.wrapAsHTML(buf.toString()); + } + + @Override + public boolean isEmpty() { + return addresses.isEmpty(); + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonActionContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonActionContext.java index acf9089ca7..f798bc7aa2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonActionContext.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonActionContext.java @@ -20,29 +20,22 @@ import java.awt.Component; import docking.ComponentProvider; import docking.DefaultActionContext; import ghidra.program.model.listing.Function; +import ghidra.util.datastruct.Duo.Side; public abstract class CodeComparisonActionContext extends DefaultActionContext implements CodeComparisonPanelActionContext { - /** - * Constructor with no source component and no context object - * @param provider the ComponentProvider that generated this context. - */ - public CodeComparisonActionContext(ComponentProvider provider) { - super(provider); - } + private CodeComparisonPanel comparisonPanel; - /** - * Constructor with source component and context object - * - * @param provider the ComponentProvider that generated this context. - * @param contextObject an optional contextObject that the ComponentProvider can provide; this - * can be anything that actions wish to later retrieve - * @param sourceComponent an optional source object; this is intended to be the component that - * is the source of the context, usually the focused component + /** + * Constructor + * @param provider the ComponentProvider containing the code comparison panel + * @param panel the CodeComparisonPanel that generated this context + * @param component the focusable component for associated with the comparison panel */ - public CodeComparisonActionContext(ComponentProvider provider, Object contextObject, - Component sourceComponent) { - super(provider, contextObject, sourceComponent); + public CodeComparisonActionContext(ComponentProvider provider, CodeComparisonPanel panel, + Component component) { + super(provider, panel, component); + this.comparisonPanel = panel; } /** @@ -50,13 +43,18 @@ public abstract class CodeComparisonActionContext extends DefaultActionContext * side of the function diff window that isn't active. * @return the function to get information from */ - public abstract Function getSourceFunction(); + public Function getSourceFunction() { + Side activeSide = comparisonPanel.getActiveSide(); + return comparisonPanel.getFunction(activeSide.otherSide()); + } /** * Returns the function that is the target of the info being applied. This will be whichever * side of the function diff window that is active. * @return the function to apply information to */ - public abstract Function getTargetFunction(); - + public Function getTargetFunction() { + Side activeSide = comparisonPanel.getActiveSide(); + return comparisonPanel.getFunction(activeSide); + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonPanel.java index 75da8e4282..546ffb4904 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonPanel.java @@ -15,25 +15,31 @@ */ package ghidra.app.util.viewer.util; -import java.awt.Color; +import static ghidra.app.util.viewer.util.ComparisonData.*; +import static ghidra.util.datastruct.Duo.Side.*; + +import java.awt.*; import java.awt.event.*; +import java.util.ArrayList; +import java.util.List; import javax.swing.*; import javax.swing.border.Border; import docking.ActionContext; import docking.ComponentProvider; -import docking.action.DockingAction; -import docking.widgets.fieldpanel.FieldPanel; -import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; +import docking.action.*; import generic.theme.GThemeDefaults.Colors.Palette; import ghidra.app.plugin.core.functioncompare.FunctionComparisonPanel; import ghidra.framework.plugintool.PluginTool; -import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSetView; -import ghidra.program.model.listing.*; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.util.HTMLUtilities; import ghidra.util.classfinder.ClassSearcher; import ghidra.util.classfinder.ExtensionPoint; +import ghidra.util.datastruct.Duo; +import ghidra.util.datastruct.Duo.Side; /** * The CodeComparisonPanel class should be extended by any class that is to be @@ -42,42 +48,29 @@ import ghidra.util.classfinder.ExtensionPoint; *

* NOTE: ALL CodeComparisonPanel CLASSES MUST END IN * CodeComparisonPanel so they are discoverable by the {@link ClassSearcher} - * @param the type */ -public abstract class CodeComparisonPanel extends JPanel - implements ExtensionPoint, FocusListener { - - // MINIMUM_PANEL_WIDTH is used to establish a minimum panel width for the left or right panel. - // Without it a long title in either panel can cause the split pane's divider to become locked. - protected static final int MINIMUM_PANEL_WIDTH = 50; - - protected static final int LEFT = 0; - protected static final int RIGHT = 1; - private static final Color FOCUS_BORDER_COLOR = Palette.getColor("lightpink"); - protected static final Border FOCUS_BORDER = - BorderFactory.createMatteBorder(3, 3, 3, 3, FOCUS_BORDER_COLOR); - protected static final Border NON_FOCUS_BORDER = BorderFactory.createEmptyBorder(3, 3, 3, 3); - protected static final AddressSetView EMPTY_ADDRESS_SET = new AddressSet(); +public abstract class CodeComparisonPanel 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; + private static final Border NON_ACTIVE_BORDER = BorderFactory.createEmptyBorder(3, 3, 3, 3); + private static final Border ACTIVE_BORDER = + BorderFactory.createMatteBorder(3, 3, 3, 3, ACTIVE_BORDER_COLOR); protected String owner; protected PluginTool tool; - protected JComponent topComp; - protected JComponent bottomComp; - protected TitledPanel[] titlePanels = new TitledPanel[2]; - protected String leftTitlePrefix = ""; - protected String rightTitlePrefix = ""; - protected int currProgramIndex = LEFT; // Side with current focus (LEFT or RIGHT) - protected Program[] programs = new Program[2]; - protected Function[] functions = new Function[2]; - protected Data[] data = new Data[2]; - /** If true, the title of each comparison panel will be shown */ + protected Duo comparisonData = new Duo<>(EMPTY, EMPTY); + private Duo titlePrefixes = new Duo<>("", ""); + private Duo titlePanels = new Duo<>(); + + protected Side activeSide = LEFT; + private JSplitPane splitPane; + private ToggleOrientationAction toggleOrientationAction; + private JComponent northComponent; private boolean showTitles = true; - private boolean syncScrolling = false; - - private T fieldPanelCoordinator; - /** * Constructor * @@ -87,91 +80,83 @@ public abstract class CodeComparisonPanel exten protected CodeComparisonPanel(String owner, PluginTool tool) { this.owner = owner; this.tool = tool; + toggleOrientationAction = new ToggleOrientationAction(getName()); + + // Important! Subclasses must call the build() method instead of calling it here. This is + // to avoid java's constructor ordering problem } /** - * The GUI component for this CodeComparisonPanel + * Displays a comparison of two ComparisonData objects * - * @return the component + * @param left the comparisonData for the left side + * @param right the comparisonData for the right side */ - public abstract JComponent getComponent(); + public void loadComparisons(ComparisonData left, ComparisonData right) { + if (comparisonData.equals(left, right)) { + return; + } + comparisonData = new Duo<>(left, right); + comparisonDataChanged(); + updateTitles(); + } /** - * The title for this code comparison panel - * - * @return the title + * Clears out the current comparisonDatas */ - public abstract String getTitle(); - - /** - * Specifies the two programs to be compared by this panel - * - * @param leftProgram the program for the left side - * @param rightProgram the program for the right side - */ - protected abstract void setPrograms(Program leftProgram, Program rightProgram); - - /** - * Displays a comparison of two program's functions - * - * @param leftFunction the function to show in the left side of the code comparison view - * @param rightFunction the function to show in the right side of the code comparison view - */ - public abstract void loadFunctions(Function leftFunction, Function rightFunction); - - /** - * Displays a comparison of two program's data items - * - * @param leftData the data item to show in the left side of the code comparison view - * @param rightData the data item to show in the right side of the code comparison view - */ - public abstract void loadData(Data leftData, Data rightData); - - /** - * Displays program information for a particular set of addresses in the two programs - * being compared - * - * @param leftProgram the program in the left side of the code comparison view - * @param rightProgram the program in the right side of the code comparison view - * @param leftAddresses the addresses of the program info to show in the left side - * @param rightAddresses the addresses of the program info to show in the right side - */ - public abstract void loadAddresses(Program leftProgram, Program rightProgram, - AddressSetView leftAddresses, AddressSetView rightAddresses); - - /** - * Cleans up resources when this panel is no longer needed - */ - public abstract void dispose(); - - /** - * Enable/disable navigation in this panel using the mouse - * - * @param enabled false disables mouse navigation - */ - public abstract void setMouseNavigationEnabled(boolean enabled); + public void clearComparisons() { + loadComparisons(ComparisonData.EMPTY, ComparisonData.EMPTY); + } /** * Returns the actions for this panel * * @return an array of docking actions */ - public DockingAction[] getActions() { - // No actions currently that appear for each CodeComparisonPanel. - // Classes that extend this class will override this method to get all actions - // specific to that CodeComparisonPanel. - DockingAction[] actions = new DockingAction[] {}; - return actions; + public List getActions() { + List actionList = new ArrayList<>(); + actionList.add(toggleOrientationAction); + return actionList; } - public boolean getShowTitles() { - return showTitles; - } - - public void setShowTitles(boolean showTitles) { + /** + * Toggles whether or not to display data titles for each side. + * @param showTitles true to show data titles + */ + public void setShowDataTitles(boolean showTitles) { this.showTitles = showTitles; } + /** + * Returns true if dual panels are displayed horizontally, false if displayed vertically. + * @return true if dual panels are displayed horizontally, false if displayed vertically + */ + public boolean isSideBySide() { + return toggleOrientationAction.isSelected(); + } + + /** + * Sets the orientation for the dual panels. + * @param b if true, panels will be display horizontally, otherwise vertically + */ + public void setSideBySide(boolean b) { + toggleOrientationAction.setSelected(b); + updateOrientation(); + } + + /** + * Force subclasses to supply a descriptive name. + * + * @return a descriptive name for this panel type + */ + @Override + public abstract String getName(); + + /** + * Cleans up resources when this panel is no longer needed + */ + public abstract void dispose(); + /** * Returns the context object which corresponds to the area of focus within this provider's * component. Null is returned when there is no context. @@ -189,13 +174,41 @@ public abstract class CodeComparisonPanel exten * refreshing itself) to respond to the program changing. * @param program the program that was restored. */ - public abstract void programRestored(Program program); + public void programRestored(Program program) { + updateTitles(); + } /** - * Determines if the left code panel currently has focus. - * @return true if the left side of the code comparison has focus. + * Called when a program is closed. + * @param program the closed program */ - public abstract boolean leftPanelHasFocus(); + public void programClosed(Program program) { + // do nothing by default + } + + /** + * Returns the {@link Side} that is currently active + * @return the {@link Side} that is currently active + */ + public Side getActiveSide() { + return activeSide; + } + + /** + * Sets the component displayed in the top of this panel. + * + * @param component the component. + */ + public void setTopComponent(JComponent component) { + if (northComponent != null) { + remove(northComponent); + } + northComponent = component; + if (northComponent != null) { + add(northComponent, BorderLayout.NORTH); + } + validate(); + } /** * A CodeComparisonPanel should provide a title based on what the code comparison panel @@ -204,157 +217,185 @@ public abstract class CodeComparisonPanel exten * @param leftTitlePrefix the prefix string to prepend to the left panel's title. * @param rightTitlePrefix the prefix string to prepend to the right panel's title. */ - public abstract void setTitlePrefixes(String leftTitlePrefix, String rightTitlePrefix); - - /** - * Gets the program being viewed in the left side of this panel. - * @return the program or null - */ - public Program getLeftProgram() { - return programs[LEFT]; + public void setTitlePrefixes(String leftTitlePrefix, String rightTitlePrefix) { + titlePrefixes = new Duo<>(leftTitlePrefix, rightTitlePrefix); + updateTitles(); } /** - * Gets the program being viewed in the right side of this panel. - * @return the program or null + * Returns the program being shown in the given side. + * @param side the {@link Side} to get the program for + * @return the program for the given side. */ - public Program getRightProgram() { - return programs[RIGHT]; + public Program getProgram(Side side) { + return comparisonData.get(side).getProgram(); } /** - * Gets the function loaded in the left side of this panel. - * @return the function or null + * Returns the function being shown in the given side. + * @param side the {@link Side} to get the function for + * @return the function for the given side. */ - public Function getLeftFunction() { - return functions[LEFT]; + public Function getFunction(Side side) { + return comparisonData.get(side).getFunction(); } /** - * Gets the function loaded in the right side of this panel. - * @return the function or null + * Returns the addresses being shown in the given side. + * @param side the {@link Side} to get the program for + * @return the address set for the given side */ - public Function getRightFunction() { - return functions[RIGHT]; + public AddressSetView getAddresses(Side side) { + return comparisonData.get(side).getAddressSet(); } /** - * Gets the data loaded in the left side of this panel. - * @return the data or null - */ - public Data getLeftData() { - return data[LEFT]; - } - - /** - * Gets the data loaded in the right side of this panel. - * @return the data or null - */ - public Data getRightData() { - return data[RIGHT]; - } - - /** - * Gets the addresses loaded in the left side of this panel. - * @return the addresses or an empty set - */ - public abstract AddressSetView getLeftAddresses(); - - /** - * Gets the addresses loaded in the right side of this panel. - * @return the addresses or an empty set - */ - public abstract AddressSetView getRightAddresses(); - - /** - * Refreshes the left side of this panel. - */ - public abstract void refreshLeftPanel(); - - /** - * Refreshes the right side of this panel. - */ - public abstract void refreshRightPanel(); - - @Override - public void focusLost(FocusEvent e) { - // Do nothing. - } - - /** - * Updates the enablement for any actions created by this code comparison panel. - */ + * Updates the enablement for any actions created by this code comparison panel. + */ public abstract void updateActionEnablement(); - /** - * Sets the coordinator for the two views within this code comparison panel. It coordinates - * their scrolling and location synchronization. - * @param fieldPanelCoordinator the coordinator for the two views - */ - public void setFieldPanelCoordinator(T fieldPanelCoordinator) { - if (this.fieldPanelCoordinator != null) { - this.fieldPanelCoordinator.dispose(); - } - this.fieldPanelCoordinator = fieldPanelCoordinator; - } - - /** - * Gets the current field panel coordinator used to synchronize scrolling between the - * left and right view for this CodeComparisonPanel. - * @return the current FieldPanelCoordinator. Otherwise, null if scrolling is not - * currently synchronized. - */ - protected T getFieldPanelCoordinator() { - return fieldPanelCoordinator; - } - - /** - * Creates a new FieldPanelCoordinator used to synchronize scrolling between the - * left and right view for this CodeComparisonPanel. - * @return a new FieldPanelCoordinator - */ - protected abstract T createFieldPanelCoordinator(); - - /** - * Gets the left field panel for this CodeComparisonPanel. - * @return the left FieldPanel. - */ - public abstract FieldPanel getLeftFieldPanel(); - - /** - * Gets the right field panel for this CodeComparisonPanel. - * @return the right FieldPanel. - */ - public abstract FieldPanel getRightFieldPanel(); - - /** - * Determines if the layouts of the views are synchronized with respect to scrolling and - * location. - * @return true if scrolling is synchronized between the two views. - */ - public final boolean isScrollingSynced() { - return syncScrolling; - } - /** * Sets whether or not scrolling is synchronized. - * @param syncScrolling true means synchronize scrolling and location between the two views. + * @param b true means synchronize scrolling between the two views. */ - public void setScrollingSyncState(boolean syncScrolling) { - if (isScrollingSynced() == syncScrolling) { - return; - } - this.syncScrolling = syncScrolling; + public abstract void setSynchronizedScrolling(boolean b); - // Refresh the left panel. - FieldPanel leftPanel = getLeftFieldPanel(); - leftPanel.validate(); - leftPanel.invalidate(); - // Refresh the right panel. - FieldPanel rightPanel = getRightFieldPanel(); - rightPanel.validate(); - rightPanel.invalidate(); + /** + * Returns the Component for the given {@link Side} + * @param side the Side to its component + * @return the Component for the given {@link Side} + */ + public abstract JComponent getComparisonComponent(Side side); - setFieldPanelCoordinator(syncScrolling ? createFieldPanelCoordinator() : null); + /** + * Notification to subclasses that the comparison data has changed + */ + protected abstract void comparisonDataChanged(); + + private final String getTitle(Side side) { + return comparisonData.get(side).getDescription(); } + + private void updateTitles() { + updateTitle(Side.LEFT); + updateTitle(Side.RIGHT); + } + + private void updateTitle(Side side) { + String title = showTitles ? getTitle(side) : ""; + setTitle(titlePanels.get(side), titlePrefixes.get(side), title); + } + + private void updateOrientation() { + int orientation = toggleOrientationAction.isSelected() ? JSplitPane.HORIZONTAL_SPLIT + : JSplitPane.VERTICAL_SPLIT; + splitPane.setOrientation(orientation); + splitPane.setDividerLocation(0.5); + } + + private void setTitle(TitledPanel titlePanel, String titlePrefix, String title) { + if (!titlePrefix.isEmpty()) { + titlePrefix += " "; // Add a space between prefix and title. + } + String htmlPrefix = ""; + if (title.startsWith(htmlPrefix)) { + titlePanel.setTitleName(htmlPrefix + HTMLUtilities.friendlyEncodeHTML(titlePrefix) + + title.substring(htmlPrefix.length())); + } + else { + titlePanel.setTitleName(titlePrefix + title); + } + } + + protected final void buildPanel() { + setLayout(new BorderLayout()); + + TitledPanel leftPanel = new TitledPanel(getTitle(LEFT), getComparisonComponent(LEFT), 5); + TitledPanel rightPanel = new TitledPanel(getTitle(RIGHT), getComparisonComponent(RIGHT), 5); + titlePanels = new Duo<>(leftPanel, rightPanel); + + // Set the MINIMUM_PANEL_WIDTH for the left and right panel to prevent the split pane's + // divider from becoming locked (can't be moved) due to extra long title names. + titlePanels.get(LEFT).setMinimumSize( + new Dimension(MINIMUM_PANEL_WIDTH, titlePanels.get(LEFT).getMinimumSize().height)); + titlePanels.get(RIGHT).setMinimumSize( + new Dimension(MINIMUM_PANEL_WIDTH, titlePanels.get(RIGHT).getMinimumSize().height)); + + splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, titlePanels.get(LEFT), + titlePanels.get(RIGHT)); + splitPane.setResizeWeight(0.5); + splitPane.setDividerSize(4); + splitPane.setBorder(BorderFactory.createEmptyBorder()); + add(splitPane, BorderLayout.CENTER); + updateOrientation(); + + addMouseAndFocusListeners(LEFT); + addMouseAndFocusListeners(RIGHT); + setActiveSide(LEFT); + } + + private void addMouseAndFocusListeners(Side side) { + JComponent comp = getComparisonComponent(side); + comp.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) { + setActiveSide(side); + updateContextForFocusGained(e.getComponent()); + } + }); + + comp = getComparisonComponent(side); + + MouseAdapter mouseListener = new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + setActiveSide(side); + } + }; + + addMouseListenerRecursively(comp, mouseListener); + } + + private void updateContextForFocusGained(Component component) { + ComponentProvider provider = tool.getWindowManager().getProvider(component); + if (provider != null) { + provider.contextChanged(); + } + + } + + private void addMouseListenerRecursively(Component component, MouseListener listener) { + component.addMouseListener(listener); + if (component instanceof Container container) { + for (int i = 0; i < container.getComponentCount(); i++) { + Component child = container.getComponent(i); + addMouseListenerRecursively(child, listener); + } + } + } + + protected void setActiveSide(Side side) { + activeSide = side; + getComparisonComponent(side).setBorder(ACTIVE_BORDER); + getComparisonComponent(side.otherSide()).setBorder(NON_ACTIVE_BORDER); + } + + private class ToggleOrientationAction extends ToggleDockingAction { + ToggleOrientationAction(String name) { + super(name + " Toggle Orientation", "FunctionComparison"); + setDescription( + "Toggle the layout to be either side by side or one above the other"); + setEnabled(true); + MenuData menuData = + new MenuData(new String[] { "Show " + name + " Side-by-Side" }, "Orientation"); + setMenuBarData(menuData); + setSelected(true); + } + + @Override + public void actionPerformed(ActionContext context) { + updateOrientation(); + } + } + } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonPanelActionContext.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonPanelActionContext.java index bc1bd23282..1082410139 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonPanelActionContext.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/CodeComparisonPanelActionContext.java @@ -15,8 +15,6 @@ */ package ghidra.app.util.viewer.util; -import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; - /** * Action context for a CodeComparisonPanel. */ @@ -26,6 +24,6 @@ public interface CodeComparisonPanelActionContext { * Gets the CodeComparisonPanel associated with this context. * @return the code comparison panel. */ - public abstract CodeComparisonPanel getCodeComparisonPanel(); + public abstract CodeComparisonPanel getCodeComparisonPanel(); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/ComparisonData.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/ComparisonData.java new file mode 100644 index 0000000000..f112453437 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/ComparisonData.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.app.util.viewer.util; + +import java.awt.Color; + +import generic.theme.GThemeDefaults.Colors.Palette; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; + +/** + * ComparisonData is an abstract of items that can be compared in a {@link CodeComparisonPanel}. + * 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. + */ +public interface ComparisonData { + public static final Color FG_COLOR_TITLE = Palette.DARK_GRAY; + public static final ComparisonData EMPTY = new EmptyComparisonData(); + + /** + * Returns the function being compared or null if this comparison data is not function based. + * @return the function being compared or null if this comparison data is not function based + */ + public Function getFunction(); + + /** + * Returns the set of addresses being compared. Currently, all comparisons are address based, + * so this should never be null. + * @return the set of addresses being compared + */ + public AddressSetView getAddressSet(); + + /** + * Returns the program containing the data being compared. + * @return the program containing the data being compared. + */ + public Program getProgram(); + + /** + * Returns a description of the data being compared. + * @return a description of the data being compared. + */ + public String getDescription(); + + /** + * Returns a short description (useful for tab name) + * @return a short description + */ + public String getShortDescription(); + + /** + * Returns true if this comparison has no addresses to compare + * @return true if this comparison has no addresses to compare + */ + public boolean isEmpty(); + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/DataComparisonData.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/DataComparisonData.java new file mode 100644 index 0000000000..c7b80d0925 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/DataComparisonData.java @@ -0,0 +1,111 @@ +/* ### + * 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.util.viewer.util; + +import java.util.Objects; + +import ghidra.program.model.address.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.util.HTMLUtilities; + +/** + * ComparisonData for a Data object + */ +public class DataComparisonData implements ComparisonData { + + private final Data data; + private final AddressSet addresses; + + public DataComparisonData(Data data, int otherLength) { + this.data = Objects.requireNonNull(data); + int size = Math.max(data.getLength(), otherLength); + this.addresses = new AddressSet(data.getMinAddress(), getEndAddress(size)); + } + + @Override + public AddressSetView getAddressSet() { + return addresses; + } + + @Override + public Program getProgram() { + return data.getProgram(); + } + + @Override + public Function getFunction() { + return null; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public String getShortDescription() { + return data.getDataType().getName(); + } + + @Override + public String getDescription() { + StringBuffer buf = new StringBuffer(); + String padStr = HTMLUtilities.spaces(4); + buf.append(padStr); + + String dataLabel = data.getLabel(); + if (dataLabel == null) { // If we can't get a label for the data then use the address . + Address address = data.getAddress(); + dataLabel = address.toString(); + } + String dataStr = HTMLUtilities.friendlyEncodeHTML(dataLabel); + String specialDataStr = HTMLUtilities.bold(dataStr); + buf.append(specialDataStr); + + Program program = data.getProgram(); + if (program != null) { + buf.append(" in "); + + String programStr = + HTMLUtilities.friendlyEncodeHTML(program.getDomainFile().getPathname()); + String specialProgramStr = HTMLUtilities.colorString(FG_COLOR_TITLE, programStr); + buf.append(specialProgramStr); + buf.append(padStr); + } + return HTMLUtilities.wrapAsHTML(buf.toString()); + } + + private Address getEndAddress(int size) { + Address minAddress = data.getMinAddress(); + if (minAddress.isExternalAddress()) { + return minAddress; // Begin and end address are same for external data. + } + MemoryBlock block = data.getProgram().getMemory().getBlock(minAddress); + Address blockEnd = block.getEnd(); + Address endAddress; + try { + endAddress = minAddress.add(size); + if (endAddress.compareTo(blockEnd) > 0) { + endAddress = blockEnd; + } + } + catch (AddressOutOfBoundsException e) { + endAddress = blockEnd; + } + return endAddress; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/EmptyComparisonData.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/EmptyComparisonData.java new file mode 100644 index 0000000000..de6cccf4b2 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/EmptyComparisonData.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.app.util.viewer.util; + +import ghidra.program.model.address.AddressSet; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; + +public class EmptyComparisonData implements ComparisonData { + + @Override + public Function getFunction() { + return null; + } + + @Override + public AddressSetView getAddressSet() { + return new AddressSet(); + } + + @Override + public Program getProgram() { + return null; + } + + @Override + public String getDescription() { + return "No Comparison Data"; + } + + @Override + public String getShortDescription() { + return "Empty"; + } + + @Override + public boolean isEmpty() { + return true; + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/FunctionComparisonData.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/FunctionComparisonData.java new file mode 100644 index 0000000000..9c6a22081b --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/util/FunctionComparisonData.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.app.util.viewer.util; + +import java.util.Objects; + +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.util.HTMLUtilities; + +/** + * ComparisonData for a function + */ +public class FunctionComparisonData implements ComparisonData { + + private final Function function; + + public FunctionComparisonData(Function function) { + this.function = Objects.requireNonNull(function); + } + + @Override + public Function getFunction() { + return function; + } + + @Override + public AddressSetView getAddressSet() { + if (function.isExternal()) { + return new AddressSet(function.getEntryPoint(), function.getEntryPoint()); + } + return function.getBody(); + } + + @Override + public Program getProgram() { + return function.getProgram(); + } + + @Override + public String getDescription() { + StringBuffer buf = new StringBuffer(); + String padStr = HTMLUtilities.spaces(4); + buf.append(padStr); + + String functionStr = HTMLUtilities.friendlyEncodeHTML(function.getName(true) + "()"); + String specialFunctionStr = HTMLUtilities.bold(functionStr); + buf.append(specialFunctionStr); + Program program = function.getProgram(); + if (program != null) { + buf.append(" in "); + + String programStr = + HTMLUtilities.friendlyEncodeHTML(program.getDomainFile().getPathname()); + String specialProgramStr = HTMLUtilities.colorString(FG_COLOR_TITLE, programStr); + buf.append(specialProgramStr); + buf.append(padStr); + } + return HTMLUtilities.wrapAsHTML(buf.toString()); + } + + @Override + public String getShortDescription() { + return function.getName(); + } + + @Override + public boolean isEmpty() { + return false; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ListingDiff.java b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ListingDiff.java index 86f0fef0e6..520fe84fb5 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/util/ListingDiff.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/util/ListingDiff.java @@ -15,6 +15,10 @@ */ package ghidra.program.util; +import static ghidra.util.datastruct.Duo.Side.*; + +import java.util.ArrayList; + import ghidra.app.util.viewer.listingpanel.ListingDiffChangeListener; import ghidra.program.model.address.*; import ghidra.program.model.data.DataType; @@ -22,10 +26,8 @@ import ghidra.program.model.lang.Register; import ghidra.program.model.listing.*; import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.scalar.Scalar; -import ghidra.program.util.ListingAddressCorrelation; import ghidra.util.Msg; - -import java.util.ArrayList; +import ghidra.util.datastruct.Duo.Side; /** * Determines where instructions couldn't be matched and where they differ between sets of @@ -100,15 +102,15 @@ public class ListingDiff { private void getDiffs() throws MemoryAccessException { init(); - AddressSetView addrSet1 = correlation.getAddressesInFirst(); - AddressSetView addrSet2 = correlation.getAddressesInSecond(); - Listing listing1 = correlation.getFirstProgram().getListing(); - Listing listing2 = correlation.getSecondProgram().getListing(); + AddressSetView addrSet1 = correlation.getAddresses(LEFT); + AddressSetView addrSet2 = correlation.getAddresses(RIGHT); + Listing listing1 = correlation.getProgram(LEFT).getListing(); + Listing listing2 = correlation.getProgram(RIGHT).getListing(); CodeUnitIterator cuIter1 = listing1.getCodeUnits(addrSet1, true); CodeUnitIterator cuIter2 = listing2.getCodeUnits(addrSet2, true); for (CodeUnit cu1 : cuIter1) { Address min1 = cu1.getMinAddress(); - Address addr2 = correlation.getAddressInSecond(min1); + Address addr2 = correlation.getAddress(RIGHT, min1); if (addr2 == null) { // Add codeunit1 to the unmatchedDiffs unmatchedCode1.addRange(cu1.getMinAddress(), cu1.getMaxAddress()); @@ -122,7 +124,7 @@ public class ListingDiff { } for (CodeUnit cu2 : cuIter2) { Address min2 = cu2.getMinAddress(); - Address addr1 = correlation.getAddressInFirst(min2); + Address addr1 = correlation.getAddress(LEFT, min2); if (addr1 == null) { // Add codeunit2 to the unmatchedDiffs unmatchedCode2.addRange(cu2.getMinAddress(), cu2.getMaxAddress()); @@ -138,18 +140,18 @@ public class ListingDiff { } private void recomputeCodeUnitDiffs() { - AddressSetView addressSet1 = correlation.getAddressesInFirst(); - AddressSetView addressSet2 = correlation.getAddressesInSecond(); + AddressSetView addressSet1 = correlation.getAddresses(LEFT); + AddressSetView addressSet2 = correlation.getAddresses(RIGHT); AddressSetView matchedAddresses1 = addressSet1.subtract(unmatchedCode1); AddressSetView matchedAddresses2 = addressSet2.subtract(unmatchedCode2); - Listing listing1 = correlation.getFirstProgram().getListing(); - Listing listing2 = correlation.getSecondProgram().getListing(); + Listing listing1 = correlation.getProgram(LEFT).getListing(); + Listing listing2 = correlation.getProgram(RIGHT).getListing(); CodeUnitIterator cuIter1 = listing1.getCodeUnits(matchedAddresses1, true); CodeUnitIterator cuIter2 = listing2.getCodeUnits(matchedAddresses2, true); codeUnitDiffs1.clear(); for (CodeUnit cu1 : cuIter1) { Address min1 = cu1.getMinAddress(); - Address addr2 = correlation.getAddressInSecond(min1); + Address addr2 = correlation.getAddress(RIGHT, min1); if (addr2 == null) { continue; } @@ -160,7 +162,7 @@ public class ListingDiff { codeUnitDiffs2.clear(); for (CodeUnit cu2 : cuIter2) { Address min2 = cu2.getMinAddress(); - Address addr1 = correlation.getAddressInFirst(min2); + Address addr1 = correlation.getAddress(LEFT, min2); if (addr1 == null) { continue; } @@ -386,85 +388,49 @@ public class ListingDiff { } /** - * Gets the addresses in the first listing where matching code couldn't be determined in the + * Gets the address set for unmatched code for the given side * second listing. + * @param side the LEFT or RIGHT side * @return the addresses of the unmatched code in the first listing. */ - public AddressSetView getListing1UnmatchedCode() { - return new AddressSet(unmatchedCode1); - } - - /** - * Gets the addresses in the second listing where matching code couldn't be determined in the - * first listing. - * @return the addresses of the unmatched code in the second listing. - */ - public AddressSetView getListing2UnmatchedCode() { - return new AddressSet(unmatchedCode2); + public AddressSetView getUnmatchedCode(Side side) { + return side == LEFT ? unmatchedCode1 : unmatchedCode2; } /** * Gets the addresses in the first listing where differences were found based on the current * difference settings. + * @param side the side (LEFT or RIGHT) to get the listing diffs for * @return the addresses with differences in the first listing. */ - public AddressSetView getListing1Diffs() { - AddressSet diffs = new AddressSet(getListing1ByteDiffs()); - diffs.add(getListing1CodeUnitDiffs()); - return DiffUtility.getCodeUnitSet(diffs, correlation.getFirstProgram()); - } + public AddressSetView getDiffs(Side side) { - /** - * Gets the addresses in the second listing where differences were found based on the current - * difference settings. - * @return the addresses with differences in the second listing. - */ - public AddressSetView getListing2Diffs() { - AddressSet diffs = new AddressSet(getListing2ByteDiffs()); - diffs.add(getListing2CodeUnitDiffs()); - return DiffUtility.getCodeUnitSet(diffs, correlation.getSecondProgram()); + AddressSet diffs = new AddressSet(getByteDiffs(side)); + diffs.add(getCodeUnitDiffs(side)); + return DiffUtility.getCodeUnitSet(diffs, correlation.getProgram(side)); } /** * Gets the addresses in the first listing where code unit (mnemonic and/or operand) differences * were found based on the current difference settings. + * @param side the side (LEFT or RIGHT) to get the code unit diffs for * @return the addresses with code unit differences in the first listing. */ - public AddressSetView getListing1CodeUnitDiffs() { - return new AddressSet(codeUnitDiffs1); - } - - /** - * Gets the addresses in the second listing where code unit (mnemonic and/or operand) differences - * were found based on the current difference settings. - * @return the addresses with code unit differences in the second listing. - */ - public AddressSetView getListing2CodeUnitDiffs() { - return new AddressSet(codeUnitDiffs2); + public AddressSetView getCodeUnitDiffs(Side side) { + return new AddressSet(side == LEFT ? codeUnitDiffs1 : codeUnitDiffs2); } /** * Gets the addresses in the first listing where byte differences were found based on the * current difference settings. + * @param side the side (LEFT or RIGHT) to get the byte diffs for * @return the addresses with byte differences in the first listing. */ - public AddressSetView getListing1ByteDiffs() { + public AddressSetView getByteDiffs(Side side) { if (ignoreByteDiffs) { return new AddressSet(); } - return new AddressSet(byteDiffs1); - } - - /** - * Gets the addresses in the second listing where byte differences were found based on the - * current difference settings. - * @return the addresses with byte differences in the second listing. - */ - public AddressSetView getListing2ByteDiffs() { - if (ignoreByteDiffs) { - return new AddressSet(); - } - return new AddressSet(byteDiffs2); + return new AddressSet(side == LEFT ? byteDiffs1 : byteDiffs2); } /** @@ -480,9 +446,9 @@ public class ListingDiff { return null; } if (isListing1) { - return correlation.getAddressInSecond(address); + return correlation.getAddress(RIGHT, address); } - return correlation.getAddressInFirst(address); + return correlation.getAddress(LEFT, address); } /** @@ -604,32 +570,26 @@ public class ListingDiff { } /** - * Gets the matching code unit from the other listing for the specified code unit from one + * Gets the matching code unit from the other side listing given a code unit from a given side * of the two listings whose differences this class determines. * @param codeUnit the code unit whose match this determines. - * @param isListing1 true indicates the code unit is from the first listing. false indicates - * it is from the second listing. + * @param side the side the code unit came from * @return the matching code unit or null */ - public CodeUnit getMatchingCodeUnit(CodeUnit codeUnit, boolean isListing1) { + public CodeUnit getMatchingCodeUnit(CodeUnit codeUnit, Side side) { if (correlation == null) { return null; } + Side otherSide = side.otherSide(); + Address minAddress = codeUnit.getMinAddress(); - Program sourceProgram = correlation.getFirstProgram(); - Program destinationProgram = correlation.getSecondProgram(); - if (isListing1) { - Address destination = correlation.getAddressInSecond(minAddress); - if (destination != null) { - return destinationProgram.getListing().getCodeUnitAt(destination); - } - } - else { - Address source = correlation.getAddressInFirst(minAddress); - if (source != null) { - return sourceProgram.getListing().getCodeUnitAt(source); - } + Program otherSideProgram = correlation.getProgram(otherSide); + + Address otherAddress = correlation.getAddress(otherSide, minAddress); + if (otherAddress != null) { + return otherSideProgram.getListing().getCodeUnitAt(otherAddress); } + // code unit not from our programs. return null; } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsSlowTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsSlowTest.java index c53dc39dca..a0e62a627a 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsSlowTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/functioncompare/CompareFunctionsSlowTest.java @@ -15,6 +15,7 @@ */ package ghidra.app.plugin.core.functioncompare; +import static ghidra.util.datastruct.Duo.Side.*; import static org.junit.Assert.*; import java.awt.Window; @@ -149,7 +150,7 @@ public class CompareFunctionsSlowTest extends AbstractGhidraHeadedIntegrationTes assertTrue(prevAction.isEnabledForContext(context)); JPanel rightPanel = - provider.getComponent().getDualListingPanel().getRightPanel().getFieldPanel(); + provider.getComponent().getDualListingPanel().getListingPanel(RIGHT).getFieldPanel(); clickMouse(rightPanel, 1, 30, 30, 1, 0); waitForSwing(); provider.getComponent().updateActionEnablement(); @@ -243,7 +244,7 @@ public class CompareFunctionsSlowTest extends AbstractGhidraHeadedIntegrationTes * Builds a program with 2 functions */ private ProgramBuilder buildTestProgram1() throws Exception { - ProgramBuilder builder = new ProgramBuilder("TestPgm1", ProgramBuilder._TOY_BE); + ProgramBuilder builder = new ProgramBuilder("Program 1", ProgramBuilder._TOY_BE); builder.createMemory(".text", "0x1001000", 0x6600); builder.setProperty(Program.DATE_CREATED, new Date(100000000)); // arbitrary, but consistent @@ -261,7 +262,7 @@ public class CompareFunctionsSlowTest extends AbstractGhidraHeadedIntegrationTes * Builds a program with 1 function */ private ProgramBuilder buildTestProgram2() throws Exception { - ProgramBuilder builder = new ProgramBuilder("TestPgm1", ProgramBuilder._TOY_BE); + ProgramBuilder builder = new ProgramBuilder("Program 2", ProgramBuilder._TOY_BE); builder.createMemory(".text", "0x1001000", 0x6600); builder.setProperty(Program.DATE_CREATED, new Date(100000000)); // arbitrary, but consistent diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/util/viewer/listingpanel/ProgramLocationTranslatorTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/util/viewer/listingpanel/ProgramLocationTranslatorTest.java new file mode 100644 index 0000000000..2978a05f40 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/util/viewer/listingpanel/ProgramLocationTranslatorTest.java @@ -0,0 +1,191 @@ +/* ### + * 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.util.viewer.listingpanel; + +import static ghidra.util.datastruct.Duo.Side.*; +import static org.junit.Assert.*; + +import org.junit.*; + +import generic.test.AbstractGenericTest; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.data.IntegerDataType; +import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.Symbol; +import ghidra.program.util.*; +import ghidra.test.ToyProgramBuilder; +import ghidra.util.datastruct.Duo.Side; + +public class ProgramLocationTranslatorTest extends AbstractGenericTest { + private static long PROGRAM1_BASE = 0x100000; + private static long PROGRAM2_BASE = 0x200000; + + private Program program1; + private Program program2; + private Function function1; + private Function functionA; + private TestAddressCorrelation correlator; + private ProgramLocationTranslator translator; + + @Before + public void setUp() throws Exception { + program1 = createProgram1(); + program2 = createProgram2(); + function1 = program1.getListing().getFunctionAt(addr(program1, PROGRAM1_BASE)); + functionA = program1.getListing().getFunctionAt(addr(program2, PROGRAM2_BASE)); + correlator = new TestAddressCorrelation(); + translator = new ProgramLocationTranslator(correlator); + } + + @After + public void tearDown() { + program1.release(this); + program2.release(this); + } + + @Test + public void testBasicProgramLocation() { + ProgramLocation location = new ProgramLocation(program1, program1.getMinAddress()); + ProgramLocation otherLocation = translator.getProgramLocation(RIGHT, location); + assertEquals(program2, otherLocation.getProgram()); + assertEquals(program2.getMinAddress(), otherLocation.getAddress()); + + ProgramLocation roundTripLocation = translator.getProgramLocation(LEFT, otherLocation); + assertEquals(location, roundTripLocation); + } + + @Test + public void testBytesLocation() { + Address minAddress = program1.getMinAddress(); + Address byteAddress = minAddress.add(1); + ProgramLocation location = + new BytesFieldLocation(program1, minAddress, byteAddress, null, 2); + + ProgramLocation otherLocation = translator.getProgramLocation(RIGHT, location); + assertTrue(otherLocation instanceof BytesFieldLocation); + assertEquals(program2, otherLocation.getProgram()); + assertEquals(program2.getMinAddress(), otherLocation.getAddress()); + assertEquals(program2.getMinAddress().add(1), otherLocation.getByteAddress()); + assertEquals(2, otherLocation.getCharOffset()); + + ProgramLocation roundTripLocation = translator.getProgramLocation(LEFT, otherLocation); + assertEquals(location, roundTripLocation); + + } + + @Test + public void testVariableLocation() { + Function f = program1.getListing().getFunctionAt(addr(program1, PROGRAM1_BASE)); + Variable[] variables = f.getAllVariables(); + VariableLocation location = new VariableLocation(program1, variables[0], 0, 1); + + ProgramLocation otherLocation = translator.getProgramLocation(RIGHT, location); + assertTrue(otherLocation instanceof VariableLocation); + + ProgramLocation roundTripLocation = translator.getProgramLocation(LEFT, otherLocation); + assertEquals(location, roundTripLocation); + + } + + @Test + public void testLableFieldLocation() { + Symbol[] symbols = program1.getSymbolTable().getSymbols(addr(program1, PROGRAM1_BASE)); + assertEquals(1, symbols.length); + LabelFieldLocation location = new LabelFieldLocation(symbols[0], 0, 3); + ProgramLocation otherLocation = translator.getProgramLocation(RIGHT, location); + assertTrue(otherLocation instanceof LabelFieldLocation); + + ProgramLocation roundTripLocation = translator.getProgramLocation(LEFT, otherLocation); + assertEquals(location, roundTripLocation); + } + + private Address addr(Program program, long offset) { + return program.getAddressFactory().getDefaultAddressSpace().getAddress(offset); + } + + private Program createProgram1() throws Exception { + ToyProgramBuilder builder = new ToyProgramBuilder("program1", true, this); + + builder.createMemory("text", Long.toHexString(PROGRAM1_BASE), 0x5000); + buildFunction(builder, "fun1", PROGRAM1_BASE); + buildFunction(builder, "fun2", PROGRAM1_BASE + 0x1000); + buildFunction(builder, "fun3", PROGRAM1_BASE + 0x2000); + + Program program = builder.getProgram(); + + builder.dispose(); + return program; + } + + private Program createProgram2() throws Exception { + ToyProgramBuilder builder = new ToyProgramBuilder("program2", true, this); + + builder.createMemory("text", Long.toHexString(PROGRAM2_BASE), 5000); + buildFunction(builder, "funA", PROGRAM2_BASE); + buildFunction(builder, "funB", PROGRAM2_BASE + 0x1000); + + Program program = builder.getProgram(); + + builder.dispose(); + return program; + } + + private void buildFunction(ToyProgramBuilder builder, String name, long address) + throws Exception { + builder.addBytesFallthrough(address); + builder.addBytesReturn(address + 2); + + String functionStart = Long.toHexString(address); + builder.disassemble(functionStart, 3, true); + Function function = builder.createFunction(functionStart); + Variable var = + new LocalVariableImpl("i", new IntegerDataType(), -0x4, builder.getProgram()); + builder.addFunctionVariable(function, var); + + builder.createLabel(functionStart, name);// function label + } + + private class TestAddressCorrelation implements ListingAddressCorrelation { + + @Override + public Program getProgram(Side side) { + return side == LEFT ? program1 : program2; + } + + @Override + public Function getFunction(Side side) { + return side == LEFT ? function1 : functionA; + } + + @Override + public AddressSetView getAddresses(Side side) { + return side == LEFT ? program1.getMemory() : program2.getMemory(); + } + + @Override + public Address getAddress(Side side, Address otherSideAddress) { + if (side == LEFT) { + long offset = otherSideAddress.getOffset() - PROGRAM2_BASE; + return addr(program1, PROGRAM1_BASE + offset); + } + long offset = otherSideAddress.getOffset() - PROGRAM1_BASE; + return addr(program2, PROGRAM2_BASE + offset); + + } + + } +} diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/DecompilerDiffCodeComparisonPanel.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/DecompilerDiffCodeComparisonPanel.java deleted file mode 100755 index d574f0a0d4..0000000000 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/DecompilerDiffCodeComparisonPanel.java +++ /dev/null @@ -1,262 +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.codecompare; - -import java.util.List; - -import javax.swing.Icon; - -import docking.ActionContext; -import docking.action.DockingAction; -import docking.action.ToggleDockingAction; -import docking.action.ToolBarData; -import generic.theme.GIcon; -import ghidra.app.decompiler.component.DecompileData; -import ghidra.app.decompiler.component.DecompilerCodeComparisonPanel; -import ghidra.app.decompiler.component.DualDecompileResultsListener; -import ghidra.app.decompiler.component.DualDecompilerActionContext; -import ghidra.codecompare.graphanalysis.TokenBin; -import ghidra.framework.options.OptionsChangeListener; -import ghidra.framework.options.ToolOptions; -import ghidra.framework.plugintool.PluginTool; -import ghidra.util.HTMLUtilities; -import ghidra.util.HelpLocation; -import ghidra.util.exception.CancelledException; -import ghidra.util.task.Task; -import ghidra.util.task.TaskBuilder; -import ghidra.util.task.TaskLauncher; -import ghidra.util.task.TaskListener; -import resources.Icons; -import resources.MultiIcon; - -/** - * This is a CodeComparisonPanel that gets discovered by other providers that display dual - * comparison views.
- * Note: Therefore there may not be any other classes that refer directly to it. - */ -public class DecompilerDiffCodeComparisonPanel - extends DecompilerCodeComparisonPanel - implements DualDecompileResultsListener, OptionsChangeListener { - - public static final String CODE_DIFF_VIEW = "Decompiler Diff View"; - private static final String HELP_TOPIC = "FunctionComparison"; - private DecompileDataDiff decompileDataDiff; - private DiffClangHighlightController leftHighlightController; - private DiffClangHighlightController rightHighlightController; - private CodeDiffFieldPanelCoordinator decompilerFieldPanelCoordinator; - private MyToggleExactConstantMatching toggleExactConstantMatchingAction; - private boolean isMatchingConstantsExactly = true; - private boolean toggleFlagWhenLastVisible = isMatchingConstantsExactly; - private CompareFuncsFromMatchedTokensAction compareFuncsAction; - private DecompilerCodeComparisonOptions comparisonOptions; - - /** - * Constructor - * @param owner owner - * @param tool tool - */ - public DecompilerDiffCodeComparisonPanel(String owner, PluginTool tool) { - super(owner, tool); - - comparisonOptions = new DecompilerCodeComparisonOptions(); - initializeOptions(); - leftHighlightController = new DiffClangHighlightController(comparisonOptions); - rightHighlightController = new DiffClangHighlightController(comparisonOptions); - setHighlightControllers(leftHighlightController, rightHighlightController); - - // Make the left highlight listen to the right. - leftHighlightController.addListener(rightHighlightController); - // Make the right highlight listen to the left. - rightHighlightController.addListener(leftHighlightController); - - addDualDecompileResultsListener(this); - decompilerFieldPanelCoordinator = new CodeDiffFieldPanelCoordinator(this); - setFieldPanelCoordinator(decompilerFieldPanelCoordinator); - - } - - private void initializeOptions() { - ToolOptions options = - tool.getOptions(DecompilerCodeComparisonOptions.OPTIONS_CATEGORY_NAME); - options.addOptionsChangeListener(this); - comparisonOptions.registerOptions(options); - comparisonOptions.loadOptions(options); - } - - @Override - public void optionsChanged(ToolOptions options, String optionName, Object oldValue, - Object newValue) { - comparisonOptions.loadOptions(options); - repaint(); - } - - @Override - public void setVisible(boolean aFlag) { - if (aFlag == isVisible()) { - return; - } - if (aFlag) { - // Becoming visible. - if (toggleFlagWhenLastVisible != isMatchingConstantsExactly) { - if (decompileDataDiff != null) { - determineDecompilerDifferences(); - } - } - } - else { - // No longer visible. - toggleFlagWhenLastVisible = isMatchingConstantsExactly; - } - super.setVisible(aFlag); - updateActionEnablement(); - } - - @Override - public String getTitle() { - return CODE_DIFF_VIEW; - } - - @Override - public void decompileResultsSet(DecompileData leftDecompileResults, - DecompileData rightDecompileResults) { - - if ((leftDecompileResults == null) || (rightDecompileResults == null) || - (leftDecompileResults.getFunction() == null) || - (rightDecompileResults.getFunction() == null)) { - return; - } - - decompileDataDiff = new DecompileDataDiff(leftDecompileResults, rightDecompileResults); - determineDecompilerDifferences(); - } - - List getHighBins() { - return decompilerFieldPanelCoordinator.getHighBins(); - } - - private void determineDecompilerDifferences() { - if (decompileDataDiff == null) { - return; - } - DetermineDecompilerDifferencesTask task = - new DetermineDecompilerDifferencesTask(decompileDataDiff, isMatchingConstantsExactly, - leftHighlightController, rightHighlightController, decompilerFieldPanelCoordinator); - - task.addTaskListener(new TaskListener() { - - @Override - public void taskCompleted(Task completedTask) { - // Does this need anything here? - } - - @Override - public void taskCancelled(Task cancelledTask) { - // Does this need anything here? - } - }); - - new TaskLauncher(task, getComponent()); - } - - @Override - protected void createActions() { - super.createActions(); - toggleExactConstantMatchingAction = new MyToggleExactConstantMatching(getClass().getName()); - compareFuncsAction = new CompareFuncsFromMatchedTokensAction(this, tool); - } - - @Override - public DockingAction[] getActions() { - DockingAction[] parentActions = super.getActions(); - DockingAction[] myActions = - new DockingAction[] { toggleExactConstantMatchingAction, compareFuncsAction }; - DockingAction[] allActions = new DockingAction[parentActions.length + myActions.length]; - System.arraycopy(parentActions, 0, allActions, 0, parentActions.length); - System.arraycopy(myActions, 0, allActions, parentActions.length, myActions.length); - return allActions; - } - - @Override - public void updateActionEnablement() { - // Need to enable/disable toolbar button. - toggleExactConstantMatchingAction.setEnabled(isVisible()); - } - - public class MyToggleExactConstantMatching extends ToggleDockingAction { - - private final Icon EXACT_CONSTANT_MATCHING_ICON = new GIcon("icon.base.source.c"); - private final Icon NO_EXACT_CONSTANT_MATCHING_ICON = - new MultiIcon(EXACT_CONSTANT_MATCHING_ICON, Icons.NOT_ALLOWED_ICON); - - /** - * Creates an action for toggling exact constant matching in the code diff's - * dual decompiler. - * @param owner the owner of this action (typically the provider). - */ - public MyToggleExactConstantMatching(String owner) { - super("Toggle Exact Constant Matching", owner); - setHelpLocation(new HelpLocation(HELP_TOPIC, "Toggle Exact Constant Matching")); - - 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 " + CODE_DIFF_VIEW + ".")); - setSelected(false); - setEnabled(true); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - if (context instanceof DualDecompilerActionContext) { - return true; - } - return false; - } - - @Override - public void actionPerformed(ActionContext context) { - isMatchingConstantsExactly = !isSelected(); - if (DecompilerDiffCodeComparisonPanel.this.isVisible()) { - DecompilerDiffCodeComparisonPanel.this.determineDecompilerDifferences(); - } - } - - @Override - public void setSelected(boolean selected) { - getToolBarData().setIcon( - selected ? NO_EXACT_CONSTANT_MATCHING_ICON : EXACT_CONSTANT_MATCHING_ICON); - super.setSelected(selected); - } - } - - @Override - protected CodeDiffFieldPanelCoordinator createFieldPanelCoordinator() { - CodeDiffFieldPanelCoordinator coordinator = new CodeDiffFieldPanelCoordinator(this); - if (decompileDataDiff != null) { - TaskBuilder.withRunnable(monitor -> { - try { - coordinator.replaceDecompileDataDiff(decompileDataDiff, - isMatchingConstantsExactly, monitor); - } - catch (CancelledException e) { - coordinator.clearLineNumberPairing(); - } - }).setTitle("Initializing Code Compare").launchNonModal(); - } - return coordinator; - - } -} diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/DiffClangHighlightController.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/DiffClangHighlightController.java index 86616803be..9582c4b08c 100755 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/DiffClangHighlightController.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/DiffClangHighlightController.java @@ -26,6 +26,7 @@ import generic.theme.GColor; import ghidra.app.decompiler.ClangSyntaxToken; import ghidra.app.decompiler.ClangToken; import ghidra.app.decompiler.component.*; +import ghidra.codecompare.decompile.DecompilerCodeComparisonOptions; import ghidra.codecompare.graphanalysis.TokenBin; import ghidra.util.ColorUtils; import ghidra.util.SystemUtilities; diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/AbstractMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/AbstractMatchedTokensAction.java similarity index 81% rename from Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/AbstractMatchedTokensAction.java rename to Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/AbstractMatchedTokensAction.java index a7cd616b77..f195272ef0 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/AbstractMatchedTokensAction.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/AbstractMatchedTokensAction.java @@ -13,7 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.codecompare; +package ghidra.codecompare.decompile; + +import static ghidra.util.datastruct.Duo.Side.*; import java.util.Iterator; import java.util.List; @@ -22,16 +24,17 @@ import docking.ActionContext; import docking.action.DockingAction; import ghidra.app.decompiler.ClangToken; import ghidra.app.decompiler.DecompilerLocation; -import ghidra.app.decompiler.component.*; +import ghidra.app.decompiler.component.DecompilerPanel; import ghidra.codecompare.graphanalysis.TokenBin; import ghidra.program.model.listing.Program; +import ghidra.util.datastruct.Duo.Side; /** * This is a base class for actions in a {@link DecompilerDiffCodeComparisonPanel} */ public abstract class AbstractMatchedTokensAction extends DockingAction { - protected DecompilerDiffCodeComparisonPanel diffPanel; + protected DecompilerCodeComparisonPanel diffPanel; protected boolean disableOnReadOnly; /** @@ -43,7 +46,7 @@ public abstract class AbstractMatchedTokensAction extends DockingAction { * @param disableOnReadOnly if true, action will be disabled for read-only programs */ public AbstractMatchedTokensAction(String actionName, String owner, - DecompilerDiffCodeComparisonPanel diffPanel, boolean disableOnReadOnly) { + DecompilerCodeComparisonPanel diffPanel, boolean disableOnReadOnly) { super(actionName, owner); this.diffPanel = diffPanel; this.disableOnReadOnly = disableOnReadOnly; @@ -63,26 +66,20 @@ public abstract class AbstractMatchedTokensAction extends DockingAction { if (!(context instanceof DualDecompilerActionContext compareContext)) { return false; } - if (!(compareContext - .getCodeComparisonPanel() instanceof DecompilerCodeComparisonPanel decompPanel)) { - return false; - } + DecompilerCodeComparisonPanel decompPanel = compareContext.getCodeComparisonPanel(); if (disableOnReadOnly) { //get the program corresponding to the panel with focus - Program program = decompPanel.getLeftProgram(); + Side focusedSide = decompPanel.getActiveSide(); + Program program = decompPanel.getProgram(focusedSide); if (program == null) { return false; //panel initializing; don't enable action } - if (!decompPanel.leftPanelHasFocus()) { - program = decompPanel.getRightProgram(); - } if (!program.canSave()) { return false; //program is read-only, don't enable action } } - @SuppressWarnings("unchecked") TokenPair currentPair = getCurrentTokenPair(decompPanel); return enabledForTokens(currentPair); @@ -96,9 +93,9 @@ public abstract class AbstractMatchedTokensAction extends DockingAction { * @return matching tokens (or null if no match) */ protected TokenPair getCurrentTokenPair( - DecompilerCodeComparisonPanel decompPanel) { + DecompilerCodeComparisonPanel decompPanel) { - DecompilerPanel focusedPanel = decompPanel.getFocusedDecompilerPanel().getDecompilerPanel(); + DecompilerPanel focusedPanel = decompPanel.getActiveDisplay().getDecompilerPanel(); if (!(focusedPanel.getCurrentLocation() instanceof DecompilerLocation focusedLocation)) { return null; @@ -126,7 +123,8 @@ public abstract class AbstractMatchedTokensAction extends DockingAction { while (tokenIter.hasNext()) { ClangToken currentMatch = tokenIter.next(); if (currentMatch.getClass().equals(focusedToken.getClass())) { - return decompPanel.leftPanelHasFocus() ? new TokenPair(focusedToken, currentMatch) + return decompPanel.getActiveSide() == LEFT + ? new TokenPair(focusedToken, currentMatch) : new TokenPair(currentMatch, focusedToken); } } diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/CDisplay.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/CDisplay.java new file mode 100644 index 0000000000..83aad0dc5c --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/CDisplay.java @@ -0,0 +1,200 @@ +/* ### + * 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.codecompare.decompile; + +import java.math.BigInteger; +import java.util.function.Consumer; + +import docking.widgets.fieldpanel.FieldPanel; +import docking.widgets.fieldpanel.support.FieldLocation; +import docking.widgets.fieldpanel.support.ViewerPosition; +import ghidra.GhidraOptions; +import ghidra.app.decompiler.DecompileOptions; +import ghidra.app.decompiler.component.*; +import ghidra.codecompare.DiffClangHighlightController; +import ghidra.framework.options.ToolOptions; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; + +/** + * Represents one side of a dual decompiler compare window. It holds the decompiler controller and + * related state information for one side. + */ +public class CDisplay { + private final static String OPTIONS_TITLE = "Decompiler"; + + private DecompilerController controller; + private DecompileOptions decompileOptions; + private FieldLocation lastCursorPosition; + private DiffClangHighlightController highlightController; + private Program program; + + private DecompilerProgramListener programListener; + + public CDisplay(DecompilerCodeComparisonOptions comparisonOptions, + DecompileResultsListener decompileListener, + Consumer locationConsumer) { + + highlightController = new DiffClangHighlightController(comparisonOptions); + + decompileOptions = new DecompileOptions(); + + DecompilerCallbackHandler handler = new DecompilerCallbackHandlerAdapter() { + @Override + public void locationChanged(ProgramLocation programLocation) { + locationConsumer.accept(programLocation); + } + }; + + controller = new DecompilerController(handler, decompileOptions, null) { + @Override + public void setDecompileData(DecompileData decompileData) { + super.setDecompileData(decompileData); + decompileListener.setDecompileData(decompileData); + controller.getDecompilerPanel().validate(); + } + }; + controller.getDecompilerPanel().setHighlightController(highlightController); + + programListener = new DecompilerProgramListener(controller, () -> refresh()); + + } + + public DecompilerPanel getDecompilerPanel() { + return controller.getDecompilerPanel(); + } + + private void updateProgram(PluginTool tool, 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(tool, function); + } + + } + + public void showFunction(PluginTool tool, Function function) { + updateProgram(tool, function); + + lastCursorPosition = null; + if (function == null) { + clearAndShowMessage("No Function"); + return; + } + if (function.isExternal()) { + clearAndShowMessage("\"" + function.getName(true) + "\" is an external function."); + return; + } + + Address entry = function.getEntryPoint(); + ProgramLocation location = new ProgramLocation(program, entry); + controller.display(program, location, new ViewerPosition(0, 0, 0)); + } + + public void clearAndShowMessage(String message) { + controller.setDecompileData(new EmptyDecompileData(message)); + DecompilerPanel decompilerPanel = getDecompilerPanel(); + decompilerPanel.paintImmediately(decompilerPanel.getBounds()); + } + + public void setMouseNavigationEnabled(boolean enabled) { + controller.setMouseNavigationEnabled(enabled); + } + + public void dispose() { + if (program != null) { + program.removeListener(programListener); + program = null; + } + programListener.dispose(); + controller.dispose(); + } + + public DecompilerController getController() { + return controller; + } + + public void refresh() { + saveCursorPosition(); + DecompileData data = getDecompileData(); + if (data != null) { + controller.refreshDisplay(data.getProgram(), data.getLocation(), null); + } + } + + public void programClosed(Program closedProgram) { + if (closedProgram == this.program) { + controller.clear(); + controller.programClosed(closedProgram); + updateProgram(null, null); + } + } + + public boolean isBusy() { + return controller.isDecompiling(); + } + + public DecompileData getDecompileData() { + DecompileData decompileData = controller.getDecompileData(); + if (decompileData instanceof EmptyDecompileData) { + return null; + } + return decompileData; + } + + public void initializeOptions(PluginTool tool, Function function) { + if (tool == null) { + return; + } + 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); + } + + DiffClangHighlightController getHighlightController() { + return highlightController; + } + + private void saveCursorPosition() { + lastCursorPosition = getDecompilerPanel().getFieldPanel().getCursorLocation(); + } + + void restoreCursorPosition() { + if (lastCursorPosition != null) { + BigInteger index = lastCursorPosition.getIndex(); + int fieldNum = lastCursorPosition.getFieldNum(); + int row = lastCursorPosition.getRow(); + int col = lastCursorPosition.getCol(); + + FieldPanel fieldPanel = getDecompilerPanel().getFieldPanel(); + fieldPanel.setCursorPosition(index, fieldNum, row, col); + } + } + +} diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/CodeDiffFieldPanelCoordinator.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/CodeDiffFieldPanelCoordinator.java similarity index 96% rename from Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/CodeDiffFieldPanelCoordinator.java rename to Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/CodeDiffFieldPanelCoordinator.java index 5c3023d913..f3ae591e13 100755 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/CodeDiffFieldPanelCoordinator.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/CodeDiffFieldPanelCoordinator.java @@ -13,7 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.codecompare; +package ghidra.codecompare.decompile; + +import static ghidra.util.datastruct.Duo.Side.*; import java.math.BigInteger; import java.util.ArrayList; @@ -25,7 +27,6 @@ import org.apache.commons.collections4.bidimap.DualHashBidiMap; import docking.widgets.fieldpanel.support.ViewerPosition; import ghidra.app.decompiler.*; import ghidra.app.decompiler.component.DecompilerPanel; -import ghidra.app.decompiler.component.DualDecompilerFieldPanelCoordinator; import ghidra.codecompare.graphanalysis.TokenBin; import ghidra.program.model.pcode.HighFunction; import ghidra.program.util.ProgramLocation; @@ -52,10 +53,10 @@ public class CodeDiffFieldPanelCoordinator extends DualDecompilerFieldPanelCoord * Constructor * @param dualDecompilerPanel decomp comparison panel */ - public CodeDiffFieldPanelCoordinator(DecompilerDiffCodeComparisonPanel dualDecompilerPanel) { + public CodeDiffFieldPanelCoordinator(DecompilerCodeComparisonPanel dualDecompilerPanel) { super(dualDecompilerPanel); - this.leftDecompilerPanel = dualDecompilerPanel.getLeftDecompilerPanel(); - this.rightDecompilerPanel = dualDecompilerPanel.getRightDecompilerPanel(); + this.leftDecompilerPanel = dualDecompilerPanel.getDecompilerPanel(LEFT); + this.rightDecompilerPanel = dualDecompilerPanel.getDecompilerPanel(RIGHT); leftToRightLineNumberPairing = new DualHashBidiMap<>(); } diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/CompareFuncsFromMatchedTokensAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/CompareFuncsFromMatchedTokensAction.java similarity index 90% rename from Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/CompareFuncsFromMatchedTokensAction.java rename to Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/CompareFuncsFromMatchedTokensAction.java index 9fe42406f0..04acb10b91 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/CompareFuncsFromMatchedTokensAction.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/CompareFuncsFromMatchedTokensAction.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.codecompare; +package ghidra.codecompare.decompile; + +import static ghidra.util.datastruct.Duo.Side.*; import docking.ActionContext; import docking.action.MenuData; import ghidra.app.decompiler.ClangFuncNameToken; -import ghidra.app.decompiler.component.DecompilerCodeComparisonPanel; -import ghidra.app.decompiler.component.DualDecompilerActionContext; import ghidra.app.plugin.core.functioncompare.FunctionComparisonProvider; import ghidra.app.services.FunctionComparisonService; import ghidra.framework.plugintool.PluginTool; @@ -45,7 +45,7 @@ public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedTokensAc * @param diffPanel diff Panel * @param tool tool */ - public CompareFuncsFromMatchedTokensAction(DecompilerDiffCodeComparisonPanel diffPanel, + public CompareFuncsFromMatchedTokensAction(DecompilerCodeComparisonPanel diffPanel, PluginTool tool) { super(ACTION_NAME, tool.getName(), diffPanel, false); this.tool = tool; @@ -84,12 +84,8 @@ public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedTokensAc return; } - if (!(compareContext - .getCodeComparisonPanel() instanceof DecompilerCodeComparisonPanel decompPanel)) { - return; - } + DecompilerCodeComparisonPanel decompPanel = compareContext.getCodeComparisonPanel(); - @SuppressWarnings("unchecked") TokenPair currentPair = getCurrentTokenPair(decompPanel); if (currentPair == null || currentPair.leftToken() == null || currentPair.rightToken() == null) { @@ -99,8 +95,8 @@ public class CompareFuncsFromMatchedTokensAction extends AbstractMatchedTokensAc ClangFuncNameToken leftFuncToken = (ClangFuncNameToken) currentPair.leftToken(); ClangFuncNameToken rightFuncToken = (ClangFuncNameToken) currentPair.rightToken(); - Function leftFunction = getFuncFromToken(leftFuncToken, decompPanel.getLeftProgram()); - Function rightFunction = getFuncFromToken(rightFuncToken, decompPanel.getRightProgram()); + Function leftFunction = getFuncFromToken(leftFuncToken, decompPanel.getProgram(LEFT)); + Function rightFunction = getFuncFromToken(rightFuncToken, decompPanel.getProgram(RIGHT)); if (leftFunction == null || rightFunction == null) { return; } diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/DecompileDataDiff.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DecompileDataDiff.java similarity index 99% rename from Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/DecompileDataDiff.java rename to Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DecompileDataDiff.java index 56aa8807ed..3af1359ff6 100755 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/DecompileDataDiff.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DecompileDataDiff.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.codecompare; +package ghidra.codecompare.decompile; import java.util.ArrayList; import java.util.HashSet; diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/DecompilerCodeComparisonOptions.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DecompilerCodeComparisonOptions.java similarity index 83% rename from Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/DecompilerCodeComparisonOptions.java rename to Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DecompilerCodeComparisonOptions.java index 07d5a750b1..9f4d6b08ca 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/DecompilerCodeComparisonOptions.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DecompilerCodeComparisonOptions.java @@ -13,18 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.codecompare; +package ghidra.codecompare.decompile; import java.awt.Color; import generic.theme.GColor; +import ghidra.framework.options.OptionsChangeListener; import ghidra.framework.options.ToolOptions; +import ghidra.framework.plugintool.PluginTool; import ghidra.util.HelpLocation; +import ghidra.util.bean.opteditor.OptionsVetoException; +import utility.function.Callback; +import utility.function.Dummy; /** * This class holds the options for the decompiler diff view. */ -public class DecompilerCodeComparisonOptions { +public class DecompilerCodeComparisonOptions implements OptionsChangeListener { private static final String MATCHING_TOKEN_HIGHLIGHT_KEY = "Focused Token Match Highlight"; private static final String UNMATCHED_TOKEN_HIGHLIGHT_KEY = "Focused Token Unmatched Highlight"; @@ -53,15 +58,18 @@ public class DecompilerCodeComparisonOptions { private Color unmatchedTokenHighlight; private Color ineligibleTokenHighlight; private Color diffHighlight; + private Callback optionsChangedCallback; public static final String OPTIONS_CATEGORY_NAME = "Decompiler Code Comparison"; public static final String HELP_TOPIC = "FunctionComparison"; - /** - * Constructor - */ - public DecompilerCodeComparisonOptions() { - + public DecompilerCodeComparisonOptions(PluginTool tool, Callback optionsChangedCallback) { + this.optionsChangedCallback = Dummy.ifNull(optionsChangedCallback); + ToolOptions options = + tool.getOptions(DecompilerCodeComparisonOptions.OPTIONS_CATEGORY_NAME); + options.addOptionsChangeListener(this); + registerOptions(options); + loadOptions(options); } /** @@ -136,4 +144,11 @@ public class DecompilerCodeComparisonOptions { return diffHighlight; } + @Override + public void optionsChanged(ToolOptions options, String optionName, Object oldValue, + Object newValue) throws OptionsVetoException { + loadOptions(options); + optionsChangedCallback.call(); + } + } diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DecompilerCodeComparisonPanel.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DecompilerCodeComparisonPanel.java new file mode 100644 index 0000000000..b6621b654d --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DecompilerCodeComparisonPanel.java @@ -0,0 +1,395 @@ +/* ### + * 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.codecompare.decompile; + +import static ghidra.util.datastruct.Duo.Side.*; + +import java.awt.Component; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.Icon; +import javax.swing.JComponent; + +import docking.ActionContext; +import docking.ComponentProvider; +import docking.action.*; +import docking.options.OptionsService; +import generic.theme.GIcon; +import ghidra.app.decompiler.component.DecompileData; +import ghidra.app.decompiler.component.DecompilerPanel; +import ghidra.app.util.viewer.util.CodeComparisonPanel; +import ghidra.codecompare.DiffClangHighlightController; +import ghidra.codecompare.graphanalysis.TokenBin; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.util.ProgramLocation; +import ghidra.util.HTMLUtilities; +import ghidra.util.HelpLocation; +import ghidra.util.datastruct.Duo; +import ghidra.util.datastruct.Duo.Side; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskBuilder; +import ghidra.util.task.TaskLauncher; +import resources.Icons; +import resources.MultiIcon; + +/** + * Panel that displays two decompilers for comparison + */ +public class DecompilerCodeComparisonPanel + extends CodeComparisonPanel { + + public static final String NAME = "Decompile Diff View"; + + private boolean isStale = true; + + private Duo cDisplays = new Duo<>(); + + private DecompilerCodeComparisonOptions comparisonOptions; + private CodeDiffFieldPanelCoordinator coordinator; + private DecompileDataDiff decompileDataDiff; + + private ToggleExactConstantMatching toggleExactConstantMatchingAction; + private List actions = new ArrayList<>(); + private Duo functions = new Duo<>(); + + /** + * Creates a comparison panel with two decompilers + * + * @param owner the owner of this panel + * @param tool the tool displaying this panel + */ + public DecompilerCodeComparisonPanel(String owner, PluginTool tool) { + super(owner, tool); + comparisonOptions = new DecompilerCodeComparisonOptions(tool, () -> repaint()); + + cDisplays = new Duo<>(buildCDisplay(LEFT), buildCDisplay(RIGHT)); + cDisplays.get(LEFT).initializeOptions(tool, getFunction(LEFT)); + cDisplays.get(RIGHT).initializeOptions(tool, getFunction(RIGHT)); + + buildPanel(); + setSynchronizedScrolling(true); + linkHighlightControllers(); + createActions(); + } + + @Override + public String getName() { + return NAME; + } + + @Override + protected void comparisonDataChanged() { + + maybeLoadFunction(LEFT, comparisonData.get(LEFT).getFunction()); + maybeLoadFunction(RIGHT, comparisonData.get(RIGHT).getFunction()); + if (coordinator != null) { + coordinator.leftLocationChanged((ProgramLocation) null); + } + } + + public 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); + + } + + @Override + public void dispose() { + setSynchronizedScrolling(false); // disposes any exiting coordinator + cDisplays.each(CDisplay::dispose); + comparisonOptions = null; + } + + /** + * Gets the display from the active side. + * @return the active display + */ + public CDisplay getActiveDisplay() { + return cDisplays.get(activeSide); + } + + /** + * Gets the left side's C display panel. + * @return the left C display panel + */ + public CDisplay getLeftPanel() { + return cDisplays.get(LEFT); + } + + /** + * Gets the right side's C display panel. + * @return the right C display panel + */ + public CDisplay getRightPanel() { + return cDisplays.get(RIGHT); + } + + @Override + public List getActions() { + List allActions = super.getActions(); + allActions.addAll(actions); + return allActions; + } + + @Override + public ActionContext getActionContext(ComponentProvider provider, MouseEvent event) { + + Component component = event != null ? event.getComponent() + : getActiveDisplay().getDecompilerPanel().getFieldPanel(); + + DualDecompilerActionContext dualDecompContext = + new DualDecompilerActionContext(provider, this, component); + + return dualDecompContext; + } + + @Override + public void programClosed(Program program) { + cDisplays.each(c -> c.programClosed(program)); + } + + @Override + public JComponent getComparisonComponent(Side side) { + return cDisplays.get(side).getDecompilerPanel(); + } + + private void createActions() { + toggleExactConstantMatchingAction = new ToggleExactConstantMatching(getClass().getName()); + + actions.add(new DecompilerDiffViewFindAction(owner, tool)); + actions.add(new DecompilerCodeComparisonOptionsAction()); + actions.add(toggleExactConstantMatchingAction); + actions.add(new CompareFuncsFromMatchedTokensAction(this, tool)); + } + + private void decompileDataSet(Side side, DecompileData dcompileData) { + cDisplays.get(side).restoreCursorPosition(); + updateDiffs(); + } + + private boolean isMatchingConstantsExactly() { + return !toggleExactConstantMatchingAction.isSelected(); + } + + private void loadFunction(Side side, Function function) { + if (functions.get(side) != function) { + functions = functions.with(side, function); + cDisplays.get(side).showFunction(tool, function); + } + } + + private void locationChanged(Side side, ProgramLocation location) { + if (coordinator != null) { + if (side == LEFT) { + coordinator.leftLocationChanged(location); + } + else { + coordinator.rightLocationChanged(location); + } + } + } + + private void updateDiffs() { + DecompileData leftDecompileData = cDisplays.get(LEFT).getDecompileData(); + DecompileData rightDecompileData = cDisplays.get(RIGHT).getDecompileData(); + + if (isValid(leftDecompileData) && isValid(rightDecompileData)) { + decompileDataDiff = new DecompileDataDiff(leftDecompileData, rightDecompileData); + determineDecompilerDifferences(); + } + } + + private boolean isValid(DecompileData decompileData) { + return decompileData != null && decompileData.isValid(); + } + + private CDisplay buildCDisplay(Side side) { + return new CDisplay(comparisonOptions, + decompileData -> decompileDataSet(side, decompileData), + l -> locationChanged(side, l)); + } + + private void linkHighlightControllers() { + DiffClangHighlightController left = cDisplays.get(LEFT).getHighlightController(); + DiffClangHighlightController right = cDisplays.get(RIGHT).getHighlightController(); + left.addListener(right); + right.addListener(left); + + } + + @Override + public void setSynchronizedScrolling(boolean synchronize) { + + if (coordinator != null) { + coordinator.dispose(); + coordinator = null; + } + + if (synchronize) { + coordinator = createCoordinator(); + doSynchronize(); + } + } + + private void doSynchronize() { + CDisplay activeDisplay = getActiveDisplay(); + ProgramLocation programLocation = + activeDisplay.getDecompilerPanel().getCurrentLocation(); + if (activeDisplay == cDisplays.get(LEFT)) { + coordinator.leftLocationChanged(programLocation); + } + else { + coordinator.rightLocationChanged(programLocation); + } + } + + private CodeDiffFieldPanelCoordinator createCoordinator() { + CodeDiffFieldPanelCoordinator panelCoordinator = new CodeDiffFieldPanelCoordinator(this); + if (decompileDataDiff != null) { + TaskBuilder.withRunnable(monitor -> { + try { + panelCoordinator.replaceDecompileDataDiff(decompileDataDiff, + isMatchingConstantsExactly(), monitor); + } + catch (CancelledException e) { + panelCoordinator.clearLineNumberPairing(); + } + }).setTitle("Initializing Code Compare").launchNonModal(); + } + return panelCoordinator; + + } + + private class DecompilerCodeComparisonOptionsAction extends DockingAction { + + DecompilerCodeComparisonOptionsAction() { + super("Decompiler Code Comparison Options", owner); + setDescription("Show the tool options for the Decompiler Code Comparison."); + setPopupMenuData(new MenuData(new String[] { "Properties" }, null, "Z_Properties")); + setHelpLocation( + new HelpLocation("FunctionComparison", "Decompiler_Code_Comparison_Options")); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + return (context instanceof DualDecompilerActionContext); + } + + @Override + public void actionPerformed(ActionContext context) { + OptionsService service = tool.getService(OptionsService.class); + service.showOptionsDialog("FunctionComparison", "Decompiler Code Comparison"); + } + } + + @Override + public void setVisible(boolean b) { + super.setVisible(b); + if (b && isStale) { + determineDecompilerDifferences(); + isStale = false; + } + updateActionEnablement(); + } + + List getHighBins() { + return coordinator == null ? null : coordinator.getHighBins(); + } + + private void determineDecompilerDifferences() { + if (decompileDataDiff == null) { + return; + } + DiffClangHighlightController leftHighlights = cDisplays.get(LEFT).getHighlightController(); + DiffClangHighlightController rightHighlights = + cDisplays.get(RIGHT).getHighlightController(); + DetermineDecompilerDifferencesTask task = + new DetermineDecompilerDifferencesTask(decompileDataDiff, isMatchingConstantsExactly(), + leftHighlights, rightHighlights, coordinator); + + new TaskLauncher(task, this); + } + + @Override + public void updateActionEnablement() { + // Need to enable/disable toolbar button. + toggleExactConstantMatchingAction.setEnabled(isVisible()); + } + + public boolean isBusy() { + return cDisplays.get(LEFT).isBusy() || cDisplays.get(RIGHT).isBusy(); + } + + public DecompilerPanel getDecompilerPanel(Side side) { + return cDisplays.get(side).getDecompilerPanel(); + } + + public class ToggleExactConstantMatching extends ToggleDockingAction { + + private final Icon EXACT_CONSTANT_MATCHING_ICON = new GIcon("icon.base.source.c"); + private final Icon NO_EXACT_CONSTANT_MATCHING_ICON = + new MultiIcon(EXACT_CONSTANT_MATCHING_ICON, Icons.NOT_ALLOWED_ICON); + + /** + * Creates an action for toggling exact constant matching in the code diff's + * dual decompiler. + * @param owner the owner of this action (typically the provider). + */ + public ToggleExactConstantMatching(String owner) { + super("Toggle Exact Constant Matching", owner); + setHelpLocation(new HelpLocation(HELP_TOPIC, "Toggle Exact Constant Matching")); + + 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.")); + setSelected(false); + setEnabled(true); + } + + @Override + public boolean isEnabledForContext(ActionContext context) { + if (context instanceof DualDecompilerActionContext) { + return true; + } + return false; + } + + @Override + public void actionPerformed(ActionContext context) { + if (isVisible()) { + determineDecompilerDifferences(); + } + } + + @Override + public void setSelected(boolean selected) { + getToolBarData().setIcon( + selected ? NO_EXACT_CONSTANT_MATCHING_ICON : EXACT_CONSTANT_MATCHING_ICON); + super.setSelected(selected); + } + } +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerDiffViewFindAction.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DecompilerDiffViewFindAction.java similarity index 66% rename from Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerDiffViewFindAction.java rename to Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DecompilerDiffViewFindAction.java index 6e3b89bbd6..5e43233797 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerDiffViewFindAction.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DecompilerDiffViewFindAction.java @@ -13,7 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.app.decompiler.component; +package ghidra.codecompare.decompile; + +import static ghidra.util.datastruct.Duo.Side.*; import java.awt.event.KeyEvent; @@ -23,17 +25,17 @@ import docking.ActionContext; import docking.DockingUtils; import docking.action.*; import docking.widgets.FindDialog; -import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; +import ghidra.app.decompiler.component.DecompilerPanel; import ghidra.app.plugin.core.decompile.actions.DecompilerSearcher; import ghidra.app.util.HelpTopics; -import ghidra.app.util.viewer.util.CodeComparisonPanel; import ghidra.framework.plugintool.PluginTool; import ghidra.util.HelpLocation; +import ghidra.util.datastruct.Duo; +import ghidra.util.datastruct.Duo.Side; public class DecompilerDiffViewFindAction extends DockingAction { - private FindDialog leftFindDialog; - private FindDialog rightFindDialog; + private Duo findDialogs; private PluginTool tool; public DecompilerDiffViewFindAction(String owner, PluginTool tool) { @@ -53,33 +55,21 @@ public class DecompilerDiffViewFindAction extends DockingAction { @Override public boolean isEnabledForContext(ActionContext context) { - if (!(context instanceof DualDecompilerActionContext dualContext)) { - return false; - } - CodeComparisonPanel compPanel = - dualContext.getCodeComparisonPanel(); - if (!(compPanel instanceof DecompilerCodeComparisonPanel decompilerCompPanel)) { - return false; - } - return true; + return (context instanceof DualDecompilerActionContext); } @Override public void actionPerformed(ActionContext context) { DualDecompilerActionContext dualContext = (DualDecompilerActionContext) context; - @SuppressWarnings("unchecked") - DecompilerCodeComparisonPanel decompilerCompPanel = - (DecompilerCodeComparisonPanel) dualContext - .getCodeComparisonPanel(); + DecompilerCodeComparisonPanel decompilerCompPanel = + dualContext.getCodeComparisonPanel(); - DecompilerPanel focusedPanel = decompilerCompPanel.getLeftDecompilerPanel(); - FindDialog dialog = leftFindDialog; - if (!decompilerCompPanel.leftPanelHasFocus()) { - focusedPanel = decompilerCompPanel.getRightDecompilerPanel(); - dialog = rightFindDialog; - } + Side focusedSide = decompilerCompPanel.getActiveSide(); + DecompilerPanel focusedPanel = decompilerCompPanel.getDecompilerPanel(focusedSide); + FindDialog dialog = findDialogs.get(focusedSide); if (dialog == null) { - dialog = createFindDialog(focusedPanel, decompilerCompPanel.leftPanelHasFocus()); + dialog = createFindDialog(focusedPanel, focusedSide); + findDialogs = findDialogs.with(focusedSide, dialog); } String text = focusedPanel.getSelectedText(); @@ -89,8 +79,8 @@ public class DecompilerDiffViewFindAction extends DockingAction { tool.showDialog(dialog); } - private FindDialog createFindDialog(DecompilerPanel decompilerPanel, boolean isLeftFocused) { - String title = (isLeftFocused ? "Left" : "Right"); + private FindDialog createFindDialog(DecompilerPanel decompilerPanel, Side side) { + String title = (side == LEFT ? "Left" : "Right"); title += " Decompiler Find Text"; FindDialog dialog = new FindDialog(title, new DecompilerSearcher(decompilerPanel)) { @@ -102,13 +92,6 @@ public class DecompilerDiffViewFindAction extends DockingAction { }; dialog.setHelpLocation(new HelpLocation(HelpTopics.DECOMPILER, "ActionFind")); - if (isLeftFocused) { - leftFindDialog = dialog; - } - else { - rightFindDialog = dialog; - } - return dialog; } diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/DetermineDecompilerDifferencesTask.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DetermineDecompilerDifferencesTask.java similarity index 96% rename from Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/DetermineDecompilerDifferencesTask.java rename to Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DetermineDecompilerDifferencesTask.java index 6520d029b7..11615891c7 100755 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/DetermineDecompilerDifferencesTask.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DetermineDecompilerDifferencesTask.java @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.codecompare; +package ghidra.codecompare.decompile; import java.util.List; import java.util.Set; import ghidra.app.decompiler.ClangToken; +import ghidra.codecompare.DiffClangHighlightController; import ghidra.codecompare.graphanalysis.TokenBin; import ghidra.util.exception.CancelledException; import ghidra.util.task.Task; diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DualDecompilerActionContext.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DualDecompilerActionContext.java new file mode 100644 index 0000000000..f3939e00ef --- /dev/null +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DualDecompilerActionContext.java @@ -0,0 +1,52 @@ +/* ### + * 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.codecompare.decompile; + +import java.awt.Component; + +import docking.ComponentProvider; +import ghidra.app.context.RestrictedAddressSetContext; +import ghidra.app.util.viewer.util.CodeComparisonActionContext; + +/** + * Action context for a dual decompiler panel. + */ +public class DualDecompilerActionContext extends CodeComparisonActionContext + implements RestrictedAddressSetContext { + + private DecompilerCodeComparisonPanel decompilerComparisonPanel = null; + + /** + * Creates an action context for a dual decompiler panel. + * @param provider the provider for this context + * @param panel the DecompilerComparisonPanel + * @param source the source of the action + */ + public DualDecompilerActionContext(ComponentProvider provider, + DecompilerCodeComparisonPanel panel, Component source) { + super(provider, panel, source); + this.decompilerComparisonPanel = panel; + } + + /** + * Returns the {@link DecompilerCodeComparisonPanel} that generated this context + * @return the decompiler comparison panel that generated this context + */ + @Override + public DecompilerCodeComparisonPanel getCodeComparisonPanel() { + return decompilerComparisonPanel; + } +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DualDecompilerFieldPanelCoordinator.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DualDecompilerFieldPanelCoordinator.java similarity index 78% rename from Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DualDecompilerFieldPanelCoordinator.java rename to Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DualDecompilerFieldPanelCoordinator.java index 58326d4874..de169cb176 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DualDecompilerFieldPanelCoordinator.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/DualDecompilerFieldPanelCoordinator.java @@ -13,7 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.app.decompiler.component; +package ghidra.codecompare.decompile; + +import static ghidra.util.datastruct.Duo.Side.*; import docking.widgets.fieldpanel.FieldPanel; import docking.widgets.fieldpanel.internal.LineLockedFieldPanelCoordinator; @@ -22,9 +24,9 @@ import ghidra.program.util.ProgramLocation; abstract public class DualDecompilerFieldPanelCoordinator extends LineLockedFieldPanelCoordinator { public DualDecompilerFieldPanelCoordinator( - DecompilerCodeComparisonPanel dualDecompilerPanel) { - super(new FieldPanel[] { dualDecompilerPanel.getLeftDecompilerPanel().getFieldPanel(), - dualDecompilerPanel.getRightDecompilerPanel().getFieldPanel() }); + DecompilerCodeComparisonPanel dualDecompilerPanel) { + super(new FieldPanel[] { dualDecompilerPanel.getDecompilerPanel(LEFT).getFieldPanel(), + dualDecompilerPanel.getDecompilerPanel(RIGHT).getFieldPanel() }); } abstract public void leftLocationChanged(ProgramLocation leftLocation); diff --git a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/TokenPair.java b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/TokenPair.java similarity index 94% rename from Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/TokenPair.java rename to Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/TokenPair.java index eab23c7eb0..4b917f038c 100644 --- a/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/TokenPair.java +++ b/Ghidra/Features/CodeCompare/src/main/java/ghidra/codecompare/decompile/TokenPair.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.codecompare; +package ghidra.codecompare.decompile; import ghidra.app.decompiler.ClangToken; diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileResults.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileResults.java index c5da8d2093..a03315940c 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileResults.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/DecompileResults.java @@ -73,6 +73,7 @@ public class DecompileResults { hfunc = null; hparamid = null; docroot = null; + this.processState = processState; //dumpResults(raw); decodeStream(decoder); } @@ -144,6 +145,14 @@ public class DecompileResults { return processState == DecompileProcess.DisposeState.DISPOSED_ON_STARTUP_FAILURE; } + /** + * Returns true if the decompile completed normally + * @return true if the decompile completed normally + */ + public boolean isValid() { + return errMsg == null || errMsg.isBlank(); + } + /** * Return any error message associated with the * decompilation producing these results. Generally, @@ -180,7 +189,7 @@ public class DecompileResults { /** * Get the marked up C code associated with these * decompilation results. If there was an error, or - * code generation was turned off, retur null + * code generation was turned off, return null * @return the resulting root of C markup */ public ClangTokenGroup getCCodeMarkup() { diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/CDisplayPanel.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/CDisplayPanel.java deleted file mode 100644 index ecb389addb..0000000000 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/CDisplayPanel.java +++ /dev/null @@ -1,187 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.app.decompiler.component; - -import java.awt.BorderLayout; - -import javax.swing.JPanel; - -import docking.widgets.fieldpanel.support.ViewerPosition; -import ghidra.app.decompiler.DecompileOptions; -import ghidra.app.plugin.core.decompile.DecompilerClipboardProvider; -import ghidra.app.util.viewer.listingpanel.ProgramLocationListener; -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.bean.field.AnnotatedTextFieldElement; -import utility.function.Callback; - -public class CDisplayPanel extends JPanel implements DecompilerCallbackHandler { - - private DecompilerController controller; - private DecompileResultsListener listener; - - private ProgramLocationListener locationListener; - - public CDisplayPanel(DecompileResultsListener listener) { - this(new DecompileOptions(), listener); - } - - public CDisplayPanel(DecompileOptions decompileOptions, DecompileResultsListener listener) { - super(new BorderLayout()); - this.listener = listener; - controller = new ExtendedDecompilerController(this, decompileOptions, null); - DecompilerPanel decompilerPanel = controller.getDecompilerPanel(); - add(decompilerPanel); - } - - public void setProgramLocationListener(ProgramLocationListener locationListener) { - this.locationListener = locationListener; - } - - class ExtendedDecompilerController extends DecompilerController { - - public ExtendedDecompilerController(DecompilerCallbackHandler handler, - DecompileOptions options, DecompilerClipboardProvider clipboard) { - super(handler, options, clipboard); - } - - @Override - public void setDecompileData(DecompileData decompileData) { - super.setDecompileData(decompileData); - if (listener != null) { - listener.setDecompileData(decompileData); - } - } - - @Override - public void dispose() { - listener = null; - super.dispose(); - } - } - - public DecompilerPanel getDecompilerPanel() { - return controller.getDecompilerPanel(); - } - - public void showFunction(Program program, Address address) { - controller.display(program, new ProgramLocation(program, address), - new ViewerPosition(0, 0, 0)); - } - - public void showFunction(Function function) { - if (function == null) { - clearAndShowMessage("No Function"); - return; - } - if (function.isExternal()) { - clearAndShowMessage("\"" + function.getName(true) + "\" is an external function."); - return; - } - Program program = function.getProgram(); - Address entry = function.getEntryPoint(); - ProgramLocation location = new ProgramLocation(program, entry); - controller.display(program, location, new ViewerPosition(0, 0, 0)); - } - - @Override - public void contextChanged() { - // stub - } - - @Override - public void decompileDataChanged(DecompileData decompileData) { - // stub - } - - @Override - public void exportLocation() { - // stub - } - - @Override - public void annotationClicked(AnnotatedTextFieldElement annotation, boolean newWindow) { - // stub - } - - @Override - public void goToAddress(Address addr, boolean newWindow) { - // stub - } - - @Override - public void goToLabel(String labelName, boolean newWindow) { - // stub - } - - @Override - public void goToScalar(long value, boolean newWindow) { - // stub - } - - @Override - public void goToFunction(Function function, boolean newWindow) { - // stub - } - - @Override - public void locationChanged(ProgramLocation programLocation) { - if (locationListener == null) { - return; - } - this.locationListener.programLocationChanged(programLocation, null); - } - - @Override - public void selectionChanged(ProgramSelection programSelection) { - // stub - } - - @Override - public void setStatusMessage(String message) { - // stub - } - - @Override - public void doWhenNotBusy(Callback c) { - // stub - } - - public void clearAndShowMessage(String message) { - controller.setDecompileData(new EmptyDecompileData(message)); - paintImmediately(getBounds()); - } - - public void setMouseNavigationEnabled(boolean enabled) { - controller.setMouseNavigationEnabled(enabled); - } - - public void dispose() { - controller.dispose(); - } - - public DecompilerController getController() { - return controller; - - } - - public void refresh(DecompileData data) { - controller.refreshDisplay(data.getProgram(), data.getLocation(), null); - } -} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompileData.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompileData.java index e4d243958a..5dadc16673 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompileData.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompileData.java @@ -15,6 +15,9 @@ */ package ghidra.app.decompiler.component; +import java.io.File; + +import docking.widgets.fieldpanel.support.ViewerPosition; import ghidra.app.decompiler.ClangTokenGroup; import ghidra.app.decompiler.DecompileResults; import ghidra.program.model.address.Address; @@ -24,10 +27,6 @@ import ghidra.program.model.listing.Program; import ghidra.program.model.pcode.HighFunction; import ghidra.program.util.ProgramLocation; -import java.io.File; - -import docking.widgets.fieldpanel.support.ViewerPosition; - public class DecompileData { private final Program program; @@ -41,22 +40,26 @@ public class DecompileData { public DecompileData(Program program, Function function, ProgramLocation location, DecompileResults decompileResults, String errorMessage, File debugFile, ViewerPosition viewerPosition) { - this.program = program; - this.function = function; - this.location = location; - this.decompileResults = decompileResults; - this.message = errorMessage; - this.debugFile = debugFile; - this.viewerPosition = viewerPosition; + this.program = program; + this.function = function; + this.location = location; + this.decompileResults = decompileResults; + this.message = errorMessage; + this.debugFile = debugFile; + this.viewerPosition = viewerPosition; } - + public boolean hasDecompileResults() { if (decompileResults == null) { return false; } return decompileResults.getCCodeMarkup() != null; } - + + public boolean isValid() { + return decompileResults != null && decompileResults.isValid(); + } + public DecompileResults getDecompileResults() { return decompileResults; } @@ -75,7 +78,7 @@ public class DecompileData { } return null; } - + public ProgramLocation getLocation() { return location; } @@ -102,7 +105,7 @@ public class DecompileData { } return "Unknown Error"; } - + public File getDebugFile() { return debugFile; } diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCallbackHandlerAdapter.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCallbackHandlerAdapter.java new file mode 100644 index 0000000000..f48ca487da --- /dev/null +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCallbackHandlerAdapter.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.app.decompiler.component; + +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Function; +import ghidra.program.util.ProgramLocation; +import ghidra.program.util.ProgramSelection; +import ghidra.util.bean.field.AnnotatedTextFieldElement; +import utility.function.Callback; + +public class DecompilerCallbackHandlerAdapter implements DecompilerCallbackHandler { + + @Override + public void decompileDataChanged(DecompileData decompileData) { + // stub + } + + @Override + public void contextChanged() { + // stub + } + + @Override + public void setStatusMessage(String message) { + // stub + } + + @Override + public void locationChanged(ProgramLocation programLocation) { + // stub + } + + @Override + public void selectionChanged(ProgramSelection programSelection) { + // stub + } + + @Override + public void annotationClicked(AnnotatedTextFieldElement annotation, boolean newWindow) { + // stub + } + + @Override + public void goToLabel(String labelName, boolean newWindow) { + // stub + } + + @Override + public void goToAddress(Address addr, boolean newWindow) { + // stub + } + + @Override + public void goToScalar(long value, boolean newWindow) { + // stub + } + + @Override + public void exportLocation() { + // stub + } + + @Override + public void goToFunction(Function function, boolean newWindow) { + // stub + } + + @Override + public void doWhenNotBusy(Callback c) { + // stub + } + +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java deleted file mode 100644 index 245bd31d31..0000000000 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerCodeComparisonPanel.java +++ /dev/null @@ -1,821 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.app.decompiler.component; - -import java.awt.*; -import java.awt.event.*; -import java.util.*; - -import javax.swing.*; - -import docking.ActionContext; -import docking.ComponentProvider; -import docking.action.*; -import docking.options.OptionsService; -import docking.widgets.fieldpanel.FieldPanel; -import docking.widgets.fieldpanel.support.FieldLocation; -import docking.widgets.label.GDHtmlLabel; -import ghidra.GhidraOptions; -import ghidra.app.decompiler.DecompileOptions; -import ghidra.app.util.viewer.listingpanel.ProgramLocationListener; -import ghidra.app.util.viewer.util.CodeComparisonPanel; -import ghidra.app.util.viewer.util.TitledPanel; -import ghidra.framework.options.ToolOptions; -import ghidra.framework.plugintool.PluginTool; -import ghidra.program.model.address.AddressSetView; -import ghidra.program.model.listing.*; -import ghidra.program.util.FunctionUtility; -import ghidra.program.util.ProgramLocation; -import ghidra.util.HTMLUtilities; -import ghidra.util.HelpLocation; - -/** - * Panel that displays two decompilers for comparison - */ -public abstract class DecompilerCodeComparisonPanel - extends CodeComparisonPanel { - - private static final String NO_FUNCTION_TITLE = "No Function"; - final static String OPTIONS_TITLE = "Decompiler"; - - private JSplitPane splitPane; - private CDisplayPanel[] cPanels = new CDisplayPanel[2]; - private DualDecompilerFieldPanelCoordinator dualDecompilerCoordinator; - private DecompileData leftDecompileData; - private DecompileData rightDecompileData; - private boolean isMatchingConstantsExactly = true; - private DecompileOptions leftDecompileOptions; - private DecompileOptions rightDecompileOptions; - - private ClangHighlightController[] highlightControllers = new ClangHighlightController[2]; - private ArrayList dualDecompileResultsListenerList = - new ArrayList<>(); - private String leftTitle = NO_FUNCTION_TITLE; - private String rightTitle = NO_FUNCTION_TITLE; - private ProgramLocationListener leftDecompilerLocationListener; - private ProgramLocationListener rightDecompilerLocationListener; - private DecompilerDiffViewFindAction diffViewFindAction; - private boolean isSideBySide = true; - private ToggleOrientationAction toggleOrientationAction; - private DecompilerCodeComparisonOptionsAction decompOptionsAction; - - private DecompilerProgramListener leftProgramListener; - private DecompilerProgramListener rightProgramListener; - - /** - * Creates a comparison panel with two decompilers - * - * @param owner the owner of this panel - * @param tool the tool displaying this panel - */ - public DecompilerCodeComparisonPanel(String owner, PluginTool tool) { - super(owner, tool); - functions = new Function[2]; - - buildPanel(); - loadFunctions(null, null); - - highlightControllers[LEFT] = new LocationClangHighlightController(); - highlightControllers[RIGHT] = new LocationClangHighlightController(); - setHighlightControllers(highlightControllers[LEFT], highlightControllers[RIGHT]); - - initialize(); - } - - private void initialize() { - ToolOptions fieldOptions = tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS); - ToolOptions options = tool.getOptions(OPTIONS_TITLE); - leftDecompileOptions.grabFromToolAndProgram(fieldOptions, options, - (functions[LEFT] != null) ? functions[LEFT].getProgram() : null); - rightDecompileOptions.grabFromToolAndProgram(fieldOptions, options, - (functions[RIGHT] != null) ? functions[RIGHT].getProgram() : null); - setFieldPanelCoordinator(createFieldPanelCoordinator()); - setScrollingSyncState(true); - createActions(); - } - - @Override - public JComponent getComponent() { - return this; - } - - @Override - public String getTitle() { - return "Decompile View"; - } - - @Override - public void setVisible(boolean aFlag) { - super.setVisible(aFlag); - // If actions are added in the future, you may need to update their enablement here. - // The applyFunctionSignatureAction enablement is already handled via context. - } - - private void setTitles(String leftTitle, String rightTitle) { - setLeftTitle(leftTitle); - setRightTitle(rightTitle); - } - - private void setTitle(TitledPanel titlePanel, String titlePrefix, String title) { - if (!titlePrefix.isEmpty()) { - titlePrefix += " "; // Add a space between prefix and title. - } - String htmlPrefix = ""; - if (title.startsWith(htmlPrefix)) { - titlePanel.setTitleName(htmlPrefix + HTMLUtilities.friendlyEncodeHTML(titlePrefix) + - title.substring(htmlPrefix.length())); - } - else { - titlePanel.setTitleName(titlePrefix + title); - } - } - - /** - * Sets the title for the left side's decompiler. - * @param leftTitle the title - */ - public void setLeftTitle(String leftTitle) { - this.leftTitle = leftTitle; - setTitle(titlePanels[LEFT], leftTitlePrefix, leftTitle); - } - - /** - * Sets the title for the right side's decompiler. - * @param rightTitle the title - */ - public void setRightTitle(String rightTitle) { - this.rightTitle = rightTitle; - setTitle(titlePanels[RIGHT], rightTitlePrefix, rightTitle); - } - - private void setTitles(Function leftFunction, Function rightFunction) { - setTitles(getTitleForFunction(leftFunction), getTitleForFunction(rightFunction)); - } - - private String getTitleForFunction(Function function) { - String title = NO_FUNCTION_TITLE; - if (function != null) { - String programName = function.getProgram().getDomainFile().getPathname(); - title = function.getName(true) + " [" + programName + "]"; - } - return title; - } - - public boolean isMatchingConstantsExactly() { - return isMatchingConstantsExactly; - } - - @Override - public void loadFunctions(Function leftFunction, Function rightFunction) { - if (leftFunction == functions[LEFT] && rightFunction == functions[RIGHT]) { - return; - } - - // Clear the scroll info and highlight info to prevent unnecessary highlighting, etc. - if (leftFunction != functions[LEFT]) { - leftDecompileData = null; - } - if (rightFunction != functions[RIGHT]) { - rightDecompileData = null; - } - notifyDecompileResultsListeners(); - - Program leftProgram = (leftFunction != null) ? leftFunction.getProgram() : null; - Program rightProgram = (rightFunction != null) ? rightFunction.getProgram() : null; - setPrograms(leftProgram, rightProgram); - - loadLeftFunction(leftFunction); - loadRightFunction(rightFunction); - - if (getShowTitles()) { - setTitles(leftFunction, rightFunction); - } - else { - setTitles("", ""); - } - if (dualDecompilerCoordinator != null) { - dualDecompilerCoordinator.leftLocationChanged((ProgramLocation) null); - } - } - - private void notifyDecompileResultsListeners() { - // Notify any decompile results listener we have new left or right decompile results. - for (DualDecompileResultsListener listener : dualDecompileResultsListenerList) { - listener.decompileResultsSet(leftDecompileData, rightDecompileData); - } - } - - private void loadLeftFunction(Function function) { - if (function == functions[LEFT]) { - return; - } - functions[LEFT] = function; - cPanels[LEFT].showFunction(function); - } - - private void loadRightFunction(Function function) { - if (function == functions[RIGHT]) { - return; - } - functions[RIGHT] = function; - cPanels[RIGHT].showFunction(function); - } - - private void buildPanel() { - setLayout(new BorderLayout()); - leftDecompileOptions = new DecompileOptions(); - rightDecompileOptions = new DecompileOptions(); -// Options options = tool.getOptions(OPTIONS_TITLE); -// leftDecompileOptions.grabFromToolAndProgram(null, options, -// (functions[LEFT] != null) ? functions[LEFT].getProgram() : null); -// rightDecompileOptions.grabFromToolAndProgram(null, options, -// (functions[LEFT] != null) ? functions[RIGHT].getProgram() : null); - - cPanels[LEFT] = new CDisplayPanel(leftDecompileOptions, - decompileData -> leftDecompileDataSet(decompileData)); - cPanels[RIGHT] = new CDisplayPanel(rightDecompileOptions, - decompileData -> rightDecompileDataSet(decompileData)); - - DecompilerController leftController = cPanels[LEFT].getController(); - leftProgramListener = new DecompilerProgramListener(leftController, () -> refresh(LEFT)); - DecompilerController rightController = cPanels[RIGHT].getController(); - rightProgramListener = new DecompilerProgramListener(rightController, () -> refresh(RIGHT)); - - leftDecompilerLocationListener = (leftLocation, trigger) -> { - if (dualDecompilerCoordinator != null) { - dualDecompilerCoordinator.leftLocationChanged(leftLocation); - } - }; - rightDecompilerLocationListener = (rightLocation, trigger) -> { - if (dualDecompilerCoordinator != null) { - dualDecompilerCoordinator.rightLocationChanged(rightLocation); - } - }; - cPanels[LEFT].setProgramLocationListener(leftDecompilerLocationListener); - cPanels[RIGHT].setProgramLocationListener(rightDecompilerLocationListener); - - // Initialize focus listeners on decompiler panels. - for (int i = 0; i < cPanels.length; i++) { - FieldPanel fieldPanel = cPanels[i].getDecompilerPanel().getFieldPanel(); - fieldPanel.addFocusListener(this); - fieldPanel.addMouseListener(new DualDecompilerMouseListener(i)); - } - setDualPanelFocus(currProgramIndex); - - String leftTitle1 = FunctionUtility.getFunctionTitle(functions[LEFT]); - String rightTitle1 = FunctionUtility.getFunctionTitle(functions[RIGHT]); - - // use mutable labels, as the titles update when functions are selected - GDHtmlLabel leftTitleLabel = new GDHtmlLabel(leftTitle1); - GDHtmlLabel rightTitleLabel = new GDHtmlLabel(rightTitle1); - - titlePanels[LEFT] = new TitledPanel(leftTitleLabel, cPanels[LEFT], 5); - titlePanels[RIGHT] = new TitledPanel(rightTitleLabel, cPanels[RIGHT], 5); - - // Set the MINIMUM_PANEL_WIDTH for the left and right panel to prevent the split pane's - // divider from becoming locked (can't be moved) due to extra long title names. - titlePanels[LEFT].setMinimumSize( - new Dimension(MINIMUM_PANEL_WIDTH, titlePanels[LEFT].getMinimumSize().height)); - titlePanels[RIGHT].setMinimumSize( - new Dimension(MINIMUM_PANEL_WIDTH, titlePanels[RIGHT].getMinimumSize().height)); - - splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, titlePanels[LEFT], - titlePanels[RIGHT]); - splitPane.setResizeWeight(0.5); - splitPane.setDividerSize(4); - splitPane.setBorder(BorderFactory.createEmptyBorder()); - add(splitPane, BorderLayout.CENTER); - } - - private void refresh(int side) { - DecompileData decompileData = side == LEFT ? leftDecompileData : rightDecompileData; - if (decompileData == null) { - return; - } - cPanels[side].refresh(decompileData); - } - - /** - * Adds the indicated listener to be notified when the decompile results have completed. - * @param listener the listener - * @return true if the listener was added. - */ - public boolean addDualDecompileResultsListener(DualDecompileResultsListener listener) { - return dualDecompileResultsListenerList.add(listener); - } - - /** - * Removes the indicated listener from being notified about decompile results. - * @param listener the listener - * @return true if the listener was removed. - */ - public boolean removeDualDecompileResultsListener(DualDecompileResultsListener listener) { - return dualDecompileResultsListenerList.remove(listener); - } - - /** - * Sets the highlight controllers for both decompiler panels. - * @param leftHighlightController the left side's highlight controller - * @param rightHighlightController the right side's highlight controller - */ - public void setHighlightControllers(ClangHighlightController leftHighlightController, - ClangHighlightController rightHighlightController) { - - highlightControllers[LEFT] = leftHighlightController; - highlightControllers[RIGHT] = rightHighlightController; - cPanels[LEFT].getDecompilerPanel().setHighlightController(leftHighlightController); - cPanels[RIGHT].getDecompilerPanel().setHighlightController(rightHighlightController); - } - - /** - * Sets the coordinator for the two decompiler panels within this code comparison panel. - * It coordinates their scrolling and location synchronization. - * @param fieldPanelCoordinator the coordinator for the two decompiler panels - */ - @Override - public void setFieldPanelCoordinator( - DualDecompilerFieldPanelCoordinator fieldPanelCoordinator) { - - if (this.dualDecompilerCoordinator == fieldPanelCoordinator) { - return; - } - if (this.dualDecompilerCoordinator != null) { - this.dualDecompilerCoordinator.dispose(); - cPanels[LEFT].setProgramLocationListener(null); - cPanels[RIGHT].setProgramLocationListener(null); - } - - this.dualDecompilerCoordinator = fieldPanelCoordinator; - - if (fieldPanelCoordinator != null) { - cPanels[LEFT].setProgramLocationListener(leftDecompilerLocationListener); - cPanels[RIGHT].setProgramLocationListener(rightDecompilerLocationListener); - CDisplayPanel focusedDecompilerPanel = getFocusedDecompilerPanel(); - ProgramLocation programLocation = - focusedDecompilerPanel.getDecompilerPanel().getCurrentLocation(); - if (programLocation != null) { - focusedDecompilerPanel.locationChanged(programLocation); - } - } - } - - protected void rightDecompileDataSet(DecompileData decompileData) { - rightDecompileData = decompileData; - notifyDecompileResultsListeners(); - } - - protected void leftDecompileDataSet(DecompileData decompileData) { - leftDecompileData = decompileData; - notifyDecompileResultsListeners(); - } - - /** - * Sets the component displayed in the top of this panel. - * @param comp the component. - */ - public void setTopComponent(JComponent comp) { - if (topComp == comp) { - return; - } - if (topComp != null) { - remove(topComp); - } - topComp = comp; - if (topComp != null) { - add(topComp, BorderLayout.NORTH); - } - validate(); - } - - /** - * Sets the component displayed in the bottom of this panel. - * @param comp the component. - */ - public void setBottomComponent(JComponent comp) { - if (bottomComp == comp) { - return; - } - if (bottomComp != null) { - remove(bottomComp); - } - validate(); // Since we are removing this while the panel is on the screen. - bottomComp = comp; - if (bottomComp != null) { - add(bottomComp, BorderLayout.SOUTH); - } - validate(); // Since we are adding this while the panel is on the screen. - } - - /** - * Gets the display panel from the left or right side that has or last had focus. - * @return the last C display panel with focus - */ - public CDisplayPanel getFocusedDecompilerPanel() { - return cPanels[currProgramIndex]; - } - - /** - * Gets the left side's C display panel. - * @return the left C display panel - */ - public CDisplayPanel getLeftPanel() { - return cPanels[LEFT]; - } - - /** - * Gets the right side's C display panel. - * @return the right C display panel - */ - public CDisplayPanel getRightPanel() { - return cPanels[RIGHT]; - } - - @Override - public void dispose() { - removeProgramListeners(); - setFieldPanelCoordinator(null); - cPanels[LEFT].dispose(); - cPanels[RIGHT].dispose(); - leftProgramListener.dispose(); - rightProgramListener.dispose(); - } - - @Override - public void focusGained(FocusEvent e) { - Component comp = e.getComponent(); - for (int i = 0; i < cPanels.length; i++) { - if (cPanels[i].getDecompilerPanel().getFieldPanel() == comp) { - setDualPanelFocus(i); - } - } - - // Kick the tool so action buttons will be updated - ComponentProvider provider = tool.getWindowManager().getProvider(comp); - if (provider != null) { - provider.contextChanged(); - } - } - - private void setDualPanelFocus(int leftOrRight) { - currProgramIndex = leftOrRight; - cPanels[leftOrRight].setBorder(FOCUS_BORDER); - cPanels[((leftOrRight == LEFT) ? RIGHT : LEFT)].setBorder(NON_FOCUS_BORDER); - } - - @SuppressWarnings("unused") - private void clearBothDisplaysAndShowMessage(String message) { - cPanels[LEFT].clearAndShowMessage(message); - cPanels[RIGHT].clearAndShowMessage(message); - } - - /** - * Disable mouse navigation from within this dual decompiler panel. - * @param enabled false disables navigation - */ - @Override - public void setMouseNavigationEnabled(boolean enabled) { - cPanels[LEFT].setMouseNavigationEnabled(enabled); - cPanels[RIGHT].setMouseNavigationEnabled(enabled); - } - - @Override - protected void setPrograms(Program leftProgram, Program rightProgram) { - removeProgramListeners(); - - ToolOptions fieldOptions = - (tool != null) ? tool.getOptions(GhidraOptions.CATEGORY_BROWSER_FIELDS) : null; - ToolOptions options = (tool != null) ? tool.getOptions(OPTIONS_TITLE) : null; - if (leftProgram != programs[LEFT]) { - programs[LEFT] = leftProgram; - if (options != null) { - leftDecompileOptions.grabFromToolAndProgram(fieldOptions, options, leftProgram); - } - } - if (rightProgram != programs[RIGHT]) { - programs[RIGHT] = rightProgram; - if (options != null) { - rightDecompileOptions.grabFromToolAndProgram(fieldOptions, options, rightProgram); - } - } - addProgramListeners(); - } - - private void addProgramListeners() { - if (programs[LEFT] != null) { - programs[LEFT].addListener(leftProgramListener); - } - if (programs[RIGHT] != null) { - programs[RIGHT].addListener(rightProgramListener); - } - } - - private void removeProgramListeners() { - if (programs[LEFT] != null) { - programs[LEFT].removeListener(leftProgramListener); - } - if (programs[RIGHT] != null) { - programs[RIGHT].removeListener(rightProgramListener); - } - } - - @Override - public void loadData(Data leftData, Data rightData) { - loadFunctions(null, null); - } - - @Override - public void loadAddresses(Program leftProgram, Program rightProgram, - AddressSetView leftAddresses, AddressSetView rightAddresses) { - loadFunctions(null, null); - } - - /** - * Gets the left side's decompiler panel. - * @return the left decompiler panel - */ - public DecompilerPanel getLeftDecompilerPanel() { - return cPanels[LEFT].getDecompilerPanel(); - } - - /** - * Gets the right side's decompiler panel. - * @return the right decompiler panel - */ - public DecompilerPanel getRightDecompilerPanel() { - return cPanels[RIGHT].getDecompilerPanel(); - } - - @Override - public void updateActionEnablement() { - // Nothing to do. - - // applyFunctionSignature enablement is handled by context. - } - - /** - * Creates the actions provided by this panel. - */ - protected void createActions() { - diffViewFindAction = new DecompilerDiffViewFindAction(owner, tool); - toggleOrientationAction = new ToggleOrientationAction(); - decompOptionsAction = new DecompilerCodeComparisonOptionsAction(); - } - - @Override - public DockingAction[] getActions() { - DockingAction[] codeCompActions = super.getActions(); - DockingAction[] otherActions = new DockingAction[] { diffViewFindAction, - toggleOrientationAction, decompOptionsAction }; - int compCount = codeCompActions.length; - int otherCount = otherActions.length; - DockingAction[] actions = new DockingAction[compCount + otherCount]; - System.arraycopy(codeCompActions, 0, actions, 0, compCount); - System.arraycopy(otherActions, 0, actions, compCount, otherCount); - return actions; - } - - /** - * Sets whether or not the decompilers are displayed side by side. - * - * @param sideBySide if true, the decompilers are side by side, otherwise one is above - * the other. - */ - private void showSideBySide(boolean sideBySide) { - isSideBySide = sideBySide; - splitPane.setOrientation( - isSideBySide ? JSplitPane.HORIZONTAL_SPLIT : JSplitPane.VERTICAL_SPLIT); - splitPane.setDividerLocation(0.5); - toggleOrientationAction.setSelected(sideBySide); - } - - private boolean isSideBySide() { - return isSideBySide; - } - - @Override - public ActionContext getActionContext(ComponentProvider provider, MouseEvent event) { - - Component component = event == null ? null : event.getComponent(); - CDisplayPanel focusedDecompilerPanel = getFocusedDecompilerPanel(); - DualDecompilerActionContext dualDecompContext = - new DualDecompilerActionContext(provider, focusedDecompilerPanel, component); - dualDecompContext.setCodeComparisonPanel(this); - return dualDecompContext; - } - - @Override - public void programRestored(Program program) { - Function leftFunction = getLeftFunction(); - Function rightFunction = getRightFunction(); - Program leftProgram = (leftFunction != null) ? leftFunction.getProgram() : null; - Program rightProgram = (rightFunction != null) ? rightFunction.getProgram() : null; - if (leftProgram == program) { - titlePanels[LEFT].setTitleName(FunctionUtility.getFunctionTitle(leftFunction)); - refreshLeftPanel(); - } - if (rightProgram == program) { - titlePanels[RIGHT].setTitleName(FunctionUtility.getFunctionTitle(rightFunction)); - refreshRightPanel(); - } - } - - @SuppressWarnings("unchecked") - private void refreshPanel(int leftOrRight) { - // Hold onto functions for reloading them after the indicated side is cleared, - // because that will have cleared it in the functions array. - Function leftFunction = functions[LEFT]; - Function rightFunction = functions[RIGHT]; - - // Save the location so it can be restored after getting new decompiler results. - FieldLocation leftCursorLocation = - getLeftDecompilerPanel().getFieldPanel().getCursorLocation(); - FieldLocation rightCursorLocation = - getRightDecompilerPanel().getFieldPanel().getCursorLocation(); - - MyDecompileResultsListener listener = - new MyDecompileResultsListener(leftCursorLocation, rightCursorLocation); - - //TEMP FIX - correct when refactoring - // Clear any previous MyDecompileResultsListener that is for a decompiler load - //that hasn't finished. - Set toRemove = new HashSet<>(); - for (DualDecompileResultsListener l : dualDecompileResultsListenerList) { - if (MyDecompileResultsListener.class.isInstance(l)) { - toRemove.add((DecompilerCodeComparisonPanel.MyDecompileResultsListener) l); - } - } - dualDecompileResultsListenerList.removeAll(toRemove); - - // Clear the left or right function by passing null to the load method - // and then reload it below to get it to update. - loadFunctions(((leftOrRight == LEFT) ? null : leftFunction), - ((leftOrRight == RIGHT) ? null : rightFunction)); - - // Setup to restore location to left or right decompiler panel. - addDualDecompileResultsListener(listener); - - // Reload the left or right function to get it to update. - loadFunctions(leftFunction, rightFunction); - } - - /** - * Refreshes the left side of this panel. - */ - @Override - public void refreshLeftPanel() { - refreshPanel(LEFT); - } - - /** - * Refreshes the right side of this panel. - */ - @Override - public void refreshRightPanel() { - refreshPanel(RIGHT); - } - - @Override - public boolean leftPanelHasFocus() { - return currProgramIndex == LEFT; - } - - @Override - public void setTitlePrefixes(String leftTitlePrefix, String rightTitlePrefix) { - this.leftTitlePrefix = leftTitlePrefix; - this.rightTitlePrefix = rightTitlePrefix; - setTitles(leftTitle, rightTitle); - } - - @Override - public AddressSetView getLeftAddresses() { - return (functions[LEFT] != null) ? functions[LEFT].getBody() : EMPTY_ADDRESS_SET; - } - - @Override - public AddressSetView getRightAddresses() { - return (functions[RIGHT] != null) ? functions[RIGHT].getBody() : EMPTY_ADDRESS_SET; - } - - @Override - public FieldPanel getLeftFieldPanel() { - return getLeftDecompilerPanel().getFieldPanel(); - } - - @Override - public FieldPanel getRightFieldPanel() { - return getRightDecompilerPanel().getFieldPanel(); - } - - @Override - protected abstract DualDecompilerFieldPanelCoordinator createFieldPanelCoordinator(); - - private class MyDecompileResultsListener implements DualDecompileResultsListener { - - private FieldLocation leftCursorLocation; - private FieldLocation rightCursorLocation; - - private MyDecompileResultsListener(FieldLocation leftCursorLocation, - FieldLocation rightCursorLocation) { - this.leftCursorLocation = leftCursorLocation; - this.rightCursorLocation = rightCursorLocation; - } - - @Override - public void decompileResultsSet(final DecompileData myLeftDecompileData, - final DecompileData myRightDecompileData) { - SwingUtilities.invokeLater(() -> { - if (myLeftDecompileData != null) { - // The left side may have reloaded with decompiler results, - // so restore the cursor location. - restoreCursor(getLeftDecompilerPanel(), leftCursorLocation); - } - if (myRightDecompileData != null) { - // The right side may have reloaded with decompiler results, - // so restore the cursor location. - restoreCursor(getRightDecompilerPanel(), rightCursorLocation); - } - - // The listener did its job so now remove it. - removeDualDecompileResultsListener(MyDecompileResultsListener.this); - }); - } - - private void restoreCursor(DecompilerPanel decompilerPanel, FieldLocation cursorLocation) { - FieldPanel fieldPanel = decompilerPanel.getFieldPanel(); - FieldLocation currentLocation = fieldPanel.getCursorLocation(); - if (cursorLocation != null && !cursorLocation.equals(currentLocation)) { - fieldPanel.setCursorPosition(cursorLocation.getIndex(), - cursorLocation.getFieldNum(), cursorLocation.getRow(), cursorLocation.getCol()); - } - } - } - - private class DualDecompilerMouseListener extends MouseAdapter { - - private int leftOrRight; - - DualDecompilerMouseListener(int leftOrRight) { - this.leftOrRight = leftOrRight; - } - - @Override - public void mousePressed(MouseEvent e) { - setDualPanelFocus(leftOrRight); - } - } - - private class ToggleOrientationAction extends ToggleDockingAction { - - ToggleOrientationAction() { - super("Dual Decompiler Toggle Orientation", "FunctionComparison"); - setDescription("Toggle the layout of the decompiler " + - "
between side-by-side and one above the other."); - setEnabled(true); - setSelected(isSideBySide); - MenuData menuData = - new MenuData(new String[] { "Show Decompilers Side-by-Side" }, "Dual Decompiler"); - setMenuBarData(menuData); - } - - @Override - public void actionPerformed(ActionContext context) { - boolean sideBySide = !isSideBySide(); - showSideBySide(sideBySide); - } - } - - private class DecompilerCodeComparisonOptionsAction extends DockingAction { - - DecompilerCodeComparisonOptionsAction() { - super("Decompiler Code Comparison Options", owner); - setDescription("Show the tool options for the Decompiler Code Comparison."); - setPopupMenuData(new MenuData(new String[] { "Properties" }, null, "Z_Properties")); - setHelpLocation( - new HelpLocation("FunctionComparison", "Decompiler_Code_Comparison_Options")); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - return (context instanceof DualDecompilerActionContext); - } - - @Override - public void actionPerformed(ActionContext context) { - OptionsService service = tool.getService(OptionsService.class); - service.showOptionsDialog("FunctionComparison", "Decompiler Code Comparison"); - } - } -} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java index 2ccb99e95a..dc1e215efa 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DecompilerPanel.java @@ -1214,6 +1214,18 @@ public class DecompilerPanel extends JPanel implements FieldMouseListener, Field buildPanels(); } + @Override + public synchronized void addFocusListener(FocusListener l) { + // we are not focusable, defer to contained field panel + fieldPanel.addFocusListener(l); + } + + @Override + public synchronized void removeFocusListener(FocusListener l) { + // we are not focusable, defer to contained field panel + fieldPanel.removeFocusListener(l); + } + private void buildPanels() { removeAll(); add(buildLeftComponent(), BorderLayout.WEST); diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DualDecompileResultsListener.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DualDecompileResultsListener.java deleted file mode 100644 index b5b35defa3..0000000000 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DualDecompileResultsListener.java +++ /dev/null @@ -1,26 +0,0 @@ -/* ### - * IP: GHIDRA - * REVIEWED: YES - * - * 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.decompiler.component; - - -public interface DualDecompileResultsListener { - -// void setProvider(DecompilerFunctionComparisonProvider provider); - - void decompileResultsSet(DecompileData leftDecompileData, DecompileData rightDecompileData); - -} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DualDecompilerActionContext.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DualDecompilerActionContext.java deleted file mode 100644 index 5a4ae7abe5..0000000000 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/DualDecompilerActionContext.java +++ /dev/null @@ -1,75 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.app.decompiler.component; - -import java.awt.Component; - -import docking.ComponentProvider; -import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; -import ghidra.app.context.RestrictedAddressSetContext; -import ghidra.app.util.viewer.util.CodeComparisonActionContext; -import ghidra.app.util.viewer.util.CodeComparisonPanel; -import ghidra.program.model.listing.Function; - -/** - * Action context for a dual decompiler panel. - */ -public class DualDecompilerActionContext extends CodeComparisonActionContext - implements RestrictedAddressSetContext { - - private CodeComparisonPanel codeComparisonPanel = null; - - /** - * Creates an action context for a dual decompiler panel. - * @param provider the provider for this context - * @param cPanel the decompiler panel associated with this context - * @param source the source of the action - */ - public DualDecompilerActionContext(ComponentProvider provider, CDisplayPanel cPanel, - Component source) { - super(provider, cPanel, source); - } - - /** - * Sets the CodeComparisonPanel associated with this context. - * @param codeComparisonPanel the code comparison panel. - */ - public void setCodeComparisonPanel( - CodeComparisonPanel codeComparisonPanel) { - this.codeComparisonPanel = codeComparisonPanel; - } - - @Override - public CodeComparisonPanel getCodeComparisonPanel() { - return codeComparisonPanel; - } - - @Override - public Function getSourceFunction() { - boolean leftHasFocus = codeComparisonPanel.leftPanelHasFocus(); - - return leftHasFocus ? codeComparisonPanel.getRightFunction() - : codeComparisonPanel.getLeftFunction(); - } - - @Override - public Function getTargetFunction() { - boolean leftHasFocus = codeComparisonPanel.leftPanelHasFocus(); - - return leftHasFocus ? codeComparisonPanel.getLeftFunction() - : codeComparisonPanel.getRightFunction(); - } -} diff --git a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/correlator/address/VTHashedFunctionAddressCorrelation.java b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/correlator/address/VTHashedFunctionAddressCorrelation.java index 85d5eaab0d..6902dec99d 100644 --- a/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/correlator/address/VTHashedFunctionAddressCorrelation.java +++ b/Ghidra/Features/VersionTracking/src/main/java/ghidra/feature/vt/api/correlator/address/VTHashedFunctionAddressCorrelation.java @@ -15,11 +15,13 @@ */ package ghidra.feature.vt.api.correlator.address; +import static ghidra.util.datastruct.Duo.Side.*; + import ghidra.program.model.address.*; import ghidra.program.model.correlate.HashedFunctionAddressCorrelation; import ghidra.program.model.listing.Function; import ghidra.program.model.mem.MemoryAccessException; -import ghidra.program.util.AddressCorrelation; +import ghidra.program.util.*; import ghidra.util.Msg; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -35,14 +37,15 @@ public class VTHashedFunctionAddressCorrelation implements AddressCorrelation { private final Function sourceFunction; private final Function destinationFunction; - private HashedFunctionAddressCorrelation addressCorrelation; + private ListingAddressCorrelation addressCorrelation; /** * Constructs an address correlation between two functions. * @param sourceFunction the source function * @param destinationFunction the destination function */ - public VTHashedFunctionAddressCorrelation(Function sourceFunction, Function destinationFunction) { + public VTHashedFunctionAddressCorrelation(Function sourceFunction, + Function destinationFunction) { this.sourceFunction = sourceFunction; this.destinationFunction = destinationFunction; addressCorrelation = null; @@ -58,7 +61,7 @@ public class VTHashedFunctionAddressCorrelation implements AddressCorrelation { throws CancelledException { try { initializeCorrelation(monitor); - Address destinationAddress = addressCorrelation.getAddressInSecond(sourceAddress); + Address destinationAddress = addressCorrelation.getAddress(RIGHT, sourceAddress); if (destinationAddress == null) { return null; // No matching destination. } @@ -83,8 +86,13 @@ public class VTHashedFunctionAddressCorrelation implements AddressCorrelation { if (addressCorrelation != null) { return; } - addressCorrelation = + if (sourceFunction != null && destinationFunction != null) { + addressCorrelation = new HashedFunctionAddressCorrelation(sourceFunction, destinationFunction, - monitor); + monitor); + } + else { + addressCorrelation = new DummyListingAddressCorrelation(); + } } } 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 c4b2d0b838..be29631133 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 @@ -15,6 +15,8 @@ */ package ghidra.feature.vt.gui.duallisting; +import static ghidra.util.datastruct.Duo.Side.*; + import java.awt.Point; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; @@ -32,12 +34,11 @@ import ghidra.program.model.address.Address; import ghidra.program.util.ProgramLocation; import ghidra.util.Msg; import ghidra.util.SystemUtilities; +import ghidra.util.datastruct.Duo; public class VTDualListingDragNDropHandler implements Draggable, Droppable { - private final int SOURCE = 0; // left side - private final int DESTINATION = 1; // right side - private ListingPanel[] listingPanels = new ListingPanel[2]; + private Duo listingPanels; private VTController controller; ListingCodeComparisonPanel dualListingPanel; @@ -55,8 +56,9 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable { ListingCodeComparisonPanel dualListingPanel) { this.controller = controller; this.dualListingPanel = dualListingPanel; - listingPanels[SOURCE] = dualListingPanel.getLeftPanel(); - listingPanels[DESTINATION] = dualListingPanel.getRightPanel(); + ListingPanel leftPanel = dualListingPanel.getListingPanel(LEFT); + ListingPanel rightPanel = dualListingPanel.getListingPanel(RIGHT); + listingPanels = new Duo<>(leftPanel, rightPanel); setUpDragDrop(); } @@ -68,7 +70,7 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable { dragSource = DragSource.getDefaultDragSource(); dragGestureAdapter = new DragGestureAdapter(this); dragSourceAdapter = new DragSrcAdapter(this); - dragSource.createDefaultDragGestureRecognizer(listingPanels[SOURCE].getFieldPanel(), + dragSource.createDefaultDragGestureRecognizer(listingPanels.get(LEFT).getFieldPanel(), dragAction, dragGestureAdapter); } @@ -79,7 +81,7 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable { // set up the destination fieldPanel as a drop target that accepts mark-up items. dropTargetAdapter = new DropTgtAdapter(this, DnDConstants.ACTION_COPY_OR_MOVE, acceptableFlavors); - dropTarget = new DropTarget(listingPanels[DESTINATION].getFieldPanel(), + dropTarget = new DropTarget(listingPanels.get(RIGHT).getFieldPanel(), DnDConstants.ACTION_COPY_OR_MOVE, dropTargetAdapter, true); dropTarget.setActive(true); } @@ -100,13 +102,14 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable { @Override public boolean isStartDragOk(DragGestureEvent e) { - if (!listingPanels[SOURCE].isStartDragOk()) { + if (!listingPanels.get(LEFT).isStartDragOk()) { return false; } Point p = e.getDragOrigin(); - ProgramLocation programLocation = listingPanels[SOURCE].getProgramLocation(p); - VTMarkupItem markupItem = controller.getCurrentMarkupForLocation(programLocation, - dualListingPanel.getLeftProgram()); + ProgramLocation programLocation = listingPanels.get(LEFT).getProgramLocation(p); + VTMarkupItem markupItem = + controller.getCurrentMarkupForLocation(programLocation, + dualListingPanel.getProgram(LEFT)); if (markupItem == null) { return false; } @@ -122,13 +125,13 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable { @Override public Transferable getTransferable(Point p) { - if (!listingPanels[SOURCE].contains(p)) { + if (!listingPanels.get(LEFT).contains(p)) { return null; } - ProgramLocation programLocation = listingPanels[SOURCE].getProgramLocation(p); + ProgramLocation programLocation = listingPanels.get(LEFT).getProgramLocation(p); VTMarkupItem markupItem = controller.getCurrentMarkupForLocation(programLocation, - dualListingPanel.getLeftProgram()); + dualListingPanel.getProgram(LEFT)); if (markupItem == null) { return null; } @@ -145,10 +148,10 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable { VTMarkupItem markupItem = (VTMarkupItem) obj; VTMarkupType markupType = markupItem.getMarkupType(); Point p = event.getLocation(); - ProgramLocation loc = listingPanels[DESTINATION].getProgramLocation(p); + ProgramLocation loc = listingPanels.get(RIGHT).getProgramLocation(p); Address newDestinationAddress = - markupType.getAddress(loc, dualListingPanel.getRightProgram()); + markupType.getAddress(loc, dualListingPanel.getProgram(RIGHT)); if (newDestinationAddress == null) { Msg.showInfo(getClass(), dualListingPanel, "Invalid Drop Location", markupType.getDisplayName() + " was not dropped at a valid location."); @@ -166,11 +169,6 @@ public class VTDualListingDragNDropHandler implements Draggable, Droppable { ArrayList arrayList = new ArrayList(); arrayList.add(markupItem); - // Use the following if you only want to set the address. -// SetMarkupItemDestinationAddressTask task = -// new SetMarkupItemDestinationAddressTask(controller.getSession(), arrayList, -// newDestinationAddress); - // Use the following if you want to set the address and apply the markup item using the default action. ApplyMarkupAtDestinationAddressTask task = new ApplyMarkupAtDestinationAddressTask( controller.getSession(), arrayList, newDestinationAddress, controller.getOptions()); 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 48eda84392..edaff79d26 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 @@ -16,7 +16,6 @@ package ghidra.feature.vt.gui.duallisting; import docking.ComponentProvider; -import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; import ghidra.app.context.ListingActionContext; import ghidra.app.nav.Navigatable; import ghidra.app.util.viewer.util.CodeComparisonPanel; @@ -28,7 +27,7 @@ import ghidra.app.util.viewer.util.CodeComparisonPanelActionContext; public class VTListingContext extends ListingActionContext implements CodeComparisonPanelActionContext { - private CodeComparisonPanel codeComparisonPanel = null; + private CodeComparisonPanel codeComparisonPanel = null; /** * Creates an action context for a VT listing. @@ -44,12 +43,12 @@ public class VTListingContext extends ListingActionContext * @param codeComparisonPanel the code comparison panel. */ public void setCodeComparisonPanel( - CodeComparisonPanel codeComparisonPanel) { + CodeComparisonPanel codeComparisonPanel) { this.codeComparisonPanel = codeComparisonPanel; } @Override - public CodeComparisonPanel getCodeComparisonPanel() { + public CodeComparisonPanel getCodeComparisonPanel() { return codeComparisonPanel; } } 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 90080aa64f..f54883be0e 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 @@ -92,13 +92,7 @@ public class VTListingNavigator implements Navigatable { @Override public boolean goTo(Program program, ProgramLocation location) { - boolean went = listingPanel.goTo(location); - // If we tried to go but couldn't, try again after showing entire listing. - if (!went && !dualListingPanel.isEntireListingShowing()) { - dualListingPanel.showEntireListing(true); - return listingPanel.goTo(location); - } - return went; + return listingPanel.goTo(location); } @Override @@ -147,7 +141,8 @@ public class VTListingNavigator implements Navigatable { } @Override - public void removeHighlightProvider(ListingHighlightProvider highlightProvider, Program program) { + public void removeHighlightProvider(ListingHighlightProvider highlightProvider, + Program program) { // currently unsupported } 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 e9eb6c9a2f..e1f0b9c040 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 @@ -16,6 +16,7 @@ package ghidra.feature.vt.gui.provider.functionassociation; import static ghidra.feature.vt.gui.provider.functionassociation.FilterSettings.*; +import static ghidra.util.datastruct.Duo.Side.*; import java.awt.*; import java.awt.event.*; @@ -220,7 +221,7 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel(); if (dualListingPanel != null) { - ListingPanel leftPanel = dualListingPanel.getLeftPanel(); + ListingPanel leftPanel = dualListingPanel.getListingPanel(LEFT); return leftPanel.getHeaderActions(getName()); } } @@ -261,7 +262,7 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter } // Is the action being taken on a toolbar button while the dual listing is visible? else if (isToolbarButtonAction && isShowingDualListing) { - listingPanel = dualListingPanel.getFocusedListingPanel(); + listingPanel = dualListingPanel.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 @@ -367,10 +368,9 @@ public class VTFunctionAssociationProvider extends ComponentProviderAdapter statusPanel.add(statusLabel, BorderLayout.CENTER); dualTablePanel.add(statusPanel, BorderLayout.SOUTH); - functionComparisonPanel = - new FunctionComparisonPanel(this, tool, (Function) null, (Function) null); + functionComparisonPanel = new FunctionComparisonPanel(this, tool); addSpecificCodeComparisonActions(); - functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.TITLE); + functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.NAME); functionComparisonPanel.setTitlePrefixes("Source:", "Destination:"); comparisonSplitPane = 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 845f37ca99..82851a9a4b 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 @@ -18,7 +18,6 @@ package ghidra.feature.vt.gui.provider.markuptable; import java.util.List; import docking.DefaultActionContext; -import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; import ghidra.app.util.viewer.util.CodeComparisonPanel; import ghidra.app.util.viewer.util.CodeComparisonPanelActionContext; import ghidra.feature.vt.api.main.VTMarkupItem; @@ -30,7 +29,7 @@ public class VTMarkupItemContext extends DefaultActionContext implements CodeComparisonPanelActionContext { private final List selectedItems; - private CodeComparisonPanel codeComparisonPanel = null; + private CodeComparisonPanel codeComparisonPanel = null; /** * Creates an action context for the VT markup item provider. @@ -55,12 +54,12 @@ public class VTMarkupItemContext extends DefaultActionContext * @param codeComparisonPanel the code comparison panel. */ public void setCodeComparisonPanel( - CodeComparisonPanel codeComparisonPanel) { + CodeComparisonPanel codeComparisonPanel) { this.codeComparisonPanel = codeComparisonPanel; } @Override - public CodeComparisonPanel getCodeComparisonPanel() { + public CodeComparisonPanel getCodeComparisonPanel() { return codeComparisonPanel; } } 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 9a4c63b09a..3dcd67a23c 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 @@ -18,6 +18,7 @@ package ghidra.feature.vt.gui.provider.markuptable; import static ghidra.feature.vt.api.impl.VTEvent.*; import static ghidra.feature.vt.gui.plugin.VTPlugin.*; import static ghidra.framework.model.DomainObjectEvent.*; +import static ghidra.util.datastruct.Duo.Side.*; import java.awt.*; import java.awt.event.*; @@ -34,7 +35,6 @@ import docking.action.*; import docking.actions.PopupActionProvider; import docking.widgets.EventTrigger; import docking.widgets.fieldpanel.FieldPanel; -import docking.widgets.fieldpanel.internal.FieldPanelCoordinator; import docking.widgets.table.GTable; import docking.widgets.table.RowObjectTableModel; import docking.widgets.table.threaded.ThreadedTableModel; @@ -150,23 +150,24 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter markupItemsTablePanel.add(tablePanel, BorderLayout.CENTER); markupItemsTablePanel.add(filterAreaPanel, BorderLayout.SOUTH); - functionComparisonPanel = - new FunctionComparisonPanel(this, tool, (Function) null, (Function) null); + functionComparisonPanel = new FunctionComparisonPanel(this, tool); addSpecificCodeComparisonActions(); - functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.TITLE); + functionComparisonPanel.setCurrentTabbedComponent(ListingCodeComparisonPanel.NAME); functionComparisonPanel.setTitlePrefixes("Source:", "Destination:"); ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel(); if (dualListingPanel != null) { - dualListingPanel.setLeftProgramLocationListener(new SourceProgramLocationListener()); - dualListingPanel - .setRightProgramLocationListener(new DestinationProgramLocationListener()); + + dualListingPanel.getListingPanel(LEFT) + .setProgramLocationListener(new SourceProgramLocationListener()); + dualListingPanel.getListingPanel(RIGHT).setProgramLocationListener( + new DestinationProgramLocationListener()); sourceHighlightProvider = new VTDualListingHighlightProvider(controller, true); destinationHighlightProvider = new VTDualListingHighlightProvider(controller, false); dualListingPanel.addHighlightProviders(sourceHighlightProvider, destinationHighlightProvider); - sourceHighlightProvider.setListingPanel(dualListingPanel.getLeftPanel()); - destinationHighlightProvider.setListingPanel(dualListingPanel.getRightPanel()); + sourceHighlightProvider.setListingPanel(dualListingPanel.getListingPanel(LEFT)); + destinationHighlightProvider.setListingPanel(dualListingPanel.getListingPanel(RIGHT)); new VTDualListingDragNDropHandler(controller, dualListingPanel); } @@ -267,24 +268,24 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter // Don't set source or destination if the location change was initiated by the dual listing. if (!processingSourceLocationChange && !processingDestinationLocationChange) { - dualListingPanel.setLeftLocation(dualListingPanel.getLeftProgram(), + dualListingPanel.setLocation(LEFT, dualListingPanel.getProgram(LEFT), markupItem.getSourceLocation()); - dualListingPanel.setRightLocation(dualListingPanel.getRightProgram(), + dualListingPanel.setLocation(RIGHT, dualListingPanel.getProgram(RIGHT), markupItem.getDestinationLocation()); } else { // Only adjust the side of the dual listing panel that didn't initiate this. ProgramLocation sourceLocation = markupItem.getSourceLocation(); if (processingDestinationLocationChange && sourceLocation != null) { - dualListingPanel.setLeftLocation(dualListingPanel.getLeftProgram(), - sourceLocation); + dualListingPanel.setLocation(LEFT, + dualListingPanel.getProgram(LEFT), sourceLocation); } ProgramLocation destinationLocation = markupItem.getDestinationLocation(); if (processingSourceLocationChange && destinationLocation != null) { - dualListingPanel.setRightLocation( - dualListingPanel.getRightProgram(), destinationLocation); + dualListingPanel.setLocation(RIGHT, + dualListingPanel.getProgram(RIGHT), destinationLocation); } } dualListingPanel.updateListings(); // refresh the dual listing's background markup colors. @@ -361,10 +362,10 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter splitPane.add(functionComparisonPanel); markupPanel.add(splitPane, BorderLayout.CENTER); if (dualListingPanel != null) { - dualListingPanel - .setLeftProgramLocationListener(new SourceProgramLocationListener()); - dualListingPanel.setRightProgramLocationListener( - new DestinationProgramLocationListener()); + dualListingPanel.getListingPanel(LEFT) + .setProgramLocationListener(new SourceProgramLocationListener()); + dualListingPanel.getListingPanel(LEFT) + .setProgramLocationListener(new DestinationProgramLocationListener()); } markupPanel.validate(); @@ -378,8 +379,8 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter if (contains) { // Remove the split pane. if (dualListingPanel != null) { - dualListingPanel.setLeftProgramLocationListener(null); - dualListingPanel.setRightProgramLocationListener(null); + dualListingPanel.getListingPanel(LEFT).setProgramLocationListener(null); + dualListingPanel.getListingPanel(RIGHT).setProgramLocationListener(null); } markupPanel.remove(splitPane); splitPane.remove(functionComparisonPanel); @@ -462,7 +463,7 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter public List getPopupActions(Tool t, ActionContext context) { ListingCodeComparisonPanel dualListingPanel = functionComparisonPanel.getDualListingPanel(); if (context.getComponentProvider() == this && dualListingPanel != null) { - ListingPanel sourcePanel = dualListingPanel.getLeftPanel(); + ListingPanel sourcePanel = dualListingPanel.getListingPanel(LEFT); return sourcePanel.getHeaderActions(getName()); } return new ArrayList<>(); @@ -477,7 +478,7 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter List selectedItems = getSelectedMarkupItems(); VTMarkupItemContext vtMarkupItemContext = new VTMarkupItemContext(this, selectedItems); if (functionComparisonPanel.isVisible()) { - CodeComparisonPanel displayedPanel = + CodeComparisonPanel displayedPanel = functionComparisonPanel.getDisplayedPanel(); vtMarkupItemContext.setCodeComparisonPanel(displayedPanel); } @@ -626,7 +627,7 @@ public class VTMarkupItemsTableProvider extends ComponentProviderAdapter } } else { - functionComparisonPanel.loadFunctions(null, null); + functionComparisonPanel.clear(); } if (sourceHighlightProvider != null) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LayoutLockedFieldPanelCoordinator.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LayoutLockedFieldPanelCoordinator.java index a26795ea19..df9c150f94 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LayoutLockedFieldPanelCoordinator.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/internal/LayoutLockedFieldPanelCoordinator.java @@ -37,24 +37,25 @@ public class LayoutLockedFieldPanelCoordinator extends LineLockedFieldPanelCoord * Constructor for the coordinator. * @param panels the field panels that will have their positions coordinated with each other. */ - public LayoutLockedFieldPanelCoordinator(FieldPanel[] panels) { + public LayoutLockedFieldPanelCoordinator(FieldPanel... panels) { super(panels); } @Override public void viewChanged(FieldPanel fp, BigInteger index, int xPos, int yPos) { - if (valuesChanging) + 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."); + 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(); @@ -62,7 +63,7 @@ public class LayoutLockedFieldPanelCoordinator extends LineLockedFieldPanelCoord 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(); @@ -70,10 +71,10 @@ public class LayoutLockedFieldPanelCoordinator extends LineLockedFieldPanelCoord // 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; 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 6ddad466b6..3770eb175a 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 @@ -15,6 +15,8 @@ */ package ghidra.program.model.correlate; +import static ghidra.util.datastruct.Duo.Side.*; + import java.util.*; import java.util.Map.Entry; @@ -23,7 +25,9 @@ import ghidra.program.model.address.AddressSetView; import ghidra.program.model.block.CodeBlock; import ghidra.program.model.listing.*; import ghidra.program.model.mem.MemoryAccessException; -import ghidra.program.util.FunctionAddressCorrelation; +import ghidra.program.util.ListingAddressCorrelation; +import ghidra.util.datastruct.Duo; +import ghidra.util.datastruct.Duo.Side; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -41,7 +45,6 @@ import ghidra.util.task.TaskMonitor; * 5) Sequences with no corresponding match are also removed from consideration. * 6) Sequences are limited to a single basic-block, and the algorithm is basic-block aware. * Once a match establishes a correspondence between a pair of basic blocks, the algorithm uses - * that information to further narrow in on and disambiguate matching sequences. * 7) If a particular sequence has matches that are not unique, the algorithm tries to disambiguate the potential * matches by looking at parent/child relationships of the containing basic-blocks. (see DisambiguateStrategy) * 8) Multiple passes are attempted, each time the set of potential sequences is completely regenerated, @@ -49,7 +52,7 @@ import ghidra.util.task.TaskMonitor; * allows matches discovered by earlier passes to disambiguate sequences in later passes. * */ -public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelation { +public class HashedFunctionAddressCorrelation implements ListingAddressCorrelation { /** * A helper class for sorting through, disambiguating, sequences with identical hashes @@ -59,64 +62,54 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat public Hash hash; // The disambiguating (secondary) hash public int count; // Number of sequences (n-grams) in the subset matching the secondary hash public InstructHash instruct; // (Starting Instruction of) the n-gram - public DisambiguatorEntry(Hash h,InstructHash inst) { + + public DisambiguatorEntry(Hash h, InstructHash inst) { hash = h; instruct = inst; count = 1; } } - - private Function srcFunction; - private Function destFunction; - private TreeMap srcToDest; // Final source -> destination address mapping - private TreeMap destToSrc; // Final destination -> source address mapping + + private Duo functions; + private TreeMap srcToDest; // Final source -> destination address mapping + private TreeMap destToSrc; // Final destination -> source address mapping private HashStore srcStore; // Sorted list of source n-grams from which to draw potential matches private HashStore destStore; // List of destination n-grams private HashCalculator hashCalc; // Object that calculates n-gram hashes private TaskMonitor monitor; - + /** * Correlates addresses between the two specified functions. - * @param function1 the first function - * @param function2 the second function - * @param mon the task monitor that indicates progress and allows the user to cancel. + * @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 MemoryAccessException if either functions memory can't be accessed. */ - public HashedFunctionAddressCorrelation(Function function1, Function function2, TaskMonitor mon) - throws CancelledException, MemoryAccessException { - srcFunction = function1; - destFunction = function2; - monitor = mon; - srcToDest = new TreeMap(); - destToSrc = new TreeMap(); - if (function1 == null || function2 == null) - return; - srcStore = new HashStore(function1, monitor); - destStore = new HashStore(function2, monitor); + public HashedFunctionAddressCorrelation(Function leftFunction, Function rightFunction, + TaskMonitor monitor) throws CancelledException, MemoryAccessException { + if (leftFunction == null || rightFunction == null) { + throw new IllegalArgumentException("Functions can't be null!"); + } + this.functions = new Duo<>(leftFunction, rightFunction); + this.monitor = monitor; + srcToDest = new TreeMap(); + destToSrc = new TreeMap(); + srcStore = new HashStore(leftFunction, monitor); + destStore = new HashStore(rightFunction, monitor); hashCalc = new MnemonicHashCalculator(); calculate(); buildFinalMaps(); } @Override - public Program getFirstProgram() { - return srcFunction.getProgram(); + public Program getProgram(Side side) { + return functions.get(side).getProgram(); } @Override - public Program getSecondProgram() { - return destFunction.getProgram(); - } - - @Override - public AddressSetView getAddressesInFirst() { - return srcFunction.getBody(); - } - - @Override - public AddressSetView getAddressesInSecond() { - return destFunction.getBody(); + public AddressSetView getAddresses(Side side) { + return functions.get(side).getBody(); } /** @@ -126,7 +119,7 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat public int getTotalInstructionsInFirst() { return srcStore.getTotalInstructions(); } - + /** * Gets the total number of instructions that are in the second function. * @return the second function's instruction count. @@ -161,7 +154,7 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat public List getUnmatchedInstructionsInFirst() { return srcStore.getUnmatchedInstructions(); } - + /** * Determines the number of instructions from the second function that do not match an * instruction in the first function. @@ -180,7 +173,8 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat * @param destInstruct is (the starting Instruction of) the destination n-gram * @throws MemoryAccessException */ - private void declareMatch(HashEntry srcEntry,InstructHash srcInstruct,HashEntry destEntry,InstructHash destInstruct) throws MemoryAccessException { + private void declareMatch(HashEntry srcEntry, InstructHash srcInstruct, HashEntry destEntry, + InstructHash destInstruct) throws MemoryAccessException { boolean cancelMatch = false; int matchSize = srcEntry.hash.size; // Its possible that some instructions of the n-gram have already been matched @@ -192,7 +186,8 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat destStore.removeHash(destEntry); // Remove this HashEntry cancelMatch = true; // Cancel the match } - if (cancelMatch) return; + if (cancelMatch) + return; ArrayList srcInstructVec = new ArrayList(); ArrayList destInstructVec = new ArrayList(); ArrayList srcBlockVec = new ArrayList(); @@ -202,7 +197,7 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat HashStore.extendMatch(matchSize, srcInstruct, srcMatch, destInstruct, destMatch, hashCalc); srcStore.matchHash(srcMatch, srcInstructVec, srcBlockVec); destStore.matchHash(destMatch, destInstructVec, destBlockVec); - for(int i=0;i constructDisambiguatorTree(HashEntry entry, HashStore store, DisambiguateStrategy strategy) throws CancelledException, MemoryAccessException { - TreeMap entryMap = new TreeMap(); + TreeMap entryMap = new TreeMap(); int matchSize = entry.hash.size; - for(InstructHash curInstruct : entry.instList) { + for (InstructHash curInstruct : entry.instList) { ArrayList hashList = strategy.calcHashes(curInstruct, matchSize, store); Iterator iter = hashList.iterator(); - while(iter.hasNext()) { + while (iter.hasNext()) { Hash curHash = iter.next(); DisambiguatorEntry curEntry = entryMap.get(curHash); if (curEntry == null) { - curEntry = new DisambiguatorEntry(curHash,curInstruct); + curEntry = new DisambiguatorEntry(curHash, curInstruct); entryMap.put(curHash, curEntry); } else @@ -247,24 +242,30 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat * @throws CancelledException * @throws MemoryAccessException */ - private int disambiguateNgramsWithStrategy(DisambiguateStrategy strategy,HashEntry srcEntry,HashEntry destEntry) throws CancelledException, MemoryAccessException { + private int disambiguateNgramsWithStrategy(DisambiguateStrategy strategy, HashEntry srcEntry, + HashEntry destEntry) throws CancelledException, MemoryAccessException { TreeMap srcDisambig = constructDisambiguatorTree(srcEntry, srcStore, strategy); TreeMap destDisambig = constructDisambiguatorTree(destEntry, destStore, strategy); int count = 0; Iterator iter = srcDisambig.values().iterator(); - while(iter.hasNext()) { + while (iter.hasNext()) { DisambiguatorEntry srcDisEntry = iter.next(); - if (srcDisEntry.count != 1) continue; + if (srcDisEntry.count != 1) + continue; // Its possible for this InstructHash to have been matched by an earlier DisambiguatorEntry - if (srcDisEntry.instruct.isMatched) continue; + if (srcDisEntry.instruct.isMatched) + continue; DisambiguatorEntry destDisEntry = destDisambig.get(srcDisEntry.hash); - if (destDisEntry == null) continue; - if (destDisEntry.count != 1) continue; - if (destDisEntry.instruct.isMatched) continue; + if (destDisEntry == null) + continue; + if (destDisEntry.count != 1) + continue; + if (destDisEntry.instruct.isMatched) + continue; // If both sides have exactly one matching InstructHash, call it a match - declareMatch(srcEntry,srcDisEntry.instruct,destEntry,destDisEntry.instruct); + declareMatch(srcEntry, srcDisEntry.instruct, destEntry, destDisEntry.instruct); count += 1; } return count; @@ -278,21 +279,27 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat * @throws CancelledException * @throws MemoryAccessException */ - private boolean disambiguateMatchingNgrams(HashEntry srcEntry,HashEntry destEntry) throws CancelledException, MemoryAccessException { + private boolean disambiguateMatchingNgrams(HashEntry srcEntry, HashEntry destEntry) + throws CancelledException, MemoryAccessException { if (srcEntry.hasDuplicateBlocks()) return false; if (destEntry.hasDuplicateBlocks()) return false; 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) return true; - count = disambiguateNgramsWithStrategy(new DisambiguateByChild(),srcEntry,destEntry); - if (count != 0) return true; - count = disambiguateNgramsWithStrategy(new DisambiguateByBytes(),srcEntry,destEntry); - if (count != 0) return true; - count= disambiguateNgramsWithStrategy(new DisambiguateByParentWithOrder(),srcEntry,destEntry); - if (count != 0) return true; + int count = disambiguateNgramsWithStrategy(new DisambiguateByParent(), srcEntry, destEntry); + if (count != 0) + return true; + count = disambiguateNgramsWithStrategy(new DisambiguateByChild(), srcEntry, destEntry); + if (count != 0) + return true; + count = disambiguateNgramsWithStrategy(new DisambiguateByBytes(), srcEntry, destEntry); + if (count != 0) + return true; + count = disambiguateNgramsWithStrategy(new DisambiguateByParentWithOrder(), srcEntry, + destEntry); + if (count != 0) + return true; return false; } @@ -303,7 +310,7 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat * @throws CancelledException */ private void findMatches() throws MemoryAccessException, CancelledException { - while(!srcStore.isEmpty() && !destStore.isEmpty()) { + while (!srcStore.isEmpty() && !destStore.isEmpty()) { HashEntry srcEntry = srcStore.getFirstEntry(); HashEntry destEntry = destStore.getEntry(srcEntry.hash); if (destEntry == null) { @@ -311,7 +318,8 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat } else if (srcEntry.instList.size() == 1 && destEntry.instList.size() == 1) { // Found a unique match - declareMatch(srcEntry,srcEntry.instList.getFirst(),destEntry,destEntry.instList.getFirst()); + declareMatch(srcEntry, srcEntry.instList.getFirst(), destEntry, + destEntry.instList.getFirst()); } else { HashEntry destEntry2 = destStore.getFirstEntry(); @@ -321,14 +329,15 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat } else if (srcEntry2.instList.size() == 1 && destEntry2.instList.size() == 1) { // Found a unique match - declareMatch(srcEntry2,srcEntry2.instList.getFirst(),destEntry2,destEntry2.instList.getFirst()); + declareMatch(srcEntry2, srcEntry2.instList.getFirst(), destEntry2, + destEntry2.instList.getFirst()); } else { if (!disambiguateMatchingNgrams(srcEntry, destEntry)) srcStore.removeHash(srcEntry); } } - } + } } /** @@ -344,21 +353,24 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat * @throws MemoryAccessException * @throws CancelledException */ - private void runPasses(int minLength,int maxLength,boolean wholeBlock,boolean matchBlock,int maxPasses) throws MemoryAccessException, CancelledException { + private void runPasses(int minLength, int maxLength, boolean wholeBlock, boolean matchBlock, + int maxPasses) throws MemoryAccessException, CancelledException { srcStore.calcHashes(minLength, maxLength, wholeBlock, matchBlock, hashCalc); destStore.calcHashes(minLength, maxLength, wholeBlock, matchBlock, hashCalc); - for(int pass=0;pass < maxPasses;++pass) { + for (int pass = 0; pass < maxPasses; ++pass) { int curMatch = srcStore.numMatchedInstructions(); - if (curMatch == srcStore.getTotalInstructions()) break; // quit if there are no unmatched instructions + if (curMatch == srcStore.getTotalInstructions()) + break; // quit if there are no unmatched instructions srcStore.clearSort(); destStore.clearSort(); - + srcStore.insertHashes(); destStore.insertHashes(); - + findMatches(); - if (curMatch == srcStore.numMatchedInstructions()) break; // quit if no new matched instructions - } + if (curMatch == srcStore.numMatchedInstructions()) + break; // quit if no new matched instructions + } } /** @@ -377,33 +389,40 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat findMatches(); - if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions()) return; - if (destStore.numMatchedInstructions() == destStore.getTotalInstructions()) return; + if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions()) + return; + 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); + runPasses(3, 4, true, true, 10); - if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions()) return; - if (destStore.numMatchedInstructions() == destStore.getTotalInstructions()) return; + if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions()) + return; + if (destStore.numMatchedInstructions() == destStore.getTotalInstructions()) + return; // Repeat with big n-grams int curMatch = srcStore.numMatchedInstructions(); - runPasses(5,10,false,false,3); + runPasses(5, 10, false, false, 3); + + if (srcStore.numMatchedInstructions() == curMatch) + return; // No progress + if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions()) + return; + if (destStore.numMatchedInstructions() == destStore.getTotalInstructions()) + return; - if (srcStore.numMatchedInstructions() == curMatch) return; // No progress - if (srcStore.numMatchedInstructions() == srcStore.getTotalInstructions()) return; - if (destStore.numMatchedInstructions() == destStore.getTotalInstructions()) return; - // Repeat with small n-grams - runPasses(3,4,true,true,10); + runPasses(3, 4, true, true, 10); } /** * {@literal Given the src -> dest map, build the dest -> src map} */ private void buildFinalMaps() { - for(Entry entry : srcToDest.entrySet()) { + for (Entry entry : srcToDest.entrySet()) { destToSrc.put(entry.getValue(), entry.getKey()); // Build the reverse map of srcToDest } } @@ -412,27 +431,20 @@ public class HashedFunctionAddressCorrelation implements FunctionAddressCorrelat * Gets an iterator of the matching addresses from the first function to the second. * @return the iterator */ - public Iterator> getFirstToSecondIterator() { + public Iterator> getFirstToSecondIterator() { return srcToDest.entrySet().iterator(); } @Override - public Address getAddressInSecond(Address addressInFirst) { - return srcToDest.get(addressInFirst); - } - - @Override - public Address getAddressInFirst(Address addressInSecond) { - return destToSrc.get(addressInSecond); + public Address getAddress(Side side, Address otherSideAddress) { + if (side == LEFT) { + return destToSrc.get(otherSideAddress); + } + return srcToDest.get(otherSideAddress); } @Override - public Function getFirstFunction() { - return srcFunction; - } - - @Override - public Function getSecondFunction() { - return destFunction; + public Function getFunction(Side side) { + return functions.get(side); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/FunctionAddressCorrelation.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/DummyListingAddressCorrelation.java similarity index 52% rename from Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/FunctionAddressCorrelation.java rename to Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/DummyListingAddressCorrelation.java index 12ae85651e..345c615027 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/FunctionAddressCorrelation.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/DummyListingAddressCorrelation.java @@ -15,25 +15,32 @@ */ package ghidra.program.util; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSetView; import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.util.datastruct.Duo.Side; -/** - * This is the interface for a correlator that associates instructions from one function to - * instructions from another function. Given an address from one function it determines the matching - * address in the other function if possible. - */ -public interface FunctionAddressCorrelation extends ListingAddressCorrelation { +public class DummyListingAddressCorrelation implements ListingAddressCorrelation { - /** - * Gets the first function for this address correlator. - * @return the first function. - */ - public Function getFirstFunction(); + @Override + public Program getProgram(Side side) { + return null; + } - /** - * Gets the second function for this address correlator. - * @return the second function. - */ - public Function getSecondFunction(); + @Override + public Function getFunction(Side side) { + return null; + } + + @Override + public AddressSetView getAddresses(Side side) { + return null; + } + + @Override + public Address getAddress(Side side, Address otherSideAddress) { + return null; + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ListingAddressCorrelation.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ListingAddressCorrelation.java index 642f4706bd..fe68a353f9 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ListingAddressCorrelation.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/ListingAddressCorrelation.java @@ -17,54 +17,47 @@ package ghidra.program.util; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; +import ghidra.util.datastruct.Duo.Side; /** * This is the interface for a correlator that associates addresses from one program with * addresses from another program or it can associate addresses from one part of a program - * with addresses from another part of the same program. Given an address from the address set - * in the first program it determines the matching address from the address set for the second - * program if possible. + * with addresses from another part of the same program. Given an address from one program, it + * can provide the corresponding address for the other program. The two programs are referred to + * as the LEFT program and the RIGHT program. See {@link ghidra.util.datastruct.Duo.Side} */ public interface ListingAddressCorrelation { /** - * Gets the program containing the first set of addresses. - * @return the program for the first set of addresses. + * Gets the program for the given side. + * @param side LEFT or RIGHT + * @return the program for the given side */ - public abstract Program getFirstProgram(); + public abstract Program getProgram(Side side); /** - * Gets the program containing the second set of addresses. - * This program may be different from or the same as the first program. - * @return the program for the second set of addresses. + * Gets the function for the given side. This will be null if the addresses are not function + * based. + * @param side LEFT or RIGHT + * @return the function for the given side or null if not function based */ - public abstract Program getSecondProgram(); + public abstract Function getFunction(Side side); /** - * Gets the first set of addresses for this correlator. - * @return the first set of addresses. + * Gets the addresses that are part of the correlator for the given side + * @param side LEFT or RIGHT + * @return the addresses that are part of the correlator for the given side */ - public abstract AddressSetView getAddressesInFirst(); + public abstract AddressSetView getAddresses(Side side); /** - * Gets the second set of addresses for this correlator. - * @return the second set of addresses. + * Gets the address for the given side that matches the given address from the other side. + * @param side the side to get an address for + * @param otherSideAddress the address from the other side to find a match for + * @return the address for the given side that matches the given address from the other side. */ - public abstract AddressSetView getAddressesInSecond(); - - /** - * Determine the address from the second set that matches the specified address in the first set. - * @param addressInFirst the address in the first address set. - * @return the matching address in the second set or null if a match couldn't be determined. - */ - public abstract Address getAddressInSecond(Address addressInFirst); - - /** - * Determine the address from the first set that matches the specified address in the second set. - * @param addressInSecond the address in the second address set. - * @return the matching address in the first set or null if a match couldn't be determined. - */ - public abstract Address getAddressInFirst(Address addressInSecond); + public abstract Address getAddress(Side side, Address otherSideAddress); } diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/util/datastruct/Duo.java b/Ghidra/Framework/Utility/src/main/java/ghidra/util/datastruct/Duo.java new file mode 100644 index 0000000000..754f842c4c --- /dev/null +++ b/Ghidra/Framework/Utility/src/main/java/ghidra/util/datastruct/Duo.java @@ -0,0 +1,126 @@ +/* ### + * 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.util.datastruct; + +import static ghidra.util.datastruct.Duo.Side.*; + +import java.util.Objects; +import java.util.function.Consumer; + +/** + * Class for holding two objects of the same type. We are using the idiom of LEFT and RIGHT to + * refer to each item in this pair of objects. + * The enum "Side" is used to represent either the LEFT (or first) or RIGHT (or second) item. + * + * @param The type of item that is stored in this Duo. + */ +public class Duo { + public enum Side { + LEFT, RIGHT; + + public Side otherSide() { + return this == LEFT ? RIGHT : LEFT; + } + } + + private final T left; + private final T right; + + /** + * Constructor with no values. + */ + public Duo() { + this(null, null); + } + + /** + * Constructor with a left and right value. + * @param left the left value + * @param right the right value + */ + public Duo(T left, T right) { + this.left = left; + this.right = right; + } + + /** + * Gets the value for the given side. + * @param side LEFT or RIGHT + * @return the value for the given side + */ + public T get(Side side) { + return side == LEFT ? left : right; + } + + /** + * Creates a new Duo, replacing the value for just one side. The other side uses the value + * from this Duo. + * @param side the side that gets a new value + * @param newValue the new value for the given side + * @return the new Duo + * value as this + */ + public Duo with(Side side, T newValue) { + if (side == LEFT) { + return new Duo<>(newValue, right); + } + return new Duo<>(left, newValue); + } + + /** + * Invokes the given consumer on both the left and right values. + * @param c the consumer to invoke on both values + */ + public void each(Consumer c) { + if (left != null) { + c.accept(left); + } + if (right != null) { + c.accept(right); + } + } + + /** + * Returns true if both values are equals to this objects values. + * @param otherLeft the value to compare to our left side value + * @param otherRight the value to compare to our right side value + * @return true if both values are equals to this objects values + */ + public boolean equals(T otherLeft, T otherRight) { + return Objects.equals(left, otherLeft) && Objects.equals(right, otherRight); + } + + @Override + public int hashCode() { + return Objects.hash(left, right); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Duo other = (Duo) obj; + return Objects.equals(left, other.left) && Objects.equals(right, other.right); + } + +} 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 d1526ecc65..ce219cb949 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionComparisonScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/FunctionComparisonScreenShots.java @@ -15,6 +15,8 @@ */ package help.screenshot; +import static ghidra.util.datastruct.Duo.Side.*; + import java.io.IOException; import javax.swing.table.TableColumn; @@ -102,7 +104,7 @@ public class FunctionComparisonScreenShots extends GhidraScreenShotGenerator { functionComparisonPanel.setCurrentTabbedComponent("Listing View"); ListingCodeComparisonPanel dualListing = (ListingCodeComparisonPanel) functionComparisonPanel.getDisplayedPanel(); - ListingPanel leftPanel = dualListing.getLeftPanel(); + ListingPanel leftPanel = dualListing.getListingPanel(LEFT); leftPanel.goTo(addr(0x004119aa)); }); waitForSwing(); 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 new file mode 100644 index 0000000000..9c01bd5428 --- /dev/null +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/app/plugin/compare/CompareFunctionsDecompilerViewTest.java @@ -0,0 +1,104 @@ +/* ### + * 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 org.junit.Assert.*; + +import java.util.Set; + +import org.junit.*; + +import ghidra.app.plugin.core.functioncompare.*; +import ghidra.codecompare.decompile.CDisplay; +import ghidra.codecompare.decompile.DecompilerCodeComparisonPanel; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.*; +import ghidra.test.*; + +/** + * Tests for the {@link FunctionComparisonPlugin function comparison plugin} + * that involve the GUI + */ +public class CompareFunctionsDecompilerViewTest extends AbstractGhidraHeadedIntegrationTest { + + private TestEnv env; + private Program program1; + private Function fun1; + private Function fun2; + private FunctionComparisonPlugin plugin; + private FunctionComparisonProvider provider; + + @Before + public void setUp() throws Exception { + env = new TestEnv(); + plugin = env.addPlugin(FunctionComparisonPlugin.class); + program1 = buildTestProgram(); + showTool(plugin.getTool()); + env.open(program1); + FunctionManager functionManager = program1.getFunctionManager(); + fun1 = functionManager.getFunctionAt(addr(0x01002cf5)); + fun2 = functionManager.getFunctionAt(addr(0x0100415a)); + + } + + private Address addr(long offset) { + return program1.getAddressFactory().getDefaultAddressSpace().getAddress(offset); + } + + @After + public void tearDown() throws Exception { + env.dispose(); + } + + @Test + public void testDecompDifView() throws Exception { + Set functions = CompareFunctionsTestUtility.getFunctionsAsSet(fun1, fun2); + provider = compareFunctions(functions); + + CompareFunctionsTestUtility.checkSourceFunctions(provider, fun1, fun2); + DecompilerCodeComparisonPanel panel = + (DecompilerCodeComparisonPanel) provider + .getCodeComparisonPanelByName(DecompilerCodeComparisonPanel.NAME); + + waitForDecompiler(panel); + assertHasLines(panel.getLeftPanel(), 28); + assertHasLines(panel.getRightPanel(), 23); + } + + private void assertHasLines(CDisplay panel, int lineCount) { + assertEquals(lineCount, panel.getDecompilerPanel().getLines().size()); + } + + private void waitForDecompiler(DecompilerCodeComparisonPanel panel) { + waitForSwing(); + waitForCondition(() -> !panel.isBusy()); + waitForSwing(); + } + + private FunctionComparisonProvider compareFunctions(Set functions) { + provider = runSwing(() -> plugin.compareFunctions(functions)); + provider.setVisible(true); + waitForSwing(); + return provider; + } + + private Program buildTestProgram() throws Exception { + ClassicSampleX86ProgramBuilder builder = + new ClassicSampleX86ProgramBuilder("Test", false); + return builder.getProgram(); + } + +}