diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisOptionsUpdater.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisOptionsUpdater.java new file mode 100644 index 0000000000..0f8f5e1387 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisOptionsUpdater.java @@ -0,0 +1,127 @@ +/* ### + * 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 java.util.*; +import java.util.function.Function; + +import ghidra.framework.options.Options; + +/** + * An object that allows analyzers to rename options. This is required to move old options stored + * in the program to the new equivalent option. This class is not required for options that have + * simply been removed. + *

+ * Notes: + *

+ */ +public class AnalysisOptionsUpdater { + + private static final Function OLD_VALUE_REPLACER = oldValue -> oldValue; + + private Map optionsByNewName = new HashMap<>(); + + /** + * Register the given old option name to be replaced with the new option name. The + * replacement strategy used in this case will be to return the old value for the new option. + * @param newOptionName the new option name + * @param oldOptionName the old option name + */ + public void registerReplacement(String newOptionName, String oldOptionName) { + + registerReplacement(newOptionName, oldOptionName, OLD_VALUE_REPLACER); + } + + /** + * Register the given old option name to be replaced with the new option name. The given + * replacer function will be called with the old option value to get the new option value. + * @param newOptionName the new option name + * @param oldOptionName the old option name + * @param replacer the function to update the update the old option value + */ + public void registerReplacement(String newOptionName, String oldOptionName, + Function replacer) { + + optionsByNewName.put(newOptionName, + new ReplaceableOption(newOptionName, oldOptionName, replacer)); + } + + Set getReplaceableOptions() { + return new HashSet<>(optionsByNewName.values()); + } + + /** + * A simple object that contains the new and old option name along with the replacer function + * that will handle the option replacement. + */ + public static class ReplaceableOption { + + private final String newName; + private final String oldName; + private final Function replacer; + + ReplaceableOption(String newName, String oldName, Function replacer) { + this.newName = newName; + this.oldName = oldName; + this.replacer = replacer; + } + + // note: this method expects to be called within a transaction + void replace(Options options) { + Object oldValue = options.getObject(oldName, null); + if (oldValue == null) { + return; + } + + if (options.isDefaultValue(oldName)) { + return; + } + + if (!options.isDefaultValue(newName)) { + return; // don't overwrite user's updated value + } + + Object newValue = replacer.apply(oldValue); + options.putObject(newName, newValue); + } + + String getNewName() { + return newName; + } + + String getOldName() { + return oldName; + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisPanel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisPanel.java index 44a0703718..07723d28c6 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisPanel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalysisPanel.java @@ -40,6 +40,7 @@ import docking.widgets.combobox.GhidraComboBox; import docking.widgets.label.GLabel; import docking.widgets.table.GTable; import ghidra.GhidraOptions; +import ghidra.app.plugin.core.analysis.AnalysisOptionsUpdater.ReplaceableOption; import ghidra.app.services.Analyzer; import ghidra.framework.Application; import ghidra.framework.GenericRunInfo; @@ -70,13 +71,15 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener { // preference which retains last used analyzer_options file name public static final String LAST_USED_OPTIONS_CONFIG = "LAST_USED_OPTIONS_CONFIG"; + static final String ANALYZER_OPTIONS_PANEL_NAME = "analyzer.options.panel"; + private List 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 GTable table; private AnalysisEnablementTableModel model; private JTextArea descriptionComponent; private JPanel analyzerOptionsPanel; @@ -130,6 +133,9 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener { currentProgramOptions = getNonDefaultProgramOptions(); setName("Analysis Panel"); build(); + + replaceOldOptions(); + load(); loadCurrentOptionsIntoEditors(); } @@ -169,13 +175,13 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener { Program program = programs.get(0); AutoAnalysisManager manager = AutoAnalysisManager.getAnalysisManager(program); - List propertyNames = analysisOptions.getOptionNames(); - Collections.sort(propertyNames, (o1, o2) -> o1.compareToIgnoreCase(o2)); - for (String analyzerName : propertyNames) { + List optionNames = analysisOptions.getOptionNames(); + Collections.sort(optionNames, (o1, o2) -> o1.compareToIgnoreCase(o2)); + for (String analyzerName : optionNames) { if (analyzerName.indexOf('.') == -1) { if (analysisOptions.getType(analyzerName) != OptionType.BOOLEAN_TYPE) { throw new AssertException( - "Analyzer enable property that is not boolean - " + analyzerName); + "Analyzer 'enable' property that is not boolean - " + analyzerName); } Analyzer analyzer = manager.getAnalyzer(analyzerName); @@ -408,7 +414,7 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener { private void reloadOptionsCombo(Options newDefaultOptions) { optionConfigurationChoices = loadPossibleOptionsChoicesForComboBox(); - optionsComboBox.setModel(new DefaultComboBoxModel(optionConfigurationChoices)); + optionsComboBox.setModel(new DefaultComboBoxModel<>(optionConfigurationChoices)); Options selected = findOptionsByName(newDefaultOptions.getName()); optionsComboBox.setSelectedItem(selected); } @@ -587,6 +593,66 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener { } } + private void replaceOldOptions() { + + for (Program program : programs) { + + boolean commit = false; + int id = program.startTransaction("Replacing old analysis properties"); + try { + doReplaceOldOptions(program); + commit = true; + } + finally { + program.endTransaction(id, commit); + } + } + } + + private void doReplaceOldOptions(Program program) { + + AutoAnalysisManager manager = AutoAnalysisManager.getAnalysisManager(program); + + Options programAnalysisOptions = program.getOptions(Program.ANALYSIS_PROPERTIES); + List allAnalyzersOptions = programAnalysisOptions.getChildOptions(); + for (Options analyzerOptions : allAnalyzersOptions) { + String analyzerName = analyzerOptions.getName(); + Analyzer analyzer = manager.getAnalyzer(analyzerName); + if (analyzer == null) { + // can be null if an analyzer no longer exists + continue; + } + AnalysisOptionsUpdater updater = analyzer.getOptionsUpdater(); + if (updater == null) { + continue; + } + + applyOptionUpdater(analyzerOptions, updater); + } + } + + private void applyOptionUpdater(Options analyzerOptions, AnalysisOptionsUpdater updater) { + + Set replaceableOptions = updater.getReplaceableOptions(); + for (ReplaceableOption ro : replaceableOptions) { + String newName = ro.getNewName(); + String oldName = ro.getOldName(); + if (!analyzerOptions.contains(oldName)) { + continue; // the old option was never saved or has been removed + } + + if (!analyzerOptions.contains(newName)) { + Msg.error(this, + "Found an option replacer without having the new option registered" + + "new option: '" + newName + "'; old option: '" + oldName + "'"); + continue; + } + + ro.replace(analyzerOptions); + analyzerOptions.removeOption(ro.getOldName()); + } + } + private void loadAnalyzerOptionsPanels() { List optionGroups = analysisOptions.getChildOptions(); noOptionsPanel = new JPanel(new VerticalLayout(5)); @@ -599,6 +665,7 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener { String analyzerName = optionsGroup.getName(); JPanel optionsContainer = new JPanel(new VerticalLayout(5)); + optionsContainer.setName(ANALYZER_OPTIONS_PANEL_NAME); optionsContainer.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5)); JScrollPane scrollPane = new JScrollPane(optionsContainer); @@ -645,11 +712,17 @@ class AnalysisPanel extends JPanel implements PropertyChangeListener { List subOptions = optionsGroup.getLeafOptionNames(); Iterator it = subOptions.iterator(); while (it.hasNext()) { - String next = it.next(); - if (!isEditable(optionsGroup, next)) { + String name = it.next(); + if (!isEditable(optionsGroup, name)) { + it.remove(); + } + + // also filter out unregistered options + if (!optionsGroup.isRegistered(name)) { it.remove(); } } + return subOptions; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalyzeAllOpenProgramsTask.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalyzeAllOpenProgramsTask.java index 4cc060f1c9..93cb4dbc1d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalyzeAllOpenProgramsTask.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AnalyzeAllOpenProgramsTask.java @@ -90,8 +90,7 @@ class AnalyzeAllOpenProgramsTask extends Task { return; // no need to log this - it's a valid condition } - AutoAnalysisManager mgr = AutoAnalysisManager.getAnalysisManager(prototypeProgram); - if (!setOptions(prototypeProgram, mgr)) { + if (!setOptions(prototypeProgram)) { return; } @@ -169,7 +168,7 @@ class AnalyzeAllOpenProgramsTask extends Task { return true; } - private boolean setOptions(final Program program, AutoAnalysisManager mgr) { + private boolean setOptions(final Program program) { AtomicBoolean analyze = new AtomicBoolean(); int id = program.startTransaction("analysis"); try { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisPlugin.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisPlugin.java index 802540285e..f7c17f9a1d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisPlugin.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AutoAnalysisPlugin.java @@ -63,21 +63,15 @@ import ghidra.util.task.TaskLauncher; public class AutoAnalysisPlugin extends Plugin implements AutoAnalysisManagerListener { private static final String SHOW_ANALYSIS_OPTIONS = "Show Analysis Options"; - private static final String ANALYZE_GROUP_NAME = "Analyze"; private DockingAction autoAnalyzeAction; - private DockingAction analyzeAllAction; private HelpLocation helpLocation; private List analyzers = new ArrayList<>(); private List oneShotActions = new ArrayList<>(); - /** - * Creates a new instance of the plugin giving it the tool that it will work - * in. - */ public AutoAnalysisPlugin(PluginTool tool) { super(tool); @@ -125,8 +119,7 @@ public class AutoAnalysisPlugin extends Plugin implements AutoAnalysisManagerLis .onAction(this::analyzeCallback) .buildAndInstall(tool); - analyzeAllAction = - new ActionBuilder("Analyze All Open", getName()) + new ActionBuilder("Analyze All Open", getName()) .supportsDefaultToolContext(true) .menuPath("&Analysis", "Analyze All &Open...") .menuGroup(ANALYZE_GROUP_NAME, "" + subGroupIndex++) @@ -206,31 +199,18 @@ public class AutoAnalysisPlugin extends Plugin implements AutoAnalysisManagerLis analysisMgr.reAnalyzeAll(selection); } - /** - * Get the description of this plugin. - */ public static String getDescription() { return "Provides coordination and a service for All Auto Analysis tasks"; } - /** - * Get the descriptive name. - */ public static String getDescriptiveName() { return "AutoAnalysisManager"; } - /** - * Get the category. - */ public static String getCategory() { return "Analysis"; } - /*************************************************************************** - * Implementation of AutoAnalysis Service - */ - protected void programClosed(Program program) { if (AutoAnalysisManager.hasAutoAnalysisManager(program)) { @@ -276,9 +256,10 @@ public class AutoAnalysisPlugin extends Plugin implements AutoAnalysisManagerLis private void programActivated(final Program program) { - program.getOptions(StoredAnalyzerTimes.OPTIONS_LIST).registerOption( - StoredAnalyzerTimes.OPTION_NAME, OptionType.CUSTOM_TYPE, null, null, - "Cumulative analysis task times", new StoredAnalyzerTimesPropertyEditor()); + program.getOptions(StoredAnalyzerTimes.OPTIONS_LIST) + .registerOption( + StoredAnalyzerTimes.OPTION_NAME, OptionType.CUSTOM_TYPE, null, null, + "Cumulative analysis task times", new StoredAnalyzerTimesPropertyEditor()); // invokeLater() to ensure that all other plugins have been notified of the program // activated. This makes sure plugins like the Listing have opened and painted the diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java index 6773377546..12ec378c13 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DWARFAnalyzer.java @@ -36,50 +36,79 @@ import ghidra.util.task.TaskMonitor; public class DWARFAnalyzer extends AbstractAnalyzer { private static final String DWARF_LOADED_OPTION_NAME = "DWARF Loaded"; - private static final String OPTION_IMPORT_DATATYPES = "Import data types"; + private static final String OPTION_IMPORT_DATATYPES = "Import Data Types"; private static final String OPTION_IMPORT_DATATYPES_DESC = "Import data types defined in the DWARF debug info."; - private static final String OPTION_PRELOAD_ALL_DIES = "Preload all DIEs"; + private static final String OPTION_PRELOAD_ALL_DIES = "Preload All DIEs"; private static final String OPTION_PRELOAD_ALL_DIES_DESC = - "Preload all DIE records. Requires more memory, but necessary for some non-standard layouts."; + "Preload all DIE records. Requires more memory, but necessary for some non-standard " + + "layouts."; - private static final String OPTION_IMPORT_FUNCS = "Import functions"; + private static final String OPTION_IMPORT_FUNCS = "Import Functions"; private static final String OPTION_IMPORT_FUNCS_DESC = - "Import function information defined in the DWARF debug info. (implies import data types)"; + "Import function information defined in the DWARF debug info\n" + + "(implies 'Import Data Types' is selected)."; - private static final String OPTION_IMPORT_LIMIT_DIE_COUNT = "Debug item count limit"; + private static final String OPTION_IMPORT_LIMIT_DIE_COUNT = "Debug Item Limit"; private static final String OPTION_IMPORT_LIMIT_DIE_COUNT_DESC = - "If the number of DWARF debug items are greater than this setting, DWARF analysis will be skipped."; + "If the number of DWARF debug items are greater than this setting, DWARF analysis will " + + "be skipped."; - private static final String OPTION_OUTPUT_SOURCE_INFO = "Output Source info"; + private static final String OPTION_OUTPUT_SOURCE_INFO = "Output Source Info"; private static final String OPTION_OUTPUT_SOURCE_INFO_DESC = - "Include source code location info (filename:linenumber) in comments attached to the Ghidra datatype or function or variable created."; + "Include source code location info (filename:linenumber) in comments attached to the " + + "Ghidra datatype or function or variable created."; - private static final String OPTION_OUTPUT_DWARF_DIE_INFO = "Output DWARF DIE info"; + private static final String OPTION_OUTPUT_DWARF_DIE_INFO = "Output DWARF DIE Info"; private static final String OPTION_OUTPUT_DWARF_DIE_INFO_DESC = - "Include DWARF DIE offset info in comments attached to the Ghidra datatype or function or variable created."; + "Include DWARF DIE offset info in comments attached to the Ghidra datatype or function " + + "or variable created."; - private static final String OPTION_NAME_LENGTH_CUTOFF = "Name length cutoff"; + private static final String OPTION_NAME_LENGTH_CUTOFF = "Maximum Name Length"; private static final String OPTION_NAME_LENGTH_CUTOFF_DESC = "Truncate symbol and type names longer than this limit. Range 20..2000"; - private static final String OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS = "Lexical block comments"; + private static final String OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS = "Add Lexical Block Comments"; private static final String OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS_DESC = "Add comments to the start of lexical blocks"; - private static final String OPTION_OUTPUT_INLINE_FUNC_COMMENTS = "Inlined functions comments"; + private static final String OPTION_OUTPUT_INLINE_FUNC_COMMENTS = + "Add Inlined Functions Comments"; private static final String OPTION_OUTPUT_INLINE_FUNC_COMMENTS_DESC = "Add comments to the start of inlined functions"; - private static final String OPTION_OUTPUT_FUNC_SIGS = "Output function signatures"; + private static final String OPTION_OUTPUT_FUNC_SIGS = "Create Function Signatures"; private static final String OPTION_OUTPUT_FUNC_SIGS_DESC = - "Create function signature data types for each function encountered in the DWARF debug data."; + "Create function signature data types for each function encountered in the DWARF debug " + + "data."; private static final String DWARF_ANALYZER_NAME = "DWARF"; private static final String DWARF_ANALYZER_DESCRIPTION = "Automatically extracts DWARF info from an ELF file."; +//================================================================================================== +// Old Option Names - Should stick around for multiple major versions after 10.2 +//================================================================================================== + + private static final String OPTION_IMPORT_DATATYPES_OLD = "Import data types"; + private static final String OPTION_PRELOAD_ALL_DIES_OLD = "Preload all DIEs"; + private static final String OPTION_IMPORT_FUNCS_OLD = "Import functions"; + private static final String OPTION_IMPORT_LIMIT_DIE_COUNT_OLD = "Debug item count limit"; + private static final String OPTION_OUTPUT_SOURCE_INFO_OLD = "Output Source info"; + private static final String OPTION_OUTPUT_DWARF_DIE_INFO_OLD = "Output DWARF DIE info"; + private static final String OPTION_NAME_LENGTH_CUTOFF_OLD = "Name length cutoff"; + private static final String OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS_OLD = "Lexical block comments"; + private static final String OPTION_OUTPUT_INLINE_FUNC_COMMENTS_OLD = + "Inlined functions comments"; + private static final String OPTION_OUTPUT_FUNC_SIGS_OLD = "Output function signatures"; + + private AnalysisOptionsUpdater optionsUpdater = new AnalysisOptionsUpdater(); + +//================================================================================================== +// End Old Option Names +//================================================================================================== + /** * Returns true if DWARF has already been imported into the specified program. * @@ -87,8 +116,8 @@ public class DWARFAnalyzer extends AbstractAnalyzer { * @return true if DWARF has already been imported, false if not yet */ public static boolean isAlreadyImported(Program program) { - Options propList = program.getOptions(Program.PROGRAM_INFO); - return propList.getBoolean(DWARF_LOADED_OPTION_NAME, false) || + Options options = program.getOptions(Program.PROGRAM_INFO); + return options.getBoolean(DWARF_LOADED_OPTION_NAME, false) || oldCheckIfDWARFImported(program); } @@ -100,6 +129,26 @@ public class DWARFAnalyzer extends AbstractAnalyzer { setDefaultEnablement(true); setPriority(AnalysisPriority.FORMAT_ANALYSIS.after()); setSupportsOneTimeAnalysis(); + + optionsUpdater.registerReplacement(OPTION_IMPORT_DATATYPES, + OPTION_IMPORT_DATATYPES_OLD); + optionsUpdater.registerReplacement(OPTION_PRELOAD_ALL_DIES, + OPTION_PRELOAD_ALL_DIES_OLD); + optionsUpdater.registerReplacement(OPTION_IMPORT_FUNCS, + OPTION_IMPORT_FUNCS_OLD); + optionsUpdater.registerReplacement(OPTION_IMPORT_LIMIT_DIE_COUNT, + OPTION_IMPORT_LIMIT_DIE_COUNT_OLD); + optionsUpdater.registerReplacement(OPTION_OUTPUT_SOURCE_INFO, + OPTION_OUTPUT_SOURCE_INFO_OLD); + optionsUpdater.registerReplacement(OPTION_OUTPUT_DWARF_DIE_INFO, + OPTION_OUTPUT_DWARF_DIE_INFO_OLD); + optionsUpdater.registerReplacement(OPTION_NAME_LENGTH_CUTOFF, + OPTION_NAME_LENGTH_CUTOFF_OLD); + optionsUpdater.registerReplacement(OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS, + OPTION_OUTPUT_LEXICAL_BLOCK_COMMENTS_OLD); + optionsUpdater.registerReplacement(OPTION_OUTPUT_INLINE_FUNC_COMMENTS, + OPTION_OUTPUT_INLINE_FUNC_COMMENTS_OLD); + optionsUpdater.registerReplacement(OPTION_OUTPUT_FUNC_SIGS, OPTION_OUTPUT_FUNC_SIGS_OLD); } @Override @@ -185,6 +234,7 @@ public class DWARFAnalyzer extends AbstractAnalyzer { @Override public void registerOptions(Options options, Program program) { + options.registerOption(OPTION_IMPORT_DATATYPES, importOptions.isImportDataTypes(), null, OPTION_IMPORT_DATATYPES_DESC); @@ -218,6 +268,11 @@ public class DWARFAnalyzer extends AbstractAnalyzer { null, OPTION_OUTPUT_FUNC_SIGS_DESC); } + @Override + public AnalysisOptionsUpdater getOptionsUpdater() { + return optionsUpdater; + } + @Override public void optionsChanged(Options options, Program program) { importOptions.setOutputDIEInfo( diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/Analyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/Analyzer.java index a47e5fbd83..cc5b35fd26 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/Analyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/Analyzer.java @@ -15,6 +15,7 @@ */ package ghidra.app.services; +import ghidra.app.plugin.core.analysis.AnalysisOptionsUpdater; import ghidra.app.util.importer.MessageLog; import ghidra.framework.options.Options; import ghidra.program.model.address.AddressSetView; @@ -24,12 +25,13 @@ import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; /** - * NOTE: ALL ANALYZER CLASSES MUST END IN "Analyzer". If not, - * the ClassSearcher will not find them. - * * Interface to perform automatic analysis. + * + * NOTE: ALL ANALYZER CLASSES MUST END IN "Analyzer". If not, the ClassSearcher will not find + * them. */ public interface Analyzer extends ExtensionPoint { + /** * Get the name of this analyzer * @return analyzer name @@ -43,15 +45,18 @@ public interface Analyzer extends ExtensionPoint { public AnalyzerType getAnalysisType(); /** - * Returns true if this analyzer should be enabled by default. Generally useful - * analyzers should return true. Specialized analyzers should return false; + * Returns true if this analyzer should be enabled by default. Generally useful analyzers + * should return true. Specialized analyzers should return false; + * @param program the program + * @return true if enabled by default */ public boolean getDefaultEnablement(Program program); /** * Returns true if it makes sense for this analyzer to directly invoked on an address or - * addressSet. The AutoAnalyzer plug-in will automatically create an action for each - * analyzer that returns true. + * addressSet. The AutoAnalyzer plug-in will automatically create an action for each analyzer + * that returns true. + * @return true if supports one-time analysis */ public boolean supportsOneTimeAnalysis(); @@ -75,29 +80,31 @@ public interface Analyzer extends ExtensionPoint { public boolean canAnalyze(Program program); /** - * Called when the requested information type has been added. - * (ie: function added.) + * Called when the requested information type has been added, for example, when a function is + * added. * * @param program program to analyze * @param set AddressSet of locations that have been added - * @param monitor monitor that indicates progress and indicates whether - * the user canceled the analysis + * @param monitor monitor that indicates progress and indicates whether the user canceled the + * analysis * @param log a message log to record analysis information * @return true if the analysis succeeded + * @throws CancelledException if the analysis is cancelled */ public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) throws CancelledException; /** - * Called when the requested information type has been removed. - * (ie: function removed.) + * Called when the requested information type has been removed, for example, when a function is + * removed. * * @param program program to analyze * @param set AddressSet of locations that have been added - * @param monitor monitor that indicates progress and indicates whether - * the user canceled the analysis + * @param monitor monitor that indicates progress and indicates whether the user canceled the + * analysis * @param log a message log to record analysis information * @return true if the analysis succeeded + * @throws CancelledException if the analysis is cancelled */ public boolean removed(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) throws CancelledException; @@ -111,8 +118,18 @@ public interface Analyzer extends ExtensionPoint { public void registerOptions(Options options, Program program); /** - * Analyzers should initialize their options from the values in the given Options, - * providing appropriate default values. + * Returns an optional options updater that allows clients to migrate old options to new + * options. This can be used to facilitate option name changes, as well as option value type + * changes. + * @return the updater; null if no updater + */ + public default AnalysisOptionsUpdater getOptionsUpdater() { + return null; // stub; clients will override as needed + } + + /** + * Analyzers should initialize their options from the values in the given Options, providing + * appropriate default values. * @param options the program options/property list that contains the options * @param program program to be analyzed */ diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalysisOptions2Test.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalysisOptions2Test.java new file mode 100644 index 0000000000..d4efc22138 --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalysisOptions2Test.java @@ -0,0 +1,520 @@ +/* ### + * 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.*; + +import java.awt.Color; +import java.awt.Component; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JPanel; +import javax.swing.table.TableModel; + +import org.junit.*; + +import docking.ActionContext; +import docking.action.DockingActionIf; +import docking.options.editor.DefaultOptionComponent; +import docking.widgets.table.GTable; +import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; +import ghidra.app.plugin.core.codebrowser.CodeViewerProvider; +import ghidra.app.services.*; +import ghidra.app.util.importer.MessageLog; +import ghidra.framework.options.*; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.database.ProgramBuilder; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.Program; +import ghidra.test.AbstractGhidraHeadedIntegrationTest; +import ghidra.test.TestEnv; +import ghidra.util.classfinder.ClassSearcher; +import ghidra.util.exception.AssertException; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * This class test the ability to replace analysis options using an {@link AnalysisOptionsUpdater}. + */ +public class AnalysisOptions2Test extends AbstractGhidraHeadedIntegrationTest { + + private static final String UNCHANGING_OPTION_NAME = "Unchanging Name"; + private static final String NEW_OPTION_NAME = "New Option"; + private static final String OLD_OPTION_NAME = "Old Option"; + private static final String NEW_OPTION_DEFAULT_VALUE = "New Default Value"; + private static final String OLD_OPTION_DEFAULT_VALUE = "Old Default Value"; + private static final String UNCHANGING_OPTION_DEFAULT_VALUE = "Unchanging Default Value"; + + private static final Color NEW_OPTION_DEFAULT_VALUE_AS_COLOR = Color.GREEN; + + 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); + } + + 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 { + close(optionsDialog); + env.dispose(); + } + + @Test + public void testOptionReplacing_DefaultNewValue_DefaultOldValue() throws Exception { + + // + // The old option's default value should not be applied to the new option + // + + installAnalyzer(NotReplacingTestAnalyzerStub.class); + + // install old options; the default value will not be used in the new option + installOldOptions(OLD_OPTION_DEFAULT_VALUE); + + openProgram(); + + optionsDialog = invokeAnalysisDialog(); + + // check options displayed in the dialog + assertOnlyNewOptionsInUi(); + assertOptionValue(NEW_OPTION_NAME, NEW_OPTION_DEFAULT_VALUE); + assertOptionValue(UNCHANGING_OPTION_NAME, UNCHANGING_OPTION_DEFAULT_VALUE); + assertOldValueRemoved(); + } + + @Test + public void testOptionReplacing_NonDefaultNewValue_DefaultOldValue() throws Exception { + + // + // The old option's default value should not be applied to the new option + // + + installAnalyzer(NotReplacingTestAnalyzerStub.class); + + // install old options; the default value will not be used in the new option + installOldOptions(OLD_OPTION_DEFAULT_VALUE); + + // put new options in the analyzer's options + String newValue = "Some New Value"; + changeNewOption(newValue); + + openProgram(); + + optionsDialog = invokeAnalysisDialog(); + + // check options displayed in the dialog + assertOnlyNewOptionsInUi(); + assertOptionValue(NEW_OPTION_NAME, newValue); + assertOptionValue(UNCHANGING_OPTION_NAME, UNCHANGING_OPTION_DEFAULT_VALUE); + assertOldValueRemoved(); + } + + @Test + public void testOptionReplacing_DefaultNewValue_NonDefaultOldValue() throws Exception { + + // + // The old option's non-default value should be applied to the new option + // + + installAnalyzer(UseOldValueTestAnalyzerStub.class); + + // install old options; the default value will not be used in the new option + installOldOptions(OLD_OPTION_DEFAULT_VALUE); + String newValue = "Old Option Non-default Value"; + changeOldOption(newValue); + + openProgram(); + + optionsDialog = invokeAnalysisDialog(); + + // check options displayed in the dialog + assertOnlyNewOptionsInUi(); + assertOptionValue(NEW_OPTION_NAME, newValue); + assertOptionValue(UNCHANGING_OPTION_NAME, UNCHANGING_OPTION_DEFAULT_VALUE); + assertOldValueRemoved(); + } + + @Test + public void testOptionReplacing_NonDefaultNewValue_NonDefaultOldValue() throws Exception { + + // + // The old option's non-default value should not be applied to the new option, since the + // new option has a non-default + // + + installAnalyzer(UseOldValueTestAnalyzerStub.class); + + // install old options; the default value will not be used in the new option + installOldOptions(OLD_OPTION_DEFAULT_VALUE); + String oldOptionNewValue = "Old Option Non-default Value"; + changeOldOption(oldOptionNewValue); + + String newOptionNewValue = "Some New Value"; + changeNewOption(newOptionNewValue); + + openProgram(); + + optionsDialog = invokeAnalysisDialog(); + + // check options displayed in the dialog + assertOnlyNewOptionsInUi(); + assertOptionValue(NEW_OPTION_NAME, newOptionNewValue); + assertOptionValue(UNCHANGING_OPTION_NAME, UNCHANGING_OPTION_DEFAULT_VALUE); + assertOldValueRemoved(); + } + + @Test + public void testOptionReplacing_DefaultNewValue_NonDefaultOldValue_DifferentValueTypes() + throws Exception { + + // + // The old option's non-default value should be applied to the new option. The new option + // has a different object type than the old option. + // + + installAnalyzer(ConvertValueTypeTestAnalyzerStub.class); + + // install old options; the default value will not be used in the new option + installOldOptions(OLD_OPTION_DEFAULT_VALUE); + String newValue = "255,175,175"; // pink + changeOldOption(newValue); + + openProgram(); + + optionsDialog = invokeAnalysisDialog(); + + // check options displayed in the dialog + assertOnlyNewOptionsInUi(); + assertOptionValue(NEW_OPTION_NAME, toColor(newValue)); + assertOptionValue(UNCHANGING_OPTION_NAME, UNCHANGING_OPTION_DEFAULT_VALUE); + } + +//================================================================================================== +// Private Methods +//================================================================================================== + + private void openProgram() { + ProgramManager pm = tool.getService(ProgramManager.class); + pm.openProgram(program.getDomainFile()); + } + + private void installOldOptions(Object value) { + Options programAnalysisOptions = program.getOptions(Program.ANALYSIS_PROPERTIES); + Options options = programAnalysisOptions.getOptions(AbstractTestAnalyzerStub.NAME); + AbstractOptions abstractOptions = (AbstractOptions) getInstanceField("options", options); + + // this call creates an 'unregistered option' + String fullOptionName = + "Analyzers." + AbstractTestAnalyzerStub.NAME + '.' + OLD_OPTION_NAME; + Option option = abstractOptions.getOption(fullOptionName, OptionType.getOptionType(value), + OLD_OPTION_DEFAULT_VALUE); + + // during testing 'value' may or may not match the default value + tx(program, () -> { + option.setCurrentValue(value); + setInstanceField("isRegistered", option, false); + }); + } + + private void installAnalyzer(Class analyzer) { + + @SuppressWarnings("unchecked") + List> extensions = + (List>) getInstanceField("extensionPoints", ClassSearcher.class); + + // remove any traces of previous test runs + extensions.removeIf(c -> c.getSimpleName().contains("TestAnalyzerStub")); + + extensions.add(analyzer); + } + + private AnalysisOptionsDialog invokeAnalysisDialog() { + + CodeBrowserPlugin cbp = env.getPlugin(CodeBrowserPlugin.class); + CodeViewerProvider provider = cbp.getProvider(); + DockingActionIf action = getAction(tool, "Auto Analyze"); + ActionContext context = runSwing(() -> provider.getActionContext(null)); + performAction(action, context, false); + + // TODO temp debug to catch issue seen when running parallel tests + try { + return waitForDialogComponent(AnalysisOptionsDialog.class); + } + catch (Throwable t) { + + printOpenWindows(); + + failWithException("Unable to find analysis dialog", t); + return null; // can't get here + } + } + + private void changeNewOption(String newValue) { + Options options = program.getOptions(Program.ANALYSIS_PROPERTIES); + Options analyzerOptions = options.getOptions(AbstractTestAnalyzerStub.NAME); + + tx(program, () -> analyzerOptions.putObject(NEW_OPTION_NAME, newValue)); + } + + private void changeOldOption(String newValue) { + Options programAnalysisOptions = program.getOptions(Program.ANALYSIS_PROPERTIES); + Options options = programAnalysisOptions.getOptions(AbstractTestAnalyzerStub.NAME); + AbstractOptions abstractOptions = (AbstractOptions) getInstanceField("options", options); + + // this call creates an 'unregistered option' + String fullOptionName = + "Analyzers." + AbstractTestAnalyzerStub.NAME + '.' + OLD_OPTION_NAME; + Option option = abstractOptions.getOption(fullOptionName, + OptionType.getOptionType(OLD_OPTION_DEFAULT_VALUE), OLD_OPTION_DEFAULT_VALUE); + + // during testing 'value' may or may not match the default value + tx(program, () -> { + option.setCurrentValue(newValue); + setInstanceField("isRegistered", option, false); + }); + } + + private void assertOldValueRemoved() { + Options options = program.getOptions(Program.ANALYSIS_PROPERTIES); + Options analyzerOptions = options.getOptions(AbstractTestAnalyzerStub.NAME); + assertFalse("Old option not removed", analyzerOptions.contains(OLD_OPTION_NAME)); + } + + private void assertOnlyNewOptionsInUi() { + + // click our analyzer in the list of options + selectAnalyzer(AbstractTestAnalyzerStub.NAME); + + // get the panel of options + JPanel panel = + (JPanel) findComponentByName(optionsDialog, AnalysisPanel.ANALYZER_OPTIONS_PANEL_NAME); + + // get the option labels + List uiOptionNames = new ArrayList<>(); + Component[] components = panel.getComponents(); + for (Component component : components) { + + DefaultOptionComponent doc = (DefaultOptionComponent) component; + String text = doc.getLabelText(); + uiOptionNames.add(text); + } + + // check against our new options + assertEquals(2, uiOptionNames.size()); + assertTrue(uiOptionNames.contains(UNCHANGING_OPTION_NAME)); + assertTrue(uiOptionNames.contains(NEW_OPTION_NAME)); + } + + private void assertOptionValue(String optionName, Object defaultValue) { + Options options = program.getOptions(Program.ANALYSIS_PROPERTIES); + Options analyzerOptions = options.getOptions(AbstractTestAnalyzerStub.NAME); + Object value = analyzerOptions.getObject(optionName, null); + assertEquals("Option value is not as expected for '" + optionName + "'", defaultValue, + value); + } + + private static Color toColor(String rgbString) { + String[] parts = rgbString.split(","); + int r = Integer.parseInt(parts[0]); + int g = Integer.parseInt(parts[1]); + int b = Integer.parseInt(parts[2]); + return new Color(r, g, b); + } + + private void selectAnalyzer(String name) { + GTable table = getAnalyzerTable(); + int analyzerRow = getRowForAnalyzer(name, table.getModel()); + runSwing(() -> table.selectRow(analyzerRow)); + } + + private GTable getAnalyzerTable() { + // The analysis dialog uses a table to display the enablement and name of each analyzer + AnalysisPanel panel = (AnalysisPanel) getInstanceField("panel", optionsDialog); + return (GTable) getInstanceField("table", 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; + } + +//================================================================================================== +// Inner Classes +//================================================================================================== + + public static abstract class AbstractTestAnalyzerStub implements Analyzer { + + protected AnalysisOptionsUpdater updater = new AnalysisOptionsUpdater(); + + private static final String NAME = "Test Analyzer"; + + @Override + public String getName() { + return NAME; + } + + @Override + public AnalyzerType getAnalysisType() { + return AnalyzerType.FUNCTION_ANALYZER; + } + + @Override + public boolean getDefaultEnablement(Program p) { + return true; + } + + @Override + public boolean supportsOneTimeAnalysis() { + return false; + } + + @Override + public String getDescription() { + return "Test analyzer..."; + } + + @Override + public AnalysisPriority getPriority() { + return AnalysisPriority.FUNCTION_ANALYSIS; + } + + @Override + public boolean canAnalyze(Program p) { + return true; + } + + @Override + public boolean added(Program p, AddressSetView set, TaskMonitor monitor, + MessageLog log) throws CancelledException { + return false; + } + + @Override + public boolean removed(Program p, AddressSetView set, TaskMonitor monitor, + MessageLog log) throws CancelledException { + return false; + } + + @Override + public void registerOptions(Options options, Program p) { + + options.registerOption(UNCHANGING_OPTION_NAME, UNCHANGING_OPTION_DEFAULT_VALUE, null, + "Unchanging option description"); + + // replaces "Old Name" + options.registerOption(NEW_OPTION_NAME, NEW_OPTION_DEFAULT_VALUE, null, + "New option description"); + } + + @Override + public AnalysisOptionsUpdater getOptionsUpdater() { + return updater; + } + + @Override + public void optionsChanged(Options options, Program p) { + // stub + } + + @Override + public void analysisEnded(Program p) { + // stub + } + + @Override + public boolean isPrototype() { + return false; + } + } + + public static class NotReplacingTestAnalyzerStub extends AbstractTestAnalyzerStub { + + public NotReplacingTestAnalyzerStub() { + super(); + + updater.registerReplacement(NEW_OPTION_NAME, OLD_OPTION_NAME, oldValue -> { + throw new AssertException( + "Replace function unexpectedly called for new/old options: '" + + NEW_OPTION_NAME + "' / '" + OLD_OPTION_NAME + "'"); + }); + } + } + + public static class UseOldValueTestAnalyzerStub extends AbstractTestAnalyzerStub { + + public UseOldValueTestAnalyzerStub() { + super(); + + updater.registerReplacement(NEW_OPTION_NAME, OLD_OPTION_NAME, oldValue -> { + return oldValue; + }); + } + } + + public static class ConvertValueTypeTestAnalyzerStub extends AbstractTestAnalyzerStub { + + public ConvertValueTypeTestAnalyzerStub() { + super(); + + updater.registerReplacement(NEW_OPTION_NAME, OLD_OPTION_NAME, oldValue -> { + // Assumption: 'oldValue' is an RGB string; the new value expects a Color + return toColor((String) oldValue); + }); + } + + @Override + public void registerOptions(Options options, Program p) { + + options.registerOption(UNCHANGING_OPTION_NAME, UNCHANGING_OPTION_DEFAULT_VALUE, null, + "Unchanging option description"); + + // replaces "Old Name" + options.registerOption(NEW_OPTION_NAME, NEW_OPTION_DEFAULT_VALUE_AS_COLOR, null, + "New option description"); + + } + } +} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalysisOptionsTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalysisOptionsTest.java index 40e8eaff8b..80574ad150 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalysisOptionsTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/analysis/AnalysisOptionsTest.java @@ -72,7 +72,6 @@ public class AnalysisOptionsTest extends AbstractGhidraHeadedIntegrationTest { @After public void tearDown() throws Exception { - env.release(program); env.dispose(); cleanUpStoredPreferences(); } @@ -258,6 +257,7 @@ public class AnalysisOptionsTest extends AbstractGhidraHeadedIntegrationTest { //================================================================================================== // Private Methods //================================================================================================== + private void createConfig(String name, boolean stackOn, boolean refOn, boolean stringOn) { setAnalyzerEnabled("Stack", stackOn); setAnalyzerEnabled("Reference", refOn); diff --git a/Ghidra/Features/FunctionID/src/main/java/ghidra/feature/fid/analyzer/FidAnalyzer.java b/Ghidra/Features/FunctionID/src/main/java/ghidra/feature/fid/analyzer/FidAnalyzer.java index aeabd0b6ea..79ec820a03 100644 --- a/Ghidra/Features/FunctionID/src/main/java/ghidra/feature/fid/analyzer/FidAnalyzer.java +++ b/Ghidra/Features/FunctionID/src/main/java/ghidra/feature/fid/analyzer/FidAnalyzer.java @@ -15,6 +15,7 @@ */ package ghidra.feature.fid.analyzer; +import ghidra.app.plugin.core.analysis.AnalysisOptionsUpdater; import ghidra.app.plugin.core.analysis.AutoAnalysisManager; import ghidra.app.services.*; import ghidra.app.util.importer.MessageLog; @@ -42,7 +43,8 @@ public class FidAnalyzer extends AbstractAnalyzer { private static final String OPTION_DESCRIPTION_CREATE_BOOKMARKS = "If checked, an analysis bookmark will be created for each function which was matched " + "against one or more known library functions."; - public static final String APPLY_ALL_FID_LABELS_OPTION_NAME = "Always apply FID labels"; + + public static final String APPLY_ALL_FID_LABELS_OPTION_NAME = "Always Apply FID Labels"; private static final String APPLY_ALL_FID_LABELS_OPTION_DESCRIPTION = "Enable this option to " + "always apply FID labels at functions regardless of existing labels at that function." + " When enabled, FID labels will always be added." + @@ -57,18 +59,33 @@ public class FidAnalyzer extends AbstractAnalyzer { private boolean alwaysApplyFidLabels = APPLY_ALL_FID_LABELS_DEFAULT; private boolean createBookmarksEnabled = OPTION_DEFAULT_CREATE_BOOKMARKS_ENABLED; - public static final String SCORE_THRESHOLD_OPTION_NAME = "Instruction count threshold"; + private static final String SCORE_THRESHOLD_OPTION_NAME = "Instruction Count Threshold"; + private static final String SCORE_THRESHOLD_OPTION_DESCRIPTION = "The minimum score that a potential match must meet to be labeled by the analyzer. " + "Score corresponds roughly to the number of instructions in the function."; private float scoreThreshold; - public static final String MULTIMATCH_THRESHOLD_OPTION_NAME = "Multiple match threshold"; + private static final String MULTIMATCH_THRESHOLD_OPTION_NAME = "Multiple Match Threshold"; private static final String MULTIMATCH_THRESHOLD_OPTION_DESCRIPTION = "If there are multiple conflicting matches for a function, its score must exceed " + "this secondary threshold in order to be labeled by the analyzer"; private float multiScoreThreshold; +//================================================================================================== +// Old Option Names - Should stick around for multiple major versions after 10.2 +//================================================================================================== + + private static final String SCORE_THRESHOLD_OPTION_NAME_OLD = "Instruction count threshold"; + private static final String MULTIMATCH_THRESHOLD_OPTION_NAME_OLD = "Multiple match threshold"; + private static final String APPLY_ALL_FID_LABELS_OPTION_NAME_OLD = "Always apply FID labels"; + + private AnalysisOptionsUpdater optionsUpdater = new AnalysisOptionsUpdater(); + +//================================================================================================== +// End Old Option Names +//================================================================================================== + public FidAnalyzer() { /* * FID is listed as a byte analyzer because we don't want to run it all the time. It @@ -86,6 +103,13 @@ public class FidAnalyzer extends AbstractAnalyzer { setPriority(AnalysisPriority.FUNCTION_ID_ANALYSIS.before()); scoreThreshold = service.getDefaultScoreThreshold(); multiScoreThreshold = service.getDefaultMultiNameThreshold(); + + optionsUpdater.registerReplacement(SCORE_THRESHOLD_OPTION_NAME, + SCORE_THRESHOLD_OPTION_NAME_OLD); + optionsUpdater.registerReplacement(MULTIMATCH_THRESHOLD_OPTION_NAME, + MULTIMATCH_THRESHOLD_OPTION_NAME_OLD); + optionsUpdater.registerReplacement(APPLY_ALL_FID_LABELS_OPTION_NAME, + APPLY_ALL_FID_LABELS_OPTION_NAME_OLD); } @Override @@ -112,8 +136,9 @@ public class FidAnalyzer extends AbstractAnalyzer { // Name Change can change the nature of a function from a system // library. Probably a better way to do this. - AutoAnalysisManager.getAnalysisManager(program).functionModifierChanged( - cmd.getFIDLocations()); + AutoAnalysisManager.getAnalysisManager(program) + .functionModifierChanged( + cmd.getFIDLocations()); return true; } @@ -129,6 +154,11 @@ public class FidAnalyzer extends AbstractAnalyzer { OPTION_DESCRIPTION_CREATE_BOOKMARKS); } + @Override + public AnalysisOptionsUpdater getOptionsUpdater() { + return optionsUpdater; + } + @Override public void optionsChanged(Options options, Program program) { scoreThreshold = diff --git a/Ghidra/Framework/Docking/src/main/java/docking/options/editor/DefaultOptionComponent.java b/Ghidra/Framework/Docking/src/main/java/docking/options/editor/DefaultOptionComponent.java index 9e821c42ba..d6bdcfed9d 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/options/editor/DefaultOptionComponent.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/options/editor/DefaultOptionComponent.java @@ -81,4 +81,8 @@ public class DefaultOptionComponent extends GenericOptionsComponent { int maxHeight = Math.max(dimension.height, component.getPreferredSize().height); return new Dimension(dimension.width, maxHeight); } + + public String getLabelText() { + return label.getText(); + } } diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/Options.java b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/Options.java index 9d10a471aa..256d38df63 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/Options.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/Options.java @@ -35,7 +35,7 @@ public interface Options { * * @return String */ - public abstract String getName(); + public String getName(); /** * Returns a unique id for option in this options with the given name. This will be the full @@ -89,13 +89,13 @@ public interface Options { * Set the location for where help can be found for this entire options object. * @param helpLocation location for help on the option */ - public abstract void setOptionsHelpLocation(HelpLocation helpLocation); + public void setOptionsHelpLocation(HelpLocation helpLocation); /** * Returns the HelpLocation for this entire Options object. * @return the HelpLocation for this entire Options object. */ - public abstract HelpLocation getOptionsHelpLocation(); + public HelpLocation getOptionsHelpLocation(); /** * Get the location for where help can be found for the option with @@ -103,7 +103,7 @@ public interface Options { * @param optionName name of the option * @return null if the help location was not set on the option */ - public abstract HelpLocation getHelpLocation(String optionName); + public HelpLocation getHelpLocation(String optionName); /** * Registers an option with a description, help location, and a default value without specifying @@ -117,7 +117,7 @@ public interface Options { * @param description a description of the option. * @throws IllegalArgumentException if the defaultValue is null */ - public abstract void registerOption(String optionName, Object defaultValue, HelpLocation help, + public void registerOption(String optionName, Object defaultValue, HelpLocation help, String description); /** @@ -130,7 +130,7 @@ public interface Options { * @param help the HelpLocation for this option. * @param description a description of the option. */ - public abstract void registerOption(String optionName, OptionType type, Object defaultValue, + public void registerOption(String optionName, OptionType type, Object defaultValue, HelpLocation help, String description); /** @@ -146,20 +146,20 @@ public interface Options { * then the property editor can't be null; * @throws IllegalStateException if the options is a custom option and the editor is null. */ - public abstract void registerOption(String optionName, OptionType type, Object defaultValue, + public void registerOption(String optionName, OptionType type, Object defaultValue, HelpLocation help, String description, PropertyEditor editor); /** * Register the options editor that will handle the editing for all the options or a sub group of options. * @param editor the custom editor panel to be used to edit the options or sub group of options. */ - public abstract void registerOptionsEditor(OptionsEditor editor); + public void registerOptionsEditor(OptionsEditor editor); /** * Get the editor that will handle editing all the values in this options or sub group of options. * @return null if no options editor was registered */ - public abstract OptionsEditor getOptionsEditor(); + public OptionsEditor getOptionsEditor(); /** * Put the object value. If the option exists, the type must match the type of the existing @@ -169,7 +169,7 @@ public interface Options { * @throws IllegalStateException if the object does not match the existing type of the option. * @throws IllegalArgumentException if the object is null or not a supported type. */ - public abstract void putObject(String optionName, Object obj); + public void putObject(String optionName, Object obj); /** * Get the object value; called when the options dialog is being @@ -179,7 +179,7 @@ public interface Options { * @return object with the given option name; if no option was found, * return default value (this value is not stored in the option maps) */ - public abstract Object getObject(String optionName, Object defaultValue); + public Object getObject(String optionName, Object defaultValue); /** * Get the boolean value for the given option name. @@ -188,7 +188,7 @@ public interface Options { * is no option with the given name. * @return boolean option value */ - public abstract boolean getBoolean(String optionName, boolean defaultValue); + public boolean getBoolean(String optionName, boolean defaultValue); /** * Get the byte array for the given option name. @@ -197,7 +197,7 @@ public interface Options { * is no option with the given name * @return byte[] byte array value */ - public abstract byte[] getByteArray(String optionName, byte[] defaultValue); + public byte[] getByteArray(String optionName, byte[] defaultValue); /** * Get the int value for the given option name. @@ -206,7 +206,7 @@ public interface Options { * is no option with the given name * @return int option value */ - public abstract int getInt(String optionName, int defaultValue); + public int getInt(String optionName, int defaultValue); /** * Get the double value for the given option name. @@ -215,7 +215,7 @@ public interface Options { * is no option with the given name * @return double value for the option */ - public abstract double getDouble(String optionName, double defaultValue); + public double getDouble(String optionName, double defaultValue); /** * Get the float value for the given option name. @@ -224,7 +224,7 @@ public interface Options { * is no option with the given name * @return float value for the option */ - public abstract float getFloat(String optionName, float defaultValue); + public float getFloat(String optionName, float defaultValue); /** * Get the long value for the given option name. @@ -233,7 +233,7 @@ public interface Options { * is no option with the given name * @return long value for the option */ - public abstract long getLong(String optionName, long defaultValue); + public long getLong(String optionName, long defaultValue); /** * Get the custom option value for the given option name. @@ -242,7 +242,7 @@ public interface Options { * is no option with the given name * @return WrappedOption value for the option */ - public abstract CustomOption getCustomOption(String optionName, CustomOption defaultValue); + public CustomOption getCustomOption(String optionName, CustomOption defaultValue); /** * Get the Color for the given option name. @@ -253,7 +253,7 @@ public interface Options { * @throws IllegalArgumentException is a option exists with the given * name but it is not a Color */ - public abstract Color getColor(String optionName, Color defaultValue); + public Color getColor(String optionName, Color defaultValue); /** * Get the File for the given option name. @@ -264,7 +264,7 @@ public interface Options { * @throws IllegalArgumentException is a option exists with the given * name but it is not a File options */ - public abstract File getFile(String optionName, File defaultValue); + public File getFile(String optionName, File defaultValue); /** * Get the Date for the given option name. @@ -275,7 +275,7 @@ public interface Options { * @throws IllegalArgumentException is a option exists with the given * name but it is not a Date options */ - public abstract Date getDate(String pName, Date date); + public Date getDate(String pName, Date date); /** * Get the Font for the given option name. @@ -286,7 +286,7 @@ public interface Options { * @throws IllegalArgumentException is a option exists with the given * name but it is not a Font */ - public abstract Font getFont(String optionName, Font defaultValue); + public Font getFont(String optionName, Font defaultValue); /** * Get the KeyStrokg for the given action name. @@ -297,7 +297,7 @@ public interface Options { * @throws IllegalArgumentException is a option exists with the given * name but it is not a KeyStroke */ - public abstract KeyStroke getKeyStroke(String optionName, KeyStroke defaultValue); + public KeyStroke getKeyStroke(String optionName, KeyStroke defaultValue); /** * Get the string value for the given option name. @@ -306,7 +306,7 @@ public interface Options { * option with the given name * @return String value for the option */ - public abstract String getString(String optionName, String defaultValue); + public String getString(String optionName, String defaultValue); /** * Get the Enum value for the given option name. @@ -315,70 +315,70 @@ public interface Options { * no option with the given name * @return Enum value for the option */ - public abstract > T getEnum(String optionName, T defaultValue); + public > T getEnum(String optionName, T defaultValue); /** * Sets the long value for the option. * @param optionName name of the option * @param value value of the option */ - public abstract void setLong(String optionName, long value); + public void setLong(String optionName, long value); /** * Sets the boolean value for the option. * @param optionName name of the option * @param value value of the option */ - public abstract void setBoolean(String optionName, boolean value); + public void setBoolean(String optionName, boolean value); /** * Sets the int value for the option. * @param optionName name of the option * @param value value of the option */ - public abstract void setInt(String optionName, int value); + public void setInt(String optionName, int value); /** * Sets the double value for the option. * @param optionName name of the option * @param value value of the option */ - public abstract void setDouble(String optionName, double value); + public void setDouble(String optionName, double value); /** * Sets the float value for the option. * @param optionName name of the option * @param value value of the option */ - public abstract void setFloat(String optionName, float value); + public void setFloat(String optionName, float value); /** * Sets the Custom option value for the option. * @param optionName name of the option * @param value the value */ - public abstract void setCustomOption(String optionName, CustomOption value); + public void setCustomOption(String optionName, CustomOption value); /** * Sets the byte[] value for the given option name. * @param optionName the name of the option on which to save bytes. * @param value the value */ - public abstract void setByteArray(String optionName, byte[] value); + public void setByteArray(String optionName, byte[] value); /** * Sets the File value for the option. * @param optionName name of the option * @param value the value */ - public abstract void setFile(String optionName, File value); + public void setFile(String optionName, File value); /** * Sets the Date value for the option. * @param optionName name of the option * @param newSetting the Date to set */ - public abstract void setDate(String optionName, Date newSetting); + public void setDate(String optionName, Date newSetting); /** * Sets the Color value for the option @@ -387,7 +387,7 @@ public interface Options { * @throws IllegalArgumentException if a option with the given * name already exists, but it is not a Color */ - public abstract void setColor(String optionName, Color value); + public void setColor(String optionName, Color value); /** * Sets the Font value for the option @@ -396,7 +396,7 @@ public interface Options { * @throws IllegalArgumentException if a option with the given * name already exists, but it is not a Font */ - public abstract void setFont(String optionName, Font value); + public void setFont(String optionName, Font value); /** * Sets the KeyStroke value for the option @@ -405,27 +405,27 @@ public interface Options { * @throws IllegalArgumentException if a option with the given * name already exists, but it is not a KeyStroke */ - public abstract void setKeyStroke(String optionName, KeyStroke value); + public void setKeyStroke(String optionName, KeyStroke value); /** * Set the String value for the option. * @param optionName name of the option * @param value value of the option */ - public abstract void setString(String optionName, String value); + public void setString(String optionName, String value); /** * Set the Enum value for the option. * @param optionName name of the option * @param value Enum value of the option */ - public abstract > void setEnum(String optionName, T value); + public > void setEnum(String optionName, T value); /** * Remove the option name. * @param optionName name of option to remove */ - public abstract void removeOption(String optionName); + public void removeOption(String optionName); /** * Get the list of option names. This method will return the names (paths) of all options contained @@ -434,21 +434,21 @@ public interface Options { * the "aaa" and "bbb" names. * @return the list of all option names(paths) under this options. */ - public abstract List getOptionNames(); + public List getOptionNames(); /** * Return true if a option exists with the given name. * @param optionName option name * @return true if there exists an option with the given name */ - public abstract boolean contains(String optionName); + public boolean contains(String optionName); /** * Get the description for the given option name. * @param optionName name of the option * @return null if the description or option name does not exist */ - public abstract String getDescription(String optionName); + public String getDescription(String optionName); /** * Returns true if the specified option has been registered. Only registered names @@ -456,21 +456,21 @@ public interface Options { * @param optionName the option name * @return true if registered */ - public abstract boolean isRegistered(String optionName); + public boolean isRegistered(String optionName); /** * Returns true if the option with the given name's current value is the default value. * @param optionName the name of the option. * @return true if the options has its current value equal to its default value. */ - public abstract boolean isDefaultValue(String optionName); + public boolean isDefaultValue(String optionName); /** * Restores all options contained herein to their default values. * * @see #restoreDefaultValue(String) */ - public abstract void restoreDefaultValues(); + public void restoreDefaultValues(); /** * Restores the option denoted by the given name to its default value. @@ -478,7 +478,7 @@ public interface Options { * @param optionName The name of the option to restore * @see #restoreDefaultValues() */ - public abstract void restoreDefaultValue(String optionName); + public void restoreDefaultValue(String optionName); /** * Returns a Options object that is a sub-options of this options. @@ -520,14 +520,14 @@ public interface Options { * @param name the name of the option for which to retrieve the value as a string * @return the value as a string for the given option. */ - public abstract String getValueAsString(String name); + public String getValueAsString(String name); /** * Returns the default value as a string for the given option. * @param optionName the name of the option for which to retrieve the default value as a string * @return the default value as a string for the given option. */ - public abstract String getDefaultValueAsString(String optionName); + public String getDefaultValueAsString(String optionName); /** * Returns true if the two options objects have the same set of options and values diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/util/constraint/PropertyConstraint.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/util/constraint/PropertyConstraint.java index a1647431fb..6cbf28229c 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/util/constraint/PropertyConstraint.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/util/constraint/PropertyConstraint.java @@ -1,6 +1,5 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +15,8 @@ */ package ghidra.util.constraint; +import java.util.Objects; + import generic.constraint.ConstraintData; import ghidra.program.model.listing.Program; import ghidra.util.SystemUtilities; @@ -26,8 +27,8 @@ public class PropertyConstraint extends ProgramConstraint { super("property"); } - private String name; // name of the program property to constrain - private String value; // value the property should take + private String name; // name of the program property to constrain + private String value; // value the property should take @Override public boolean isSatisfied(Program program) { @@ -41,6 +42,11 @@ public class PropertyConstraint extends ProgramConstraint { value = data.getString("value"); } + @Override + public int hashCode() { + return Objects.hash(name, value); + } + @Override public boolean equals(Object obj) { if (!(obj instanceof PropertyConstraint)) { @@ -54,5 +60,4 @@ public class PropertyConstraint extends ProgramConstraint { public String getDescription() { return "property " + name + " = " + value; } - }