mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-06 03:50:02 +02:00
Merge remote-tracking branch
'origin/GP-544_ghidravore_default_analysis_options'
This commit is contained in:
commit
da3a80f4fe
12 changed files with 1082 additions and 66 deletions
|
@ -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 |
Binary file not shown.
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 80 KiB |
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -298,6 +298,7 @@ public class AnalyzeAllOpenProgramsTaskTest extends AbstractGhidraHeadedIntegrat
|
|||
findComponent(optionsDialog.getComponent(), AnalysisPanel.class, false);
|
||||
|
||||
runSwing(() -> invokeInstanceMethod("deselectAll", panel));
|
||||
runSwing(() -> panel.applyChanges());
|
||||
|
||||
close(optionsDialog);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue