From b34bfecf6c3cb0676d9789b6418f7f410ae5eaa5 Mon Sep 17 00:00:00 2001 From: ghidravore Date: Wed, 18 Aug 2021 10:47:33 -0400 Subject: [PATCH] GP-1188 fixed analysis panel to not use 'last used' analysis profile when accessed via program options versus analysis action. Also added check if you made changes and then change the combo --- .../core/analysis/AnalysisOptionsDialog.java | 2 +- .../plugin/core/analysis/AnalysisPanel.java | 123 ++++++++++++------ .../core/analysis/AnalysisOptionsTest.java | 15 ++- .../ghidra/framework/options/Options.java | 28 +++- .../ghidra/framework/options/SubOptions.java | 6 +- 5 files changed, 124 insertions(+), 50 deletions(-) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisOptionsDialog.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisOptionsDialog.java index e5cf8b91f8..1ed2d6c751 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisOptionsDialog.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisOptionsDialog.java @@ -56,7 +56,7 @@ public class AnalysisOptionsDialog extends DialogComponentProvider super("Analysis Options"); setHelpLocation(new HelpLocation("AutoAnalysisPlugin", "AnalysisOptions")); panel = new AnalysisPanel(programs, editorStateFactory, this); - + panel.setToLastUsedAnalysisOptionsIfProgramNotAnalyzed(); addWorkPanel(panel); addOKButton(); addCancelButton(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisPanel.java index 5e04cb76ee..44a0703718 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisPanel.java @@ -17,6 +17,7 @@ package ghidra.app.plugin.core.analysis; import java.awt.*; import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; import java.beans.*; import java.io.File; import java.io.IOException; @@ -86,9 +87,15 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener { private EditorStateFactory editorStateFactory; private JPanel noOptionsPanel; - private GhidraComboBox defaultOptionsCombo; + private GhidraComboBox optionsComboBox; private JButton deleteButton; + private Options[] optionConfigurationChoices; + + private ItemListener optionsComboBoxListener = this::optionsComboBoxChanged; + + private FileOptions currentNonDefaults; + /** * Constructor * @@ -207,14 +214,14 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener { private Component buildOptionsComboBoxPanel() { JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER)); - Options[] defaultOptionsArray = getDefaultOptionsArray(); - defaultOptionsCombo = new GhidraComboBox<>(defaultOptionsArray); - selectedOptions = findOptions(defaultOptionsArray, getLastUsedDefaultOptionsName()); - defaultOptionsCombo.setSelectedItem(selectedOptions); - defaultOptionsCombo.addItemListener(this::analysisComboChanged); - Dimension preferredSize = defaultOptionsCombo.getPreferredSize(); - defaultOptionsCombo.setPreferredSize(new Dimension(200, preferredSize.height)); - panel.add(defaultOptionsCombo); + optionConfigurationChoices = loadPossibleOptionsChoicesForComboBox(); + optionsComboBox = new GhidraComboBox<>(optionConfigurationChoices); + selectedOptions = currentProgramOptions; + optionsComboBox.setSelectedItem(selectedOptions); + optionsComboBox.addItemListener(optionsComboBoxListener); + Dimension preferredSize = optionsComboBox.getPreferredSize(); + optionsComboBox.setPreferredSize(new Dimension(200, preferredSize.height)); + panel.add(optionsComboBox); deleteButton = new JButton("Delete"); deleteButton.addActionListener(e -> deleteSelectedOptionsConfiguration()); @@ -350,17 +357,18 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener { "Overwrite existing configuration file: " + saveName + " ?", "Overwrite")) { return; } - FileOptions saved = saveCurrentOptions(); + FileOptions currentOptions = getCurrentOptionsAsFileOptions(); try { - saved.save(saveFile); - reloadOptionsCombo(saved); + currentOptions.save(saveFile); + currentNonDefaults = currentOptions; + reloadOptionsCombo(currentOptions); } catch (IOException e) { Msg.error(this, "Error saving default options", e); } } - private FileOptions saveCurrentOptions() { + private FileOptions getCurrentOptionsAsFileOptions() { FileOptions saveTo = new FileOptions(""); List analyzerStates = model.getModelData(); for (AnalyzerEnablementState analyzerState : analyzerStates) { @@ -395,17 +403,18 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener { editorState.loadFrom(selectedOptions); } updateDeleteButton(); + currentNonDefaults = getCurrentOptionsAsFileOptions(); } private void reloadOptionsCombo(Options newDefaultOptions) { - Options[] defaultOptionsArray = getDefaultOptionsArray(); - defaultOptionsCombo.setModel(new DefaultComboBoxModel(defaultOptionsArray)); - Options selected = findOptions(defaultOptionsArray, newDefaultOptions.getName()); - defaultOptionsCombo.setSelectedItem(selected); + optionConfigurationChoices = loadPossibleOptionsChoicesForComboBox(); + optionsComboBox.setModel(new DefaultComboBoxModel(optionConfigurationChoices)); + Options selected = findOptionsByName(newDefaultOptions.getName()); + optionsComboBox.setSelectedItem(selected); } - private Options findOptions(Options[] defaultOptionsArray, String name) { - for (Options fileOptions : defaultOptionsArray) { + private Options findOptionsByName(String name) { + for (Options fileOptions : optionConfigurationChoices) { if (fileOptions.getName().equals(name)) { return fileOptions; } @@ -539,6 +548,13 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener { copyOptionsToAllPrograms(); currentProgramOptions = getNonDefaultProgramOptions(); reloadOptionsCombo(currentProgramOptions); + + // save off preference (unless it is the current program options, then don't save it) + if (selectedOptions != currentProgramOptions) { + Preferences.setProperty(LAST_USED_OPTIONS_CONFIG, + selectedOptions.getName()); + } + } private void copyOptionsToAllPrograms() { @@ -684,23 +700,12 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener { } } - private String getLastUsedDefaultOptionsName() { - // if the program has non-default options or has been analyzed use its - // current settings initially - if (isAnalyzed() || !currentProgramOptions.getOptionNames().isEmpty()) { - return currentProgramOptions.getName(); - } - // Otherwise, use the last used analysis options configuration - return Preferences.getProperty(LAST_USED_OPTIONS_CONFIG, - STANDARD_DEFAULT_OPTIONS.getName()); - } - private boolean isAnalyzed() { Options options = programs.get(0).getOptions(Program.PROGRAM_INFO); return options.getBoolean(Program.ANALYZED, false); } - private Options[] getDefaultOptionsArray() { + private Options[] loadPossibleOptionsChoicesForComboBox() { List savedDefaultsList = getSavedOptionsObjects(); Options[] optionsArray = new FileOptions[savedDefaultsList.size() + 2]; // 2 standard configurations always present optionsArray[CURRENT_PROGRAM_OPTIONS_CHOICE_INDEX] = currentProgramOptions; @@ -712,10 +717,9 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener { } private String[] getSavedChoices() { - Options[] defaultOptionsArray = getDefaultOptionsArray(); List list = new ArrayList<>(); - for (int i = 2; i < defaultOptionsArray.length; i++) { - list.add(defaultOptionsArray[i].getName()); + for (int i = 2; i < optionConfigurationChoices.length; i++) { + list.add(optionConfigurationChoices[i].getName()); } String[] a = new String[list.size()]; list.toArray(a); @@ -804,20 +808,57 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener { } - private void analysisComboChanged(ItemEvent e) { + private void optionsComboBoxChanged(ItemEvent e) { if (e.getStateChange() == ItemEvent.SELECTED) { - selectedOptions = (FileOptions) defaultOptionsCombo.getSelectedItem(); + if (!checkOkToChange()) { + optionsComboBox.removeItemListener(optionsComboBoxListener); + optionsComboBox.setSelectedItem(selectedOptions); + optionsComboBox.addItemListener(optionsComboBoxListener); + return; + } + selectedOptions = (FileOptions) optionsComboBox.getSelectedItem(); updateDeleteButton(); loadCurrentOptionsIntoEditors(); - // save off preference (unless it is the current program options, then don't save it) - if (selectedOptions != currentProgramOptions) { - Preferences.setProperty(LAST_USED_OPTIONS_CONFIG, - selectedOptions.getName()); - } + propertyChangeListener.propertyChange( + new PropertyChangeEvent(this, GhidraOptions.APPLY_ENABLED, null, + hasChangedValues())); } } + private boolean checkOkToChange() { + FileOptions current = getCurrentOptionsAsFileOptions(); + if (Options.hasSameOptionsAndValues(current, currentNonDefaults)) { + return true; + } + int result = OptionDialog.showYesNoDialog(this, "Loose Changes?", + "You have made changes from the current options set. If you change\n" + + "the current option set, those changes will be lost.\n" + + "Do you want to proceed?"); + return result == OptionDialog.YES_OPTION; + } + private void updateDeleteButton() { deleteButton.setEnabled(isUserConfiguration(selectedOptions)); } + + public void setToLastUsedAnalysisOptionsIfProgramNotAnalyzed() { + // if already analyzed, get out + if (isAnalyzed()) { + return; + } + + // if any analysis options are non default, it means the user previously saved + // some options, so don't use last save profile + if (!getNonDefaultProgramOptions().getOptionNames().isEmpty()) { + return; + } + + // Otherwise, use the last used analysis options configuration + String optionsName = Preferences.getProperty(LAST_USED_OPTIONS_CONFIG, + STANDARD_DEFAULT_OPTIONS.getName()); + Options lastUsed = findOptionsByName(optionsName); + if (lastUsed != null) { + optionsComboBox.setSelectedItem(lastUsed); + } + } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalysisOptionsTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalysisOptionsTest.java index cafd0f387b..1672a3e94f 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalysisOptionsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalysisOptionsTest.java @@ -101,6 +101,8 @@ public class AnalysisOptionsTest extends AbstractGhidraHeadedIntegrationTest { assertTrue(isAnalyzerEnabled("Stack")); assertTrue(isAnalyzerEnabled("Reference")); assertTrue(isAnalyzerEnabled("ASCII Strings")); + + close(optionsDialog); } @Test @@ -119,6 +121,8 @@ public class AnalysisOptionsTest extends AbstractGhidraHeadedIntegrationTest { assertFalse(isAnalyzerEnabled("Stack")); assertFalse(isAnalyzerEnabled("Reference")); assertFalse(isAnalyzerEnabled("ASCII Strings")); + + close(optionsDialog); } @Test @@ -140,6 +144,8 @@ public class AnalysisOptionsTest extends AbstractGhidraHeadedIntegrationTest { assertTrue(isAnalyzerEnabled("Stack")); assertTrue(isAnalyzerEnabled("Reference")); assertTrue(isAnalyzerEnabled("ASCII Strings")); + + close(optionsDialog); } @Test @@ -154,6 +160,7 @@ public class AnalysisOptionsTest extends AbstractGhidraHeadedIntegrationTest { assertComboboxEquals("foo"); + close(optionsDialog); } @Test @@ -166,6 +173,8 @@ public class AnalysisOptionsTest extends AbstractGhidraHeadedIntegrationTest { confirmDelete(); assertComboboxEquals("Current Program Options"); + + close(optionsDialog); } @Test @@ -190,6 +199,7 @@ public class AnalysisOptionsTest extends AbstractGhidraHeadedIntegrationTest { assertTrue(isAnalyzerEnabled("Reference")); assertFalse(isAnalyzerEnabled("ASCII Strings")); + close(optionsDialog); } @Test @@ -212,6 +222,7 @@ public class AnalysisOptionsTest extends AbstractGhidraHeadedIntegrationTest { pressButtonByText(optionsDialog, "Cancel", false); OptionDialog yesNoDialog = waitForDialogComponent(OptionDialog.class); pressButtonByText(yesNoDialog.getComponent(), "No"); + waitForSwing(); assertTrue(isAnalyzerEnabledInProgramOptions("Stack")); assertTrue(isAnalyzerEnabledInProgramOptions("Reference")); @@ -260,7 +271,7 @@ public class AnalysisOptionsTest extends AbstractGhidraHeadedIntegrationTest { AnalysisPanel panel = (AnalysisPanel) getInstanceField("panel", optionsDialog); @SuppressWarnings("unchecked") GhidraComboBox combo = - (GhidraComboBox) getInstanceField("defaultOptionsCombo", panel); + (GhidraComboBox) getInstanceField("optionsComboBox", panel); assertEquals(name, ((Options) combo.getSelectedItem()).getName()); } @@ -269,7 +280,7 @@ public class AnalysisOptionsTest extends AbstractGhidraHeadedIntegrationTest { AnalysisPanel panel = (AnalysisPanel) getInstanceField("panel", optionsDialog); @SuppressWarnings("unchecked") GhidraComboBox combo = - (GhidraComboBox) getInstanceField("defaultOptionsCombo", panel); + (GhidraComboBox) getInstanceField("optionsComboBox", panel); ComboBoxModel model = combo.getModel(); for (int i = 0; i < model.getSize(); i++) { Options elementAt = model.getElementAt(i); diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/Options.java b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/Options.java index ef8432abfe..884fe63314 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/Options.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/Options.java @@ -19,8 +19,7 @@ import java.awt.Color; import java.awt.Font; import java.beans.PropertyEditor; import java.io.File; -import java.util.Date; -import java.util.List; +import java.util.*; import javax.swing.KeyStroke; @@ -517,4 +516,29 @@ public interface Options { */ public abstract String getDefaultValueAsString(String optionName); + /** + * Returns true if the two options objects have the same set of options and values + * @param options1 the first options object to test + * @param options2 the second options object to test + * @return true if the two options objects have the same set of options and values + */ + public static boolean hasSameOptionsAndValues(Options options1, Options options2) { + List leafOptionNames1 = options1.getOptionNames(); + List leafOptionNames2 = options2.getOptionNames(); + Collections.sort(leafOptionNames1); + Collections.sort(leafOptionNames2); + + if (!leafOptionNames1.equals(leafOptionNames2)) { + return false; + } + for (String optionName : leafOptionNames1) { + Object value1 = options1.getObject(optionName, null); + Object value2 = options2.getObject(optionName, null); + if (!Objects.equals(value1, value2)) { + return false; + } + } + return true; + + } } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/SubOptions.java b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/SubOptions.java index 89bd17c30c..0ad00dd09a 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/SubOptions.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/SubOptions.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. @@ -16,8 +15,6 @@ */ package ghidra.framework.options; -import ghidra.util.HelpLocation; - import java.awt.Color; import java.awt.Font; import java.beans.PropertyEditor; @@ -26,6 +23,8 @@ import java.util.*; import javax.swing.KeyStroke; +import ghidra.util.HelpLocation; + public class SubOptions implements Options { private AbstractOptions options; @@ -369,5 +368,4 @@ public class SubOptions implements Options { Set leaves = AbstractOptions.getLeaves(optionPaths); return new ArrayList(leaves); } - }