Merge remote-tracking branch

'origin/GP-544_ghidravore_default_analysis_options'
This commit is contained in:
ghidra1 2021-04-26 10:49:23 -04:00
commit da3a80f4fe
12 changed files with 1082 additions and 66 deletions

View file

@ -158,12 +158,48 @@
<P><IMG alt="" src="images/note.png"> A separate dialog showing the <I>Analysis</I>
options is also displayed after a new program has been imported, or the <B>Analysis<IMG
alt="" src="images/arrow.gif"> Auto Analysis</B> menu item is chosen.</P>
<P><IMG alt="" src="images/warning.help.png"> <FONT color="red">Some analyzers only work
on certain processors. For example, the MIPS Instruction Analyzer only works on MIPS
processors. Their options will only show up if the currently open program can be analyzed
with the analyzer.</FONT></P>
</BLOCKQUOTE>
<P style="background-color: #FFF0E0;">
<IMG SRC="../../shared/warning.png" />Note that multi-user merge does not currently support
merging of Program Options (including Analysis Options). Options stored in shared Program database
following a conflicting checkin may not reflect option settings specified prior to checkin.
</P>
<H3>Saved Options Configurations</H3>
<BLOCKQUOTE>
<P>The Options Configurations combo box at bottom of the analyzer enablement panel can be used to quickly
switch between sets of analyzer option values. The combo box will always contain two standard sets of options as well as any
previously saved configurations. The two standard configurations are:
<UL>
<LI>Standard Defaults - This setting will put all the analyzer enablements and options to the original default values.</LI>
<LI>Current Program Options - This setting will put all the analyzer enablements and options to values as stored in
the current program.</LI>
</UL>
<P style="background-color: #FFF0E0;">
<IMG SRC="../../shared/warning.png" />Access to stored configurations is not currently
aupported across different versions of Ghidra.
</P>
</BLOCKQUOTE>
<H3>Analysis Panel Buttons</H3>
<BLOCKQUOTE>
<UL>
<LI>Select All - Turns all analyzers on.</LI>
<LI>Deselect All - Turns all analyzers off.</LI>
<LI>Reset - Resets all the options to the currently selected configuration.</LI>
<LI>Save... - Saves the current analysis options to a named configuration.</LI>
<LI>Delete - Deletes the currently selected named configuration.</LI>
</UL>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H2><A name="Auto_Analyzers"></A>Auto Analyzers</H2>
@ -617,3 +653,4 @@
<BR>
</BODY>
</HTML>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Before After
Before After

View file

@ -91,7 +91,7 @@ public class AnalysisEnablementTableModel
Boolean enabled = (Boolean) value;
analyzerStates.get(rowIndex).setEnabled(enabled);
String analyzerName = analyzerStates.get(rowIndex).getName();
panel.setAnalyzerEnabled(analyzerName, enabled);
panel.setAnalyzerEnabled(analyzerName, enabled, true);
fireTableRowsUpdated(rowIndex, rowIndex);
}
}

View file

@ -29,8 +29,7 @@ import ghidra.util.Msg;
* Dialog to show the panel for the auto analysis options.
*
*/
public class AnalysisOptionsDialog extends DialogComponentProvider implements
PropertyChangeListener {
public class AnalysisOptionsDialog extends DialogComponentProvider {
private boolean doAnalysis;
private AnalysisPanel panel;
private EditorStateFactory editorStateFactory = new EditorStateFactory();
@ -64,21 +63,6 @@ public class AnalysisOptionsDialog extends DialogComponentProvider implements
setRememberSize(true);
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
// On any analyzer status change, update the options for all programs
// being analyzed. This is necessary to keep options consistent across all
// programs being analyzed.
//
// Note: It's possible to receive a property change notification before the
// analysis panel has finished being constructed, so protect against
// that before calling the update method.
if (panel != null) {
panel.updateOptionForAllPrograms(evt.getPropertyName(), (Boolean) evt.getNewValue());
}
}
@Override
public void okCallback() {
try {
@ -102,7 +86,8 @@ public class AnalysisOptionsDialog extends DialogComponentProvider implements
* @return the new analysis panel
*/
private AnalysisPanel buildComponent(List<Program> programs) {
AnalysisPanel panel = new AnalysisPanel(programs, editorStateFactory, this);
AnalysisPanel panel = new AnalysisPanel(programs, editorStateFactory);
return panel;
}
}

View file

@ -16,7 +16,10 @@
package ghidra.app.plugin.core.analysis;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.beans.*;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.List;
@ -26,29 +29,47 @@ import javax.swing.event.ListSelectionEvent;
import javax.swing.table.TableColumn;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FilenameUtils;
import docking.help.Help;
import docking.help.HelpService;
import docking.options.editor.GenericOptionsComponent;
import docking.widgets.OptionDialog;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.label.GLabel;
import docking.widgets.table.GTable;
import ghidra.GhidraOptions;
import ghidra.app.services.Analyzer;
import ghidra.framework.Application;
import ghidra.framework.GenericRunInfo;
import ghidra.framework.options.*;
import ghidra.framework.preferences.Preferences;
import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.exception.AssertException;
import ghidra.util.layout.VerticalLayout;
import utilities.util.FileUtilities;
class AnalysisPanel extends JPanel implements PropertyChangeListener {
// create an empty options to represent the defaults of the analyzers
private static final Options STANDARD_DEFAULT_OPTIONS =
new FileOptions("Standard Defaults");
private static final String OPTIONS_FILE_EXTENSION = "options";
public static final String PROTOTYPE = " (Prototype)";
public final static int COLUMN_ANALYZER_IS_ENABLED = 0;
private static final String ANALYZER_OPTIONS_SAVE_DIR = "analyzer_options";
// preference which retains last used analyzer_options file name
private static final String LAST_DEFAULT_OPTIONS = "LAST_ANALYSIS_OPTIONS_USED";
private List<Program> programs;
private PropertyChangeListener propertyChangeListener;
private Options analysisOptions;
private Options currentProgramOptions; // this will have all the non-default options from the program
private Options selectedOptions = STANDARD_DEFAULT_OPTIONS;
private JTable table;
private AnalysisEnablementTableModel model;
@ -61,6 +82,8 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
private EditorStateFactory editorStateFactory;
private JPanel noOptionsPanel;
private GhidraComboBox<Options> defaultOptionsCombo;
private JButton deleteButton;
/**
* Constructor
@ -74,6 +97,18 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
this(List.of(program), editorStateFactory, propertyChangeListener);
}
/**
* Constructor
*
* @param programs list of programs that will be analyzed
* @param editorStateFactory the editor factory
* @param propertyChangeListener subscriber for property change notifications
*/
AnalysisPanel(List<Program> programs, EditorStateFactory editorStateFactory) {
this(programs, editorStateFactory, e -> {});
}
/**
* Constructor
*
@ -89,15 +124,30 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
if (CollectionUtils.isEmpty(programs)) {
throw new AssertException("Must provide a program to run analysis");
}
this.programs = programs;
this.propertyChangeListener = propertyChangeListener;
this.editorStateFactory = editorStateFactory;
analysisOptions = programs.get(0).getOptions(Program.ANALYSIS_PROPERTIES);
currentProgramOptions = getNonDefaultProgramOptions();
setName("Analysis Panel");
build();
load();
loadCurrentOptionsIntoEditors();
}
/**
* Copies the the non-default options from the program analysis options into a new options object
* @return the the non-default options from the program analysis options into a new options object
*/
private Options getNonDefaultProgramOptions() {
FileOptions options = new FileOptions("Current Program Options");
List<String> optionNames = analysisOptions.getOptionNames();
for (String optionName : optionNames) {
if (!analysisOptions.isDefaultValue(optionName)) {
options.putObject(optionName, analysisOptions.getObject(optionName, null));
}
}
return options;
}
private void load() {
@ -141,6 +191,12 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
}
private void build() {
setLayout(new BorderLayout());
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
add(buildMainPanel(), BorderLayout.CENTER);
}
private JComponent buildMainPanel() {
buildTable();
buildAnalyzerOptionsPanel();
@ -148,9 +204,7 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, buildLeftPanel(), buildRightPanel());
splitpane.setBorder(null);
setLayout(new BorderLayout());
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
add(splitpane, BorderLayout.CENTER);
return splitpane;
}
private void buildAnalyzerOptionsPanel() {
@ -158,6 +212,27 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
configureBorder(analyzerOptionsPanel, "Options");
}
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);
deleteButton = new JButton("Delete");
deleteButton.addActionListener(e -> deleteSelectedOptionsConfiguration());
deleteButton.setToolTipText("Deletes the currently selected user configuration");
panel.add(deleteButton);
panel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
return panel;
}
private Component buildRightPanel() {
JSplitPane splitpane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, buildDescriptionPanel(),
analyzerOptionsPanel);
@ -185,7 +260,7 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
}
private JPanel buildLeftPanel() {
JPanel buttonPanel = buildButtonPanel();
JPanel buttonPanel = buildControlPanel();
JScrollPane scrollPane = new JScrollPane(table);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
@ -199,20 +274,53 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
return panel;
}
private JPanel buildControlPanel() {
JPanel panel = new JPanel(new BorderLayout());
panel.add(buildButtonPanel(), BorderLayout.NORTH);
panel.add(buildOptionsComboBoxPanel(), BorderLayout.SOUTH);
return panel;
}
private JPanel buildButtonPanel() {
JButton selectAllButton = new JButton("Select All");
selectAllButton.addActionListener(e -> selectAll());
JButton deselectAllButton = new JButton("Deselect All");
deselectAllButton.addActionListener(e -> deselectAll());
JButton restoreDefaultsButton = new JButton("Restore Defaults");
restoreDefaultsButton.addActionListener(e -> restoreDefaults());
JButton resetButton = new JButton("Reset");
resetButton.setToolTipText("Resets the editors to the selected options configuration");
resetButton.addActionListener(e -> loadCurrentOptionsIntoEditors());
JButton saveButton = new JButton("Save...");
saveButton.setToolTipText("Saves the current editor settings to a named configuration");
saveButton.addActionListener(e -> saveCurrentOptionsConfiguration());
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
buttonPanel.add(selectAllButton);
buttonPanel.add(deselectAllButton);
buttonPanel.add(restoreDefaultsButton);
buttonPanel.add(resetButton);
buttonPanel.add(saveButton);
return buttonPanel;
}
private void deleteSelectedOptionsConfiguration() {
if (!isUserConfiguration(selectedOptions)) {
// can only delete user configurations
return;
}
String configurationName = selectedOptions.getName();
int result = OptionDialog.showYesNoDialog(this, "Delete Configuration?",
"Are you sure you want to delete options configuration \"" + configurationName + "\"?");
if (result != OptionDialog.YES_OPTION) {
return;
}
File configurationFile = getOptionsSaveFile(configurationName);
configurationFile.delete();
selectedOptions = currentProgramOptions;
reloadOptionsCombo(currentProgramOptions);
loadCurrentOptionsIntoEditors();
}
private void selectAll() {
int rowCount = model.getRowCount();
for (int i = 0; i < rowCount; ++i) {
@ -227,15 +335,83 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
}
}
private void restoreDefaults() {
int answer = OptionDialog.showYesNoDialog(this, "Restore Default Analysis Options",
"Do you really want to restore the analysis options to the default values?");
if (answer == OptionDialog.YES_OPTION) {
AutoAnalysisManager manager = AutoAnalysisManager.getAnalysisManager(programs.get(0));
manager.restoreDefaultOptions();
editorStateFactory.clearAll();
load();
private void saveCurrentOptionsConfiguration() {
String defaultSaveName = "";
if (selectedOptions != STANDARD_DEFAULT_OPTIONS &&
selectedOptions != currentProgramOptions) {
defaultSaveName = selectedOptions.getName();
}
String saveName = OptionDialog.showInputSingleLineDialog(this, "Save Configuration",
"Options Configuration Name", defaultSaveName);
if (saveName == null) {
return;
}
saveName = saveName.trim();
if (saveName.length() == 0) {
return;
}
File saveFile = getOptionsSaveFile(saveName);
FileOptions saved = saveCurrentOptions();
try {
saved.save(saveFile);
reloadOptionsCombo(saved);
}
catch (IOException e) {
Msg.error(this, "Error saving default options", e);
}
}
private FileOptions saveCurrentOptions() {
FileOptions saveTo = new FileOptions("");
List<AnalyzerEnablementState> analyzerStates = model.getModelData();
for (AnalyzerEnablementState analyzerState : analyzerStates) {
String analyzerName = analyzerState.getName();
boolean enabled = analyzerState.isEnabled();
if (!Objects.equals(Boolean.valueOf(enabled),
analysisOptions.getDefaultValue(analyzerName))) {
saveTo.setBoolean(analyzerName, enabled);
}
}
for (EditorState editorState : editorList) {
editorState.applyNonDefaults(saveTo);
}
return saveTo;
}
private void loadCurrentOptionsIntoEditors() {
List<AnalyzerEnablementState> analyzerStates = model.getModelData();
for (AnalyzerEnablementState analyzerState : analyzerStates) {
String analyzerName = analyzerState.getName();
Object defaultObject = analysisOptions.getDefaultValue(analyzerName);
boolean defaultValue =
(defaultObject instanceof Boolean) ? (Boolean) defaultObject : false;
boolean newValue = selectedOptions.getBoolean(analyzerName, defaultValue);
analyzerState.setEnabled(newValue);
setAnalyzerEnabled(analyzerName, newValue, false);
model.fireTableRowsUpdated(0, model.getRowCount() - 1);
}
for (EditorState editorState : editorList) {
editorState.loadFrom(selectedOptions);
}
updateDeleteButton();
}
private void reloadOptionsCombo(Options newDefaultOptions) {
Options[] defaultOptionsArray = getDefaultOptionsArray();
defaultOptionsCombo.setModel(new DefaultComboBoxModel<Options>(defaultOptionsArray));
Options selected = findOptions(defaultOptionsArray, newDefaultOptions.getName());
defaultOptionsCombo.setSelectedItem(selected);
}
private Options findOptions(Options[] defaultOptionsArray, String name) {
for (Options fileOptions : defaultOptionsArray) {
if (fileOptions.getName().equals(name)) {
return fileOptions;
}
}
return STANDARD_DEFAULT_OPTIONS;
}
private void configureEnabledColumnWidth(int width) {
@ -290,7 +466,7 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
component.setBorder(compoundBorder);
}
void setAnalyzerEnabled(String analyzerName, boolean enabled) {
void setAnalyzerEnabled(String analyzerName, boolean enabled, boolean fireEvent) {
List<Component> list = analyzerManagedComponentsMap.get(analyzerName);
if (list != null) {
Iterator<Component> iterator = list.iterator();
@ -299,23 +475,25 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
next.setEnabled(enabled);
}
}
propertyChange(null);
if (fireEvent) {
propertyChange(null);
}
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (checkForDifferences()) {
if (checkForDifferencesWithProgram()) {
propertyChangeListener.propertyChange(
new PropertyChangeEvent(this, GhidraOptions.APPLY_ENABLED, null, Boolean.TRUE));
}
}
private boolean checkForDifferences() {
private boolean checkForDifferencesWithProgram() {
List<AnalyzerEnablementState> analyzerStates = model.getModelData();
boolean changes = false;
for (int i = 0; i < analyzerStates.size(); ++i) {
String analyzerName = analyzerStates.get(i).getName();
boolean currEnabled = analyzerStates.get(i).isEnabled();
for (AnalyzerEnablementState analyzerState : analyzerStates) {
String analyzerName = analyzerState.getName();
boolean currEnabled = analyzerState.isEnabled();
boolean origEnabled = analysisOptions.getBoolean(analyzerName, false);
if (currEnabled != origEnabled) {
changes = true;
@ -342,26 +520,56 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
* analyzed.
*/
void applyChanges() {
List<AnalyzerEnablementState> analyzerStates = model.getModelData();
for (AnalyzerEnablementState analyzerState : analyzerStates) {
String analyzerName = analyzerState.getName();
boolean enabled = analyzerState.isEnabled();
int id = programs.get(0).startTransaction("setting analysis options");
boolean commit = false;
try {
int id = programs.get(0).startTransaction("Setting Analysis Options");
boolean commit = false;
try {
List<AnalyzerEnablementState> analyzerStates = model.getModelData();
for (AnalyzerEnablementState analyzerState : analyzerStates) {
String analyzerName = analyzerState.getName();
boolean enabled = analyzerState.isEnabled();
analysisOptions.setBoolean(analyzerName, enabled);
commit = true;
}
finally {
programs.get(0).endTransaction(id, commit);
for (EditorState info : editorList) {
info.applyValue();
}
updateOptionForAllPrograms(analyzerName, enabled);
}
finally {
programs.get(0).endTransaction(id, commit);
}
for (EditorState info : editorList) {
info.applyValue();
copyOptionsToAllPrograms();
currentProgramOptions = getNonDefaultProgramOptions();
reloadOptionsCombo(currentProgramOptions);
}
private void copyOptionsToAllPrograms() {
for (int i = 1; i < programs.size(); i++) {
Program program = programs.get(i);
int id = program.startTransaction("Setting Analysis Options");
boolean commit = false;
try {
copyOptionsTo(program);
commit = true;
}
finally {
program.endTransaction(id, commit);
}
}
}
private void copyOptionsTo(Program program) {
Options destinationOptions = program.getOptions(Program.ANALYSIS_PROPERTIES);
// first remove all options in destination
for (String optionName : destinationOptions.getOptionNames()) {
destinationOptions.removeOption(optionName);
}
// now copy all the options in the source
for (String optionName : analysisOptions.getOptionNames()) {
destinationOptions.putObject(optionName, analysisOptions.getObject(optionName, null));
}
}
@ -415,7 +623,7 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
if (value instanceof Boolean) {
enabled = (Boolean) value;
}
setAnalyzerEnabled(analyzerName, enabled);
setAnalyzerEnabled(analyzerName, enabled, false);
}
}
@ -478,4 +686,126 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener {
}
}
private String getLastUsedDefaultOptionsName() {
// if the program has non-default options, then it has been analyzed before, use its
// current settings initially
if (isAnalyzed()) {
return currentProgramOptions.getName();
}
// Otherwise, use the last used analysis options configuration
return Preferences.getProperty(LAST_DEFAULT_OPTIONS, 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() {
List<Options> savedDefaultsList = getSavedOptionsObjects();
Options[] optionsArray = new FileOptions[savedDefaultsList.size() + 2]; // 2 standard configurations always present
optionsArray[0] = currentProgramOptions;
optionsArray[1] = STANDARD_DEFAULT_OPTIONS;
for (int i = 0; i < savedDefaultsList.size(); i++) {
optionsArray[i + 2] = savedDefaultsList.get(i);
}
return optionsArray;
}
private File getOptionsSaveFile(String saveName) {
File userSettingsDirectory = Application.getUserSettingsDirectory();
File optionsDir = new File(userSettingsDirectory, ANALYZER_OPTIONS_SAVE_DIR);
FileUtilities.mkdirs(optionsDir);
return new File(optionsDir, saveName + "." + OPTIONS_FILE_EXTENSION);
}
private List<Options> getSavedOptionsObjects() {
File userSettingsDirectory = Application.getUserSettingsDirectory();
File optionsDir = new File(userSettingsDirectory, ANALYZER_OPTIONS_SAVE_DIR);
if (!optionsDir.isDirectory()) {
// new installation, copy any old saved analysis options files to current
migrateOptionsFromPreviousRevision(optionsDir);
}
return readSavedOptions(optionsDir);
}
private List<Options> readSavedOptions(File optionsDir) {
List<Options> list = new ArrayList<>();
File[] listFiles = optionsDir.listFiles();
for (File file : listFiles) {
if (OPTIONS_FILE_EXTENSION.equals(FilenameUtils.getExtension(file.getName()))) {
FileOptions fileOptions;
try {
fileOptions = new FileOptions(file);
list.add(fileOptions);
}
catch (IOException e) {
Msg.error(this, "Error reading saved analysis options", e);
}
}
}
return list;
}
private void migrateOptionsFromPreviousRevision(File optionsDir) {
FileUtilities.mkdirs(optionsDir);
File previous = getMostRecentApplicationSettingsDirWithSavedOptions();
if (previous == null) {
return;
}
List<Options> readSavedOptions = readSavedOptions(previous);
for (Options options : readSavedOptions) {
FileOptions fileOptions = (FileOptions) options;
String name = fileOptions.getName();
try {
fileOptions.save(getOptionsSaveFile(name));
} catch (IOException e) {
Msg.error(this, "Error copying analysis options from previous Ghidra install", e);
}
}
}
private File getMostRecentApplicationSettingsDirWithSavedOptions() {
List<File> ghidraUserDirsByTime = GenericRunInfo.getPreviousApplicationSettingsDirsByTime();
if (ghidraUserDirsByTime.size() == 0) {
return null;
}
// get the tools from the most recent projects first
for (File ghidraUserDir : ghidraUserDirsByTime) {
File possible = new File(ghidraUserDir, ANALYZER_OPTIONS_SAVE_DIR);
if (possible.exists()) {
return possible;
}
}
return null;
}
private boolean isUserConfiguration(Options options) {
if (options == STANDARD_DEFAULT_OPTIONS ||
options == currentProgramOptions) {
// these two are not user configurations.
return false;
}
return true;
}
private void analysisComboChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
selectedOptions = (FileOptions) defaultOptionsCombo.getSelectedItem();
updateDeleteButton();
loadCurrentOptionsIntoEditors();
// save off preference (unless it is the current program options, then don't save it)
if (selectedOptions != currentProgramOptions) {
Preferences.setProperty(LAST_DEFAULT_OPTIONS, selectedOptions.getName());
}
}
}
private void updateDeleteButton() {
deleteButton.setEnabled(isUserConfiguration(selectedOptions));
}
}

View file

@ -0,0 +1,355 @@
/* ###
* 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.analysis;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.ComboBoxModel;
import javax.swing.table.TableModel;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import docking.action.DockingActionIf;
import docking.widgets.OptionDialog;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.dialogs.InputDialog;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.app.services.ProgramManager;
import ghidra.framework.options.Options;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.listing.Program;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.test.TestEnv;
public class AnalysisOptionsTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env;
private PluginTool tool;
private Program program;
private AnalysisOptionsDialog optionsDialog;
@Before
public void setUp() throws Exception {
env = new TestEnv();
tool = env.getTool();
tool.addPlugin(CodeBrowserPlugin.class.getName());
addPlugin(tool, AutoAnalysisPlugin.class);
showTool(tool);
program = buildProgram("test", ProgramBuilder._TOY);
ProgramManager pm = tool.getService(ProgramManager.class);
pm.openProgram(program.getDomainFile());
optionsDialog = invokeAnalysisDialog();
}
private Program buildProgram(String name, String languageID) throws Exception {
ProgramBuilder builder = new ProgramBuilder(name, languageID);
builder.createMemory("test1", "0x1000", 0x2000);
return builder.getProgram();
}
@After
public void tearDown() throws Exception {
env.release(program);
tool.close();
env.dispose();
}
@Test
public void testSelectAll() throws Exception {
setAnalyzerEnabled("Stack", false);
setAnalyzerEnabled("Reference", false);
setAnalyzerEnabled("ASCII Strings", false);
assertFalse(isAnalyzerEnabled("Stack"));
assertFalse(isAnalyzerEnabled("Reference"));
assertFalse(isAnalyzerEnabled("ASCII Strings"));
pressButtonByText(optionsDialog, "Select All");
assertTrue(isAnalyzerEnabled("Stack"));
assertTrue(isAnalyzerEnabled("Reference"));
assertTrue(isAnalyzerEnabled("ASCII Strings"));
}
@Test
public void testDeselectAll() throws Exception {
setAnalyzerEnabled("Stack", true);
setAnalyzerEnabled("Reference", true);
setAnalyzerEnabled("ASCII Strings",true);
assertTrue(isAnalyzerEnabled("Stack"));
assertTrue(isAnalyzerEnabled("Reference"));
assertTrue(isAnalyzerEnabled("ASCII Strings"));
pressButtonByText(optionsDialog, "Deselect All");
assertFalse(isAnalyzerEnabled("Stack"));
assertFalse(isAnalyzerEnabled("Reference"));
assertFalse(isAnalyzerEnabled("ASCII Strings"));
}
@Test
public void testReset() throws Exception {
assertTrue(isAnalyzerEnabled("Stack"));
assertTrue(isAnalyzerEnabled("Reference"));
assertTrue(isAnalyzerEnabled("ASCII Strings"));
setAnalyzerEnabled("Stack", false);
setAnalyzerEnabled("Reference", false);
setAnalyzerEnabled("ASCII Strings", false);
assertFalse(isAnalyzerEnabled("Stack"));
assertFalse(isAnalyzerEnabled("Reference"));
assertFalse(isAnalyzerEnabled("ASCII Strings"));
pressButtonByText(optionsDialog, "Reset");
assertTrue(isAnalyzerEnabled("Stack"));
assertTrue(isAnalyzerEnabled("Reference"));
assertTrue(isAnalyzerEnabled("ASCII Strings"));
}
@Test
public void testSaveConfiguration() {
assertComboboxEquals("Current Program Options");
setAnalyzerEnabled("Stack", false);
setAnalyzerEnabled("Reference", false);
setAnalyzerEnabled("ASCII Strings", false);
pressButtonByText(optionsDialog, "Save", false);
saveConfig("foo");
assertComboboxEquals("foo");
}
@Test
public void testDeleteConfiguration() {
assertComboboxEquals("Current Program Options");
createConfig("foo", false, false, false);
assertComboboxEquals("foo");
pressButtonByText(optionsDialog, "Delete", false);
confirmDelete();
assertComboboxEquals("Current Program Options");
}
@Test
public void testSwitchCombo() {
createConfig("a", false, false, false);
createConfig("b", false, true, false);
createConfig("c", true, false, true);
assertComboboxEquals("c");
assertTrue(isAnalyzerEnabled("Stack"));
assertFalse(isAnalyzerEnabled("Reference"));
assertTrue(isAnalyzerEnabled("ASCII Strings"));
setCombobox("a");
assertFalse(isAnalyzerEnabled("Stack"));
assertFalse(isAnalyzerEnabled("Reference"));
assertFalse(isAnalyzerEnabled("ASCII Strings"));
setCombobox("b");
assertFalse(isAnalyzerEnabled("Stack"));
assertTrue(isAnalyzerEnabled("Reference"));
assertFalse(isAnalyzerEnabled("ASCII Strings"));
}
@Test
public void testCancelDialogDoesntSaveChanges() {
assertComboboxEquals("Current Program Options");
assertTrue(isAnalyzerEnabledInProgramOptions("Stack"));
assertTrue(isAnalyzerEnabledInProgramOptions("Reference"));
assertTrue(isAnalyzerEnabled("Stack"));
assertTrue(isAnalyzerEnabled("Reference"));
setAnalyzerEnabled("Stack", false);
setAnalyzerEnabled("Reference", false);
assertTrue(isAnalyzerEnabledInProgramOptions("Stack"));
assertTrue(isAnalyzerEnabledInProgramOptions("Reference"));
assertFalse(isAnalyzerEnabled("Stack"));
assertFalse(isAnalyzerEnabled("Reference"));
pressButtonByText(optionsDialog, "Cancel");
assertTrue(isAnalyzerEnabledInProgramOptions("Stack"));
assertTrue(isAnalyzerEnabledInProgramOptions("Reference"));
}
@Test
public void testAnalyzeSavesChangesToProgram() {
assertComboboxEquals("Current Program Options");
assertTrue(isAnalyzerEnabledInProgramOptions("Stack"));
assertTrue(isAnalyzerEnabledInProgramOptions("Reference"));
assertTrue(isAnalyzerEnabled("Stack"));
assertTrue(isAnalyzerEnabled("Reference"));
setAnalyzerEnabled("Stack", false);
setAnalyzerEnabled("Reference", false);
assertTrue(isAnalyzerEnabledInProgramOptions("Stack"));
assertTrue(isAnalyzerEnabledInProgramOptions("Reference"));
assertFalse(isAnalyzerEnabled("Stack"));
assertFalse(isAnalyzerEnabled("Reference"));
pressButtonByText(optionsDialog, "Analyze");
assertFalse(isAnalyzerEnabledInProgramOptions("Stack"));
assertFalse(isAnalyzerEnabledInProgramOptions("Reference"));
}
//==================================================================================================
// Private Methods
//==================================================================================================
private void createConfig(String name, boolean stackOn, boolean refOn, boolean stringOn) {
setAnalyzerEnabled("Stack", stackOn);
setAnalyzerEnabled("Reference", refOn);
setAnalyzerEnabled("ASCII Strings", stringOn);
pressButtonByText(optionsDialog, "Save", false);
saveConfig(name);
}
private void assertComboboxEquals(String name) {
AnalysisPanel panel = (AnalysisPanel) getInstanceField("panel", optionsDialog);
@SuppressWarnings("unchecked")
GhidraComboBox<Options> combo = (GhidraComboBox<Options>) getInstanceField("defaultOptionsCombo", panel);
assertEquals(name, ((Options)combo.getSelectedItem()).getName());
}
private void setCombobox(String name) {
runSwing(() -> {
AnalysisPanel panel = (AnalysisPanel) getInstanceField("panel", optionsDialog);
@SuppressWarnings("unchecked")
GhidraComboBox<Options> combo = (GhidraComboBox<Options>) getInstanceField("defaultOptionsCombo", panel);
ComboBoxModel<Options> model = combo.getModel();
for (int i = 0; i < model.getSize(); i++) {
Options elementAt = model.getElementAt(i);
if (elementAt.getName().equals(name)) {
combo.setSelectedItem(elementAt);
return;
}
}
fail("Couldn't find combobox item: " + name);
});
}
private void saveConfig(String name) {
InputDialog dialog = waitForDialogComponent(InputDialog.class);
runSwing(() -> dialog.setValue(name));
pressButtonByText(dialog, "OK");
waitForSwing();
}
private void confirmDelete() {
OptionDialog dialog = waitForDialogComponent(OptionDialog.class);
pressButtonByText(dialog, "Yes");
waitForSwing();
}
private AnalysisOptionsDialog invokeAnalysisDialog() {
DockingActionIf action = getAction(tool, "Auto Analyze");
performAction(action, false);
return waitForDialogComponent(AnalysisOptionsDialog.class);
}
private void apply() {
pressButtonByText(optionsDialog, "Analyze");
}
private boolean isAnalyzerEnabledInProgramOptions(String analyzerName) {
Options options = program.getOptions(Program.ANALYSIS_PROPERTIES);
return options.getBoolean(analyzerName, false);
}
private void verifyDefaultOptions(Collection<Program> programs) {
for (Program program : programs) {
Options options = program.getOptions(Program.ANALYSIS_PROPERTIES);
for (String name : options.getOptionNames()) {
assertTrue("Program options are unexpectedly changed: " + program,
options.isDefaultValue(name));
}
}
}
private void setAnalyzerEnabled(String name, boolean enabled) {
TableModel model = getAnalyzerTableModel();
int analyzerRow = getRowForAnalyzer(name, model);
runSwing(new Runnable() {
@Override
public void run() {
model.setValueAt(enabled, analyzerRow, 0);
}
});
}
private boolean isAnalyzerEnabled(String name) {
TableModel model = getAnalyzerTableModel();
int analyzerRow = getRowForAnalyzer(name, model);
AtomicBoolean result = new AtomicBoolean();
runSwing(new Runnable() {
@Override
public void run() {
result.set((Boolean)model.getValueAt(analyzerRow, 0));
}
});
return result.get();
}
private TableModel getAnalyzerTableModel() {
// The analysis dialog uses a table to display the enablement and name of each
// analyzer
AnalysisPanel panel = (AnalysisPanel) getInstanceField("panel", optionsDialog);
return (TableModel) getInstanceField("model", panel);
}
private int getRowForAnalyzer(String name, TableModel model) {
int rowCount = model.getRowCount();
int row = 0;
for (row = 0; row < rowCount; row++) {
String rowName = (String) model.getValueAt(row, 1);
if (name.equals(rowName)) {
break;// found it
}
}
if (row == rowCount) {
Assert.fail("Couldn't find analyzer named " + name);
}
int analyzerRow = row;
return analyzerRow;
}
}

View file

@ -298,6 +298,7 @@ public class AnalyzeAllOpenProgramsTaskTest extends AbstractGhidraHeadedIntegrat
findComponent(optionsDialog.getComponent(), AnalysisPanel.class, false);
runSwing(() -> invokeInstanceMethod("deselectAll", panel));
runSwing(() -> panel.applyChanges());
close(optionsDialog);
}

View file

@ -0,0 +1,132 @@
/* ###
* 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.framework.options;
import static org.junit.Assert.*;
import java.awt.Color;
import java.io.File;
import java.io.IOException;
import org.junit.Test;
import generic.test.AbstractGenericTest;
public class FileOptionsTest extends AbstractGenericTest {
@Test
public void testSavingRestoringFileOptions() throws IOException {
FileOptions options = new FileOptions("Test");
options.registerOption("aaa", Integer.valueOf(5), null, "aaa description");
options.registerOption("bbb", Integer.valueOf(5), null, "bbb description");
options.registerOption("ccc", Color.RED, null, "ccc description");
TestCustomOption custom = new TestCustomOption("bob", 23, true);
options.setInt("aaa", 10);
options.setColor("ccc", Color.BLUE);
options.setCustomOption("ddd", custom);
assertEquals(10, options.getInt("aaa", 0));
assertEquals(5, options.getInt("bbb", 0));
assertEquals(Color.BLUE, options.getColor("ccc", null));
assertEquals(custom, options.getCustomOption("ddd", null));
File file = createTempFile("optionsFile", "options");
options.save(file);
FileOptions restored = new FileOptions(file);
assertEquals(10, restored.getInt("aaa", 0));
assertFalse(restored.contains("bbb")); // default value should not have been saved
assertEquals(Color.BLUE, restored.getColor("ccc", null));
assertEquals(custom, restored.getCustomOption("ddd", null));
}
public static class TestCustomOption implements CustomOption {
String name;
int count;
boolean active;
public TestCustomOption() {
}
public TestCustomOption(String name, int count, boolean active) {
this.name = name;
this.count = count;
this.active = active;
}
@Override
public void readState(SaveState saveState) {
name = saveState.getString("name", null);
count = saveState.getInt("count", 0);
active = saveState.getBoolean("active", false);
}
@Override
public void writeState(SaveState saveState) {
saveState.putString("name", name);
saveState.putInt("count", count);
saveState.putBoolean("active", active);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (active ? 1231 : 1237);
result = prime * result + count;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
TestCustomOption other = (TestCustomOption) obj;
if (active != other.active) {
return false;
}
if (count != other.count) {
return false;
}
if (name == null) {
if (other.name != null) {
return false;
}
}
else if (!name.equals(other.name)) {
return false;
}
return true;
}
}
}

View file

@ -17,11 +17,9 @@ package ghidra.framework.options;
import java.awt.Component;
import java.beans.*;
import java.util.HashSet;
import java.util.Set;
import java.util.*;
import ghidra.framework.Application;
import ghidra.util.SystemUtilities;
public class EditorState implements PropertyChangeListener {
@ -90,11 +88,32 @@ public class EditorState implements PropertyChangeListener {
}
public boolean isValueChanged() {
return !SystemUtilities.isEqual(currentValue, originalValue);
return !Objects.equals(currentValue, originalValue);
}
public void applyNonDefaults(Options save) {
if (!Objects.equals(currentValue, options.getDefaultValue(name))) {
Options sub = save.getOptions(options.getName());
sub.putObject(name, currentValue);
}
}
public void loadFrom(Options loadFrom) {
Options sub = loadFrom.getOptions(options.getName());
Object newValue = sub.getObject(name, options.getDefaultValue(name));
if (editor != null && !Objects.equals(currentValue, newValue)) {
editor.setValue(newValue);
}
}
public boolean hasSameValue(Options compareTo) {
Options sub = compareTo.getOptions(options.getName());
Object newValue = sub.getObject(name, options.getDefaultValue(name));
return Objects.equals(newValue, currentValue);
}
public void applyValue() {
if (SystemUtilities.isEqual(currentValue, originalValue)) {
if (Objects.equals(currentValue, originalValue)) {
return;
}
boolean success = false;
@ -166,4 +185,5 @@ public class EditorState implements PropertyChangeListener {
public String getDescription() {
return options.getDescription(name);
}
}

View file

@ -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.
@ -26,7 +25,8 @@ public class EditorStateFactory {
public EditorStateFactory() {
}
public EditorState getEditorState(Options options, String name, PropertyChangeListener listener) {
public EditorState getEditorState(Options options, String name,
PropertyChangeListener listener) {
String optionID = options.getID(name);
EditorState editorState = cache.get(optionID);

View file

@ -0,0 +1,156 @@
/* ###
* 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.framework.options;
import java.beans.PropertyEditor;
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FilenameUtils;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
public class FileOptions extends AbstractOptions {
private File file;
public FileOptions(String name) {
super(name);
}
public FileOptions(File file) throws IOException {
this(FilenameUtils.getBaseName(file.getName()));
this.file = file;
loadFromFile();
}
public void save(File saveFile) throws IOException {
this.name = FilenameUtils.getBaseName(saveFile.getName());
this.file = saveFile;
saveToFile();
}
public File getFile() {
return file;
}
public CustomOption readCustomOption(SaveState saveState) {
String customOptionClassName = saveState.getString("CUSTOM_OPTION_CLASS", null);
try {
Class<?> c = Class.forName(customOptionClassName);
CustomOption customOption = (CustomOption) c.getDeclaredConstructor().newInstance();
customOption.readState(saveState);
return customOption;
}
catch (Exception e) {
Msg.error(this, "Can't create customOption instance for: " + customOptionClassName, e);
}
return null;
}
private void loadFromFile() throws IOException {
SaveState saveState = SaveState.readJsonFile(file);
for (String optionName : saveState.getNames()) {
Object object = saveState.getObject(optionName);
if (object instanceof SaveState) {
SaveState customState = (SaveState) object;
object = readCustomOption(customState);
}
Option option =
createUnregisteredOption(optionName, OptionType.getOptionType(object), null);
option.doSetCurrentValue(object); // use doSet versus set so that it is not registered
valueMap.put(optionName, option);
}
}
private void saveToFile() throws IOException {
SaveState saveState = new SaveState("File_Options");
for (String optionName : valueMap.keySet()) {
Option optionValue = valueMap.get(optionName);
if (!optionValue.isDefault()) {
Object value = optionValue.getValue(null);
if (value instanceof CustomOption) {
SaveState customState = new SaveState();
customState.putString("CUSTOM_OPTION_CLASS", value.getClass().getName());
((CustomOption) value).writeState(customState);
value = customState;
}
saveState.putObject(optionName, value);
}
}
saveState.saveToJsonFile(file);
}
@Override
protected Option createRegisteredOption(String optionName, OptionType type, String description,
HelpLocation help, Object defaultValue, PropertyEditor editor) {
return new FileOption(optionName, type, description, help, defaultValue, true, editor);
}
@Override
protected Option createUnregisteredOption(String optionName, OptionType type,
Object defaultValue) {
return new FileOption(optionName, type, null, null, defaultValue, false, null);
}
@Override
protected boolean notifyOptionChanged(String optionName, Object oldValue, Object newValue) {
// do nothing for now
return true;
}
private static class FileOption extends Option {
private Object currentValue;
FileOption(String name, OptionType type, String description, HelpLocation helpLocation,
Object defaultValue, boolean isRegistered, PropertyEditor editor) {
super(name, type, description, helpLocation, defaultValue, isRegistered, editor);
this.currentValue = defaultValue;
}
@Override
public Object getCurrentValue() {
return currentValue;
}
@Override
public void doSetCurrentValue(Object value) {
currentValue = value;
}
}
@Override
public String toString() {
return name;
}
public FileOptions copy() {
FileOptions copy = new FileOptions("new");
for (String optionName : valueMap.keySet()) {
Option optionValue = valueMap.get(optionName);
if (!optionValue.isDefault()) {
Object value = optionValue.getValue(null);
copy.putObject(optionName, value);
}
}
return copy;
}
}