From ec88c732d260593e73eda089b72c7a1db2cea5c6 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Wed, 20 Oct 2021 16:54:04 -0400 Subject: [PATCH] GP-1382 - Added Find actions to the Data Types provider to allow users to find structures by size or by offset(s). --- Ghidra/Features/Base/certification.manifest | 1 + .../data_type_manager_window.html | 40 ++- .../images/FindDataTypesBySize.png | Bin 0 -> 6129 bytes .../core/datamgr/DataTypesProvider.java | 25 +- ...on.java => FindDataTypesByNameAction.java} | 37 ++- .../actions/FindDataTypesBySizeAction.java | 56 ++-- .../FindDataTypesContainingAction.java | 251 ---------------- .../actions/FindStructuresByOffsetAction.java | 159 ++++++++++ .../actions/FindStructuresBySizeAction.java | 82 ++++++ .../IncludeDataTypesInFilterAction.java | 13 +- .../dialogs/NumberRangeInputDialogTest.java | 178 +++++++++++ .../datamgr/DataTypeManagerPluginTest.java | 278 +++++++++++++++--- .../docking/test/AbstractDockingTest.java | 39 +++ .../dialogs/NumberRangeInputDialog.java | 252 ++++++++++++++++ .../util/classfinder/ClassSearchTask.java | 4 +- .../util/datastruct/SortedRangeList.java | 11 +- .../model/data/BuiltInDataTypeManager.java | 19 +- .../java/ghidra/util/datastruct/Range.java | 29 +- .../DataTypeManagerPluginScreenShots.java | 9 + 19 files changed, 1104 insertions(+), 379 deletions(-) create mode 100644 Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/images/FindDataTypesBySize.png rename Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/{FindDataTypesAction.java => FindDataTypesByNameAction.java} (65%) delete mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesContainingAction.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindStructuresByOffsetAction.java create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindStructuresBySizeAction.java create mode 100644 Ghidra/Features/Base/src/test.slow/java/docking/widgets/dialogs/NumberRangeInputDialogTest.java create mode 100644 Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/NumberRangeInputDialog.java diff --git a/Ghidra/Features/Base/certification.manifest b/Ghidra/Features/Base/certification.manifest index ce9bd6030a..2b20e90f04 100644 --- a/Ghidra/Features/Base/certification.manifest +++ b/Ghidra/Features/Base/certification.manifest @@ -228,6 +228,7 @@ src/main/help/help/topics/DataTypeManagerPlugin/images/DisassociateDialog.png||G src/main/help/help/topics/DataTypeManagerPlugin/images/EditPaths.png||GHIDRA||||END| src/main/help/help/topics/DataTypeManagerPlugin/images/FavoriteDts.png||GHIDRA||||END| src/main/help/help/topics/DataTypeManagerPlugin/images/FindDataTypes.png||GHIDRA||||END| +src/main/help/help/topics/DataTypeManagerPlugin/images/FindDataTypesBySize.png||GHIDRA||||END| src/main/help/help/topics/DataTypeManagerPlugin/images/PreviewWindow.png||GHIDRA||||END| src/main/help/help/topics/DataTypeManagerPlugin/images/RevertDialog.png||GHIDRA||||END| src/main/help/help/topics/DataTypeManagerPlugin/images/SearchResults.png||GHIDRA||||END| diff --git a/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_window.html b/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_window.html index b8a820e45e..e5a8364198 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_window.html +++ b/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/data_type_manager_window.html @@ -650,7 +650,7 @@ types to another program.

-

Find Data Types by +

Find Data Types by Name

@@ -667,8 +667,8 @@


-

Enter a value in the "Please enter the search string: " field, or choose a - previously entered value from the combo box. Either press the <Enter> key or +

Enter a value in the "Please enter the search string: " field. + Either press the <Enter> key or select the OK button. A new Find Data Type window will appear with the filter set to the entered search text.

@@ -681,9 +681,37 @@

Find Data Types by Size

-

This is similar to the Find Data Types by Name action (show above) except - that it lets you specify a size that will be used to show only those data types with - the same length.

+ +


+

+ + +

This is similar to the + Find Data Types by Name action (show above) except that it lets you specify + one or more sizes that will be used to show only those data types with the same length. +

+

+ This dialog accepts a single integer value or multiple values separated by a ','. + You may also enter ranges in this dialog using ':' as the range separator. Values + with a leading '0x' will be treated as hex. +

+
+ +

Find Structures by Size

+ +
+ +

This is similar to the Find Data Types by Size action (show above) except + that it filters results to show only Structures.

+
+ +

Find Structures by Offset

+ +
+ +

This is similar to the Find Structures by Size action (show above) except + that it lets you specify one or more offset values that will be used to show only those + structures that contain any of the specified offsets.

Export Data Type(s)

diff --git a/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/images/FindDataTypesBySize.png b/Ghidra/Features/Base/src/main/help/help/topics/DataTypeManagerPlugin/images/FindDataTypesBySize.png new file mode 100644 index 0000000000000000000000000000000000000000..13e21e5ed8958b9ec41b2660a6e365966b4a8133 GIT binary patch literal 6129 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 literal 0 HcmV?d00001 diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java index 8d4fb70d66..515dad44db 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/DataTypesProvider.java @@ -177,9 +177,14 @@ public class DataTypesProvider extends ComponentProviderAdapter { addLocalAction(new ExpandAllAction(plugin)); // Tree // VeryLast group - addLocalAction(getIncludeDataMembersInSearchAction()); // Common - addLocalAction(new FindDataTypesAction(plugin)); // Common - addLocalAction(new FindDataTypesBySizeAction(plugin)); // Common + addLocalAction(new FindDataTypesByNameAction(plugin, "1")); + addLocalAction(new FindDataTypesBySizeAction(plugin, "2")); + addLocalAction(new FindStructuresByOffsetAction(plugin, "3")); + addLocalAction(new FindStructuresBySizeAction(plugin, "4")); + includeDataMembersInSearchAction = + new IncludeDataTypesInFilterAction(plugin, this, "5"); + addLocalAction(includeDataMembersInSearchAction); + addLocalAction(new ApplyFunctionDataTypesAction(plugin)); // Tree addLocalAction(new CaptureFunctionDataTypesAction(plugin)); // Tree addLocalAction(new SetFavoriteDataTypeAction(plugin)); // Data Type @@ -189,7 +194,6 @@ public class DataTypesProvider extends ComponentProviderAdapter { // ZVeryLast group addLocalAction(new FindReferencesToDataTypeAction(plugin)); // DataType addLocalAction(new FindReferencesToFieldAction(plugin)); // DataType -// addLocalAction( new FindDataTypesContainingAction(plugin) ); // DataType addLocalAction(new FindBaseDataTypeAction(plugin)); // DataType addLocalAction(new DisplayTypeAsGraphAction(plugin)); @@ -322,13 +326,6 @@ public class DataTypesProvider extends ComponentProviderAdapter { return previewWindowAction; } - private ToggleDockingAction getIncludeDataMembersInSearchAction() { - if (includeDataMembersInSearchAction == null) { - includeDataMembersInSearchAction = new IncludeDataTypesInFilterAction(plugin, this); - } - return includeDataMembersInSearchAction; - } - @Override public ActionContext getActionContext(MouseEvent event) { GTreeNode clickedNode = null; @@ -641,7 +638,7 @@ public class DataTypesProvider extends ComponentProviderAdapter { getPreviewWindowAction().setSelected(previewWindowVisible); boolean dataMembersInSearch = saveState.getBoolean(INCLUDE_DATA_MEMBERS_IN_SEARCH, false); - getIncludeDataMembersInSearchAction().setSelected(dataMembersInSearch); + includeDataMembersInSearchAction.setSelected(dataMembersInSearch); } void save(SaveState saveState) { @@ -651,7 +648,7 @@ public class DataTypesProvider extends ComponentProviderAdapter { getConflictHandlerModesAction().getCurrentUserData().toString()); saveState.putBoolean(PREVIEW_WINDOW_STATE, getPreviewWindowAction().isSelected()); saveState.putBoolean(INCLUDE_DATA_MEMBERS_IN_SEARCH, - getIncludeDataMembersInSearchAction().isSelected()); + includeDataMembersInSearchAction.isSelected()); } public DataTypeArchiveGTree getGTree() { @@ -788,7 +785,7 @@ public class DataTypesProvider extends ComponentProviderAdapter { archiveGTree.setIncludeDataTypeMembersInSearch(includeDataMembersInFilter); // make sure the action is in sync - ToggleDockingAction action = getIncludeDataMembersInSearchAction(); + ToggleDockingAction action = includeDataMembersInSearchAction; boolean selected = action.isSelected(); if (selected != includeDataMembersInFilter) { action.setSelected(includeDataMembersInFilter); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesByNameAction.java similarity index 65% rename from Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesAction.java rename to Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesByNameAction.java index 4223b794e5..3723df0b56 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesByNameAction.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,30 +15,32 @@ */ package ghidra.app.plugin.core.datamgr.actions; -import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; -import ghidra.app.plugin.core.datamgr.DataTypesProvider; -import ghidra.framework.plugintool.PluginTool; - -import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import docking.ActionContext; +import docking.DockingUtils; import docking.action.*; import docking.widgets.dialogs.InputDialog; +import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; +import ghidra.app.plugin.core.datamgr.DataTypesProvider; +import ghidra.framework.plugintool.PluginTool; +import ghidra.util.HelpLocation; -public class FindDataTypesAction extends DockingAction { +public class FindDataTypesByNameAction extends DockingAction { + + public static final String NAME = "Find Data Types by Name"; private final DataTypeManagerPlugin plugin; - public FindDataTypesAction(DataTypeManagerPlugin plugin) { - super("Find Data Types", plugin.getName()); + public FindDataTypesByNameAction(DataTypeManagerPlugin plugin, String menuSubGroup) { + super("Find Data Types by Name", plugin.getName()); this.plugin = plugin; - setMenuBarData(new MenuData(new String[] { "Find Data Types by Name..." }, null, - "VeryLast", -1, "1")); - setKeyBindingData(new KeyBindingData(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK)); - - setEnabled(true); + setMenuBarData(new MenuData(new String[] { NAME + "..." }, null, + "VeryLast", -1, menuSubGroup)); + setKeyBindingData( + new KeyBindingData(KeyEvent.VK_F, DockingUtils.CONTROL_KEY_MODIFIER_MASK)); + setHelpLocation(new HelpLocation("DataTypeManagerPlugin", "Find_Data_Types_By_Name")); } @Override @@ -49,16 +50,14 @@ public class FindDataTypesAction extends DockingAction { new InputDialog("Find Data Types", "Please enter the search string: "); PluginTool tool = plugin.getTool(); tool.showDialog(inputDialog); - if (inputDialog.isCanceled()) { return; } - final String searchString = inputDialog.getValue(); - String title = "Find Data Type"; - final DataTypesProvider newProvider = plugin.createProvider(); + String searchString = inputDialog.getValue(); + DataTypesProvider newProvider = plugin.createProvider(); newProvider.setIncludeDataTypeMembersInFilter(plugin.includeDataMembersInSearch()); - newProvider.setTitle(title); + newProvider.setTitle(NAME); newProvider.setFilterText(searchString); newProvider.setVisible(true); } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesBySizeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesBySizeAction.java index f28b89e472..7291896355 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesBySizeAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesBySizeAction.java @@ -18,7 +18,7 @@ package ghidra.app.plugin.core.datamgr.actions; import docking.ActionContext; import docking.action.DockingAction; import docking.action.MenuData; -import docking.widgets.dialogs.NumberInputDialog; +import docking.widgets.dialogs.NumberRangeInputDialog; import docking.widgets.tree.*; import docking.widgets.tree.support.CombinedGTreeFilter; import docking.widgets.tree.support.GTreeFilter; @@ -28,40 +28,50 @@ import ghidra.app.plugin.core.datamgr.tree.DataTypeArchiveGTree; import ghidra.app.plugin.core.datamgr.tree.DataTypeNode; import ghidra.program.model.data.DataType; import ghidra.util.HelpLocation; +import ghidra.util.datastruct.Range; +import ghidra.util.datastruct.SortedRangeList; public class FindDataTypesBySizeAction extends DockingAction { + public static final String NAME = "Find Data Types by Size"; + private DataTypeManagerPlugin plugin; - public FindDataTypesBySizeAction(DataTypeManagerPlugin plugin) { - super("Find Data Types By Size", plugin.getName()); + public FindDataTypesBySizeAction(DataTypeManagerPlugin plugin, String menuSubGroup) { + this(plugin, NAME, menuSubGroup); + } + + FindDataTypesBySizeAction(DataTypeManagerPlugin plugin, String name, String menuSubGroup) { + super(name, plugin.getName()); this.plugin = plugin; setMenuBarData( - new MenuData(new String[] { "Find Data Types by Size..." }, null, "VeryLast", -1, "2")); - - setEnabled(true); + new MenuData(new String[] { name + "..." }, null, "VeryLast", -1, menuSubGroup)); setHelpLocation(new HelpLocation("DataTypeManagerPlugin", "Find_Data_Types_By_Size")); } @Override public void actionPerformed(ActionContext context) { - NumberInputDialog numberInputDialog = new NumberInputDialog("bytes", 1, 1); - if (!numberInputDialog.show()) { + NumberRangeInputDialog inputDialog = + new NumberRangeInputDialog(getName(), "Size(s)"); + if (!inputDialog.show()) { return; } - int value = numberInputDialog.getValue(); - - String title = "Find Data Types With Size"; - final DataTypesProvider newProvider = plugin.createProvider(); - newProvider.setTitle(title); + SortedRangeList values = inputDialog.getValue(); + DataTypesProvider newProvider = plugin.createProvider(); + newProvider.setTitle(getName()); DataTypeArchiveGTree tree = newProvider.getGTree(); - tree.setFilterProvider(new MyTreeFilterProvider(tree, new SizeGTreeFilter(value))); + GTreeFilter filter = createFilter(values); + tree.setFilterProvider(new MyTreeFilterProvider(tree, filter)); newProvider.setVisible(true); } + protected GTreeFilter createFilter(SortedRangeList values) { + return new SizeGTreeFilter(values); + } + private class MyTreeFilterProvider extends DefaultGTreeFilterProvider { private GTreeFilter secondaryFilter; @@ -82,10 +92,10 @@ public class FindDataTypesBySizeAction extends DockingAction { private class SizeGTreeFilter implements GTreeFilter { - private final int size; + private final SortedRangeList sizes; - SizeGTreeFilter(int size) { - this.size = size; + SizeGTreeFilter(SortedRangeList sizes) { + this.sizes = sizes; } @Override @@ -99,9 +109,15 @@ public class FindDataTypesBySizeAction extends DockingAction { return false; } DataTypeNode dataTypeNode = (DataTypeNode) node; - DataType dataType = dataTypeNode.getDataType(); - int length = dataType.getLength(); - return length == size; + DataType dt = dataTypeNode.getDataType(); + int length = dt.getLength(); + for (Range range : sizes) { + if (range.contains(length)) { + return true; + } + } + + return false; } } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesContainingAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesContainingAction.java deleted file mode 100644 index ce5a8bf90d..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindDataTypesContainingAction.java +++ /dev/null @@ -1,251 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.app.plugin.core.datamgr.actions; - -import java.util.*; - -import javax.swing.tree.TreePath; - -import docking.ActionContext; -import docking.action.DockingAction; -import docking.action.MenuData; -import docking.widgets.tree.GTree; -import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; -import ghidra.app.plugin.core.datamgr.DataTypesActionContext; -import ghidra.app.plugin.core.datamgr.tree.DataTypeNode; -import ghidra.program.database.DatabaseObject; -import ghidra.program.model.data.*; -import ghidra.program.model.data.Enum; -import ghidra.util.*; -import ghidra.util.exception.AssertException; - -public class FindDataTypesContainingAction extends DockingAction { -// private final DataTypeManagerPlugin plugin; - - public FindDataTypesContainingAction(DataTypeManagerPlugin plugin) { - super("Find Data Types Containing", plugin.getName()); -// this.plugin = plugin; - - setPopupMenuData( - new MenuData(new String[] { "Find Data Types Containing..." }, null, "ZVeryLast")); - - setHelpLocation(new HelpLocation("DataTypeManagerPlugin", "Data_Type_Manager_Plugin")); - setEnabled(true); - } - - @Override - public boolean isEnabledForContext(ActionContext context) { - return (context instanceof DataTypesActionContext); - } - - @Override - public void actionPerformed(ActionContext context) { - if (!(context instanceof DataTypesActionContext)) { - return; - } - Object contextObject = context.getContextObject(); - GTree gTree = (GTree) contextObject; - TreePath[] selectionPaths = gTree.getSelectionPaths(); - StringBuffer text = new StringBuffer(); - - if (selectionPaths.length != 1) { - Msg.showInfo(getClass(), gTree, "Find Data Types Containing", - "You must select a single data type in the tree."); - return; - } - TreePath path = gTree.getSelectionPath(); - Object node = path.getLastPathComponent(); - if (!(node instanceof DataTypeNode)) { - Msg.showInfo(getClass(), gTree, "Find Data Types Containing", - "You must select a single data type in the tree."); - return; - } - DataTypeNode dataTypeNode = (DataTypeNode) node; - DataType dataType = dataTypeNode.getDataType(); - Iterator parentDtIterator = getDataTypesContaining(dataType, gTree); - if (!parentDtIterator.hasNext()) { - DataTypeManager dataTypeManager = dataType.getDataTypeManager(); - ArchiveType type = dataTypeManager.getType(); - String typeName = " archive"; - if (type.equals(ArchiveType.PROGRAM)) { - typeName = " program"; - } - else if (type.equals(ArchiveType.BUILT_IN)) { - typeName = ""; - } - Msg.showInfo(getClass(), gTree, - "Find Data Types Containing \"" + dataType.getPathName() + "\"", - "" + HTMLUtilities.friendlyEncodeHTML(dataType.getPathName()) + - " isn't contained in any other data types in the " + - HTMLUtilities.friendlyEncodeHTML(dataTypeManager.getName()) + "" + - typeName + "!!!"); - return; - } - - text.append(""); - text.append("" + HTMLUtilities.friendlyEncodeHTML(dataType.getPathName()) + - " is contained in:
"); - int count = 0; - while (parentDtIterator.hasNext()) { - DataType parentDt = parentDtIterator.next(); - count++; - if (count >= 50) { - text.append("

Too many to display..."); - break; - } - text.append( - "
" + HTMLUtilities.friendlyEncodeHTML(parentDt.getPathName())); - } - text.append(""); - - Msg.showInfo(getClass(), gTree, - "Find Data Types Containing \"" + dataType.getPathName() + "\"", text.toString()); - } - - protected Iterator getDataTypesContaining(DataType dataType, GTree gTree) { - DataTypeManager dataTypeManager = dataType.getDataTypeManager(); - if (dataTypeManager == null) { - Msg.showInfo(getClass(), gTree, - "Find Data Types Containing \"" + dataType.getName() + "\"", - "\"" + dataType.getName() + "\" doesn't have a data type manager."); - return new DataTypeArrayIterator(new DataType[0]); - } - if (dataType instanceof DatabaseObject) { - return new DataTypeArrayIterator(dataType.getParents()); - } - return new DataTypeContainingIterator(dataType); - } - - private class DataTypeArrayIterator implements Iterator { - DataType[] dataTypes; - int nextIndex = 0; - - private DataTypeArrayIterator(DataType[] dataTypes) { - this.dataTypes = dataTypes; - } - - @Override - public boolean hasNext() { - return nextIndex < dataTypes.length; - } - - @Override - public DataType next() { - return dataTypes[nextIndex++]; - } - - @Override - public void remove() { - } - } - - private class DataTypeContainingIterator implements Iterator { - DataType dataType; - Iterator iterator; - DataType nextDataType = null; - - private DataTypeContainingIterator(DataType dataType) { - this.dataType = dataType; - DataTypeManager dataTypeManager = dataType.getDataTypeManager(); - if (dataTypeManager == null) { - iterator = new DataTypeArrayIterator(new DataType[0]); - } - else { - iterator = dataTypeManager.getAllDataTypes(); - } - } - - @Override - public boolean hasNext() { - if (nextDataType != null) { - return true; - } - while (iterator.hasNext()) { - DataType dt = iterator.next(); - Collection containedDataTypes = getDirectContainedDatatypes(dt); - for (DataType containedDt : containedDataTypes) { - if (containedDt == dataType) { - nextDataType = dt; - return true; - } - } - } - return false; - } - - @Override - public DataType next() { - if (nextDataType == null) { - hasNext(); - } - DataType tempDataType = nextDataType; - nextDataType = null; - return tempDataType; - } - - @Override - public void remove() { - } - } - - private static List getDirectContainedDatatypes(DataType dt) { - List list = new ArrayList(); - if (dt instanceof Array) { - Array array = (Array) dt; - list.add(array.getDataType()); - } - else if (dt instanceof Pointer) { - Pointer ptr = (Pointer) dt; - DataType ptrDt = ptr.getDataType(); - if (ptrDt != null) { - list.add(ptrDt); - } - } - else if (dt instanceof Composite) { - Composite composite = (Composite) dt; - int n = composite.getNumComponents(); - for (int i = 0; i < n; i++) { - DataTypeComponent component = composite.getComponent(i); - list.add(component.getDataType()); - } - } - else if (dt instanceof TypeDef) { - TypeDef typedef = (TypeDef) dt; - list.add(typedef.getDataType()); - } - else if (dt instanceof Enum) { - } - else if (dt instanceof FunctionDefinition) { - FunctionDefinition funDef = (FunctionDefinition) dt; - list.add(funDef.getReturnType()); - ParameterDefinition[] arguments = funDef.getArguments(); - for (ParameterDefinition parameter : arguments) { - list.add(parameter.getDataType()); - } - } - else if (dt instanceof BuiltInDataType) { - } - else if (dt instanceof MissingBuiltInDataType) { - } - else if (dt.equals(DataType.DEFAULT)) { - - } - else { - throw new AssertException("Unknown data Type:" + dt.getDisplayName()); - } - return list; - } -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindStructuresByOffsetAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindStructuresByOffsetAction.java new file mode 100644 index 0000000000..4501ded966 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindStructuresByOffsetAction.java @@ -0,0 +1,159 @@ +/* ### + * 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.datamgr.actions; + +import java.util.Iterator; + +import docking.ActionContext; +import docking.action.DockingAction; +import docking.action.MenuData; +import docking.widgets.dialogs.NumberRangeInputDialog; +import docking.widgets.tree.*; +import docking.widgets.tree.support.CombinedGTreeFilter; +import docking.widgets.tree.support.GTreeFilter; +import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; +import ghidra.app.plugin.core.datamgr.DataTypesProvider; +import ghidra.app.plugin.core.datamgr.tree.DataTypeArchiveGTree; +import ghidra.app.plugin.core.datamgr.tree.DataTypeNode; +import ghidra.program.model.data.*; +import ghidra.util.HelpLocation; +import ghidra.util.datastruct.Range; +import ghidra.util.datastruct.SortedRangeList; +import util.CollectionUtils; + +/** + * Allows the user to supply one or more offsets that are used to search for structures that have + * any of those offsets. + */ +public class FindStructuresByOffsetAction extends DockingAction { + + public static final String NAME = "Find Structures by Offset"; + + private DataTypeManagerPlugin plugin; + + public FindStructuresByOffsetAction(DataTypeManagerPlugin plugin, String menuSubGroup) { + super(NAME, plugin.getName()); + this.plugin = plugin; + + setMenuBarData( + new MenuData(new String[] { NAME + "..." }, null, "VeryLast", -1, menuSubGroup)); + setHelpLocation(new HelpLocation("DataTypeManagerPlugin", "Find_Data_Types_By_Offset")); + } + + @Override + public void actionPerformed(ActionContext context) { + + NumberRangeInputDialog inputDialog = + new NumberRangeInputDialog(NAME, "Offset(s)"); + if (!inputDialog.show()) { + return; + } + + SortedRangeList values = inputDialog.getValue(); + DataTypesProvider newProvider = plugin.createProvider(); + newProvider.setTitle(NAME); + DataTypeArchiveGTree tree = newProvider.getGTree(); + tree.setFilterProvider(new MyTreeFilterProvider(tree, new OffsetGTreeFilter(values))); + newProvider.setVisible(true); + } + + private class MyTreeFilterProvider extends DefaultGTreeFilterProvider { + private GTreeFilter secondaryFilter; + + MyTreeFilterProvider(GTree tree, GTreeFilter secondaryFilter) { + super(tree); + this.secondaryFilter = secondaryFilter; + } + + @Override + public GTreeFilter getFilter() { + GTreeFilter filter = super.getFilter(); + if (filter == null) { + return secondaryFilter; + } + return new CombinedGTreeFilter(filter, secondaryFilter); + } + } + + private class OffsetGTreeFilter implements GTreeFilter { + + private final SortedRangeList offsets; + + OffsetGTreeFilter(SortedRangeList offsets) { + this.offsets = offsets; + } + + @Override + public boolean showFilterMatches() { + return true; + } + + @Override + public boolean acceptsNode(GTreeNode node) { + if (!(node instanceof DataTypeNode)) { + return false; + } + DataTypeNode dataTypeNode = (DataTypeNode) node; + DataType dataType = dataTypeNode.getDataType(); + if (!(dataType instanceof Structure)) { + return false; + } + + Structure structure = (Structure) dataType; + OffsetIterator it = new OffsetIterator(structure); + for (Integer structureOffset : CollectionUtils.asIterable(it)) { + for (Range range : offsets) { + if (range.contains(structureOffset)) { + return true; + } + if (structureOffset > range.max) { + // ranges are ascending sorted order; the structure offset is already + // bigger than this range, so no more ranges can match + break; + } + } + } + + return false; + } + + private class OffsetIterator implements Iterator { + + private DataTypeComponent[] components; + private int index = 0; + private int length; + + OffsetIterator(Structure s) { + this.components = s.getComponents(); + this.length = components.length; + } + + @Override + public boolean hasNext() { + if (index >= length) { + return false; + } + return true; + } + + @Override + public Integer next() { + DataTypeComponent dtc = components[index++]; + return dtc.getOffset(); + } + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindStructuresBySizeAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindStructuresBySizeAction.java new file mode 100644 index 0000000000..75127c62a1 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/FindStructuresBySizeAction.java @@ -0,0 +1,82 @@ +/* ### + * 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.datamgr.actions; + +import docking.widgets.tree.GTreeNode; +import docking.widgets.tree.support.GTreeFilter; +import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; +import ghidra.app.plugin.core.datamgr.tree.DataTypeNode; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.Structure; +import ghidra.util.HelpLocation; +import ghidra.util.datastruct.Range; +import ghidra.util.datastruct.SortedRangeList; + +/** + * Allows the user to supply one or more sizes that are used to search for structures that have + * that size. + */ +public class FindStructuresBySizeAction extends FindDataTypesBySizeAction { + + @SuppressWarnings("hiding") + public static final String NAME = "Find Structures by Size"; + + public FindStructuresBySizeAction(DataTypeManagerPlugin plugin, String menuSubGroup) { + super(plugin, NAME, menuSubGroup); + setHelpLocation(new HelpLocation("DataTypeManagerPlugin", "Find_Structures_By_Size")); + } + + @Override + protected GTreeFilter createFilter(SortedRangeList values) { + return new StructureSizeGTreeFilter(values); + } + + private class StructureSizeGTreeFilter implements GTreeFilter { + + private final SortedRangeList sizes; + + StructureSizeGTreeFilter(SortedRangeList sizes) { + this.sizes = sizes; + } + + @Override + public boolean showFilterMatches() { + return true; + } + + @Override + public boolean acceptsNode(GTreeNode node) { + if (!(node instanceof DataTypeNode)) { + return false; + } + DataTypeNode dataTypeNode = (DataTypeNode) node; + DataType dataType = dataTypeNode.getDataType(); + if (!(dataType instanceof Structure)) { + return false; + } + + Structure structure = (Structure) dataType; + int length = structure.getLength(); + for (Range range : sizes) { + if (range.contains(length)) { + return true; + } + } + + return false; + } + } +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/IncludeDataTypesInFilterAction.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/IncludeDataTypesInFilterAction.java index 91b3511b1d..c641561add 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/IncludeDataTypesInFilterAction.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/datamgr/actions/IncludeDataTypesInFilterAction.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,23 +15,25 @@ */ package ghidra.app.plugin.core.datamgr.actions; +import docking.action.MenuData; +import docking.action.ToggleDockingAction; import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin; import ghidra.app.plugin.core.datamgr.DataTypesProvider; import ghidra.util.HelpLocation; -import docking.action.MenuData; -import docking.action.ToggleDockingAction; public class IncludeDataTypesInFilterAction extends ToggleDockingAction { private final DataTypesProvider provider; - public IncludeDataTypesInFilterAction(DataTypeManagerPlugin plugin, DataTypesProvider provider) { + public IncludeDataTypesInFilterAction(DataTypeManagerPlugin plugin, DataTypesProvider provider, + String menuSubGroup) { super("Include Data Members in Filter", plugin.getName()); this.provider = provider; setMenuBarData(new MenuData(new String[] { "Include Data Members in Filter" }, null, - "VeryLast", MenuData.NO_MNEMONIC, "3")); - setDescription("Selected indicates to include member names and data types in filter operations."); + "VeryLast", MenuData.NO_MNEMONIC, menuSubGroup)); + setDescription( + "Selected indicates to include member names and data types in filter operations."); setEnabled(true); setSelected(false); // default to off! diff --git a/Ghidra/Features/Base/src/test.slow/java/docking/widgets/dialogs/NumberRangeInputDialogTest.java b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/dialogs/NumberRangeInputDialogTest.java new file mode 100644 index 0000000000..1ba30d609b --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/docking/widgets/dialogs/NumberRangeInputDialogTest.java @@ -0,0 +1,178 @@ +/* ### + * 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 docking.widgets.dialogs; + +import static org.junit.Assert.*; + +import java.util.HashSet; +import java.util.Set; + +import javax.swing.JTextField; + +import org.junit.*; + +import docking.DialogComponentProvider; +import docking.DockingWindowManager; +import docking.test.AbstractDockingTest; +import ghidra.util.datastruct.Range; +import ghidra.util.datastruct.SortedRangeList; + +public class NumberRangeInputDialogTest extends AbstractDockingTest { + + private NumberRangeInputDialog dialog; + private JTextField textField; + + @Before + public void setUp() throws Exception { + createAndShowDialog(); + } + + @After + public void tearDown() throws Exception { + close(dialog); + } + + @Test + public void testRangeInput_SingleValue_Hex() { + + setText("0x4"); + + ok(); + + assertValue(0x4); + } + + @Test + public void testRangeInput_MultiValue_Mixed() { + + setText("0x4,0x12, 100"); + + ok(); + + assertValues(0x4, 0x12, 100); + } + + @Test + public void testRangeInput_Range_OneRange() { + + setText("0x4:0x12"); + + ok(); + + assertValues(new Range(0x4, 0x12)); + } + + @Test + public void testRangeInput_MixedValues_RangeAndSingleValues() { + + setText("100, -20, 0x4:0x6, -100"); + + ok(); + + assertValues(-100, -20, 0x4, 0x5, 0x6, 100); + } + + @Test + public void testRangeInput_SingleValue_InvalidNumber() { + + setText("0xBob"); + + ok(); + + assertStatusText("Unable to parse as a number: '0xBob'"); + } + + @Test + public void testRangeInput_MixedValues_InvalidNumber() { + + setText("100, 0xBob"); + + ok(); + + assertStatusText("Unable to parse as a number: '0xBob'"); + } + + @Test + public void testRangeInput_Range_InvalidNumber() { + + setText("100:0xBob"); + + ok(); + + assertStatusText("Unable to parse as a number: '100:0xBob'"); + } + + private void assertStatusText(String expected) { + String actual = runSwing(() -> dialog.getStatusText()); + assertEquals(expected, actual); + } + + private void assertValues(Range range) { + SortedRangeList ranges = dialog.getValue(); + assertEquals(range.size(), ranges.getNumValues()); + assertEquals(range, ranges.getRange(0)); + } + + private void assertValues(int... values) { + SortedRangeList ranges = dialog.getValue(); + assertEquals(values.length, ranges.getNumValues()); + + Set set = new HashSet<>(); + for (int value : values) { + set.add(value); + } + + for (Range range : ranges) { + for (int value : range) { + assertTrue("Range value not expected: " + value, set.contains(value)); + } + } + } + + private void assertValue(int expected) { + SortedRangeList ranges = dialog.getValue(); + assertEquals(1, ranges.getNumValues()); + assertEquals(expected, ranges.getRange(0).min); + assertEquals(expected, ranges.getRange(0).max); + } + + private void createAndShowDialog() { + dialog = new NumberRangeInputDialog("My Dialog Title", "Offset(s)"); + show(dialog); + textField = getTextField(dialog); + } + + private void ok() { + pressButtonByText(dialog, "OK"); + } + + private void setText(String value) { + setText(textField, value); + } + + private void show(DialogComponentProvider theDialog) { + + runSwing(() -> { + DockingWindowManager.showDialog(theDialog); + }, false); + + waitForDialogComponent(NumberRangeInputDialog.class); + } + + private JTextField getTextField(NumberRangeInputDialog theDialog) { + return theDialog.getTextField(); + } +} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPluginTest.java index d91ff9750d..0d95ee33f2 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/datamgr/DataTypeManagerPluginTest.java @@ -24,8 +24,7 @@ import java.awt.event.KeyEvent; import java.io.File; import java.io.FileNotFoundException; import java.net.*; -import java.util.ArrayList; -import java.util.Iterator; +import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -35,6 +34,7 @@ import javax.swing.tree.TreePath; import org.junit.*; +import docking.DialogComponentProvider; import docking.DockingUtils; import docking.action.DockingActionIf; import docking.action.ToggleDockingActionIf; @@ -42,11 +42,12 @@ import docking.actions.KeyBindingUtils; import docking.tool.ToolConstants; import docking.tool.util.DockingToolConstants; import docking.widgets.OptionDialog; +import docking.widgets.dialogs.NumberRangeInputDialog; import docking.widgets.tree.GTreeNode; import ghidra.app.context.ProgramActionContext; import ghidra.app.plugin.core.compositeeditor.ApplyAction; import ghidra.app.plugin.core.compositeeditor.CompositeEditorTableAction; -import ghidra.app.plugin.core.datamgr.actions.CreateTypeDefDialog; +import ghidra.app.plugin.core.datamgr.actions.*; import ghidra.app.plugin.core.datamgr.archive.Archive; import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler; import ghidra.app.plugin.core.datamgr.tree.*; @@ -62,9 +63,10 @@ import ghidra.program.database.ProgramDB; import ghidra.program.database.data.ProgramDataTypeManager; import ghidra.program.model.data.*; import ghidra.test.*; -import ghidra.util.classfinder.ClassFilter; -import ghidra.util.classfinder.ClassSearcher; +import ghidra.util.classfinder.ClassSearchTask; +import ghidra.util.task.Task; import ghidra.util.task.TaskMonitor; +import util.CollectionUtils; import utilities.util.FileUtilities; /** @@ -74,10 +76,11 @@ import utilities.util.FileUtilities; public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTest { private static final String BUILTIN_NAME = "BuiltInTypes"; - private static final String PROGRAM_FILENAME = "notepad"; + private static final String PROGRAM_FILENAME = "sample"; private TestEnv env; private PluginTool tool; + private ProgramBuilder builder; private ProgramDB program; private DataTypeManagerPlugin plugin; private DataTypeArchiveGTree tree; @@ -122,7 +125,7 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe } private ProgramDB buildProgram() throws Exception { - ProgramBuilder builder = new ProgramBuilder("notepad", ProgramBuilder._TOY, this); + builder = new ProgramBuilder("sample", ProgramBuilder._TOY, this); builder.createMemory(".text", "0x1001000", 0x100); CategoryPath miscPath = new CategoryPath("/MISC"); @@ -231,10 +234,10 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe // select "New Category" action DataTypeTestUtils.performAction(action, tree, false); - waitForDialogComponent("Cannot Edit Tree Node"); + DialogComponentProvider dialog = waitForDialogComponent("Cannot Edit Tree Node"); + close(dialog); - // verify that the tree opens a new node with the default - // category name is "New Category" + // verify that the tree opens a new node with the default category name is "New Category" assertEquals(childCount + 1, miscNode.getChildCount()); GTreeNode node = miscNode.getChild("New Category"); assertNotNull(node); @@ -620,7 +623,7 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe toggleDetailedSearch(true); assertSingleFilterMatch( - new String[] { "Data Types", "notepad", "Category1", "Category2", "MyStruct" }); + new String[] { "Data Types", "sample", "Category1", "Category2", "MyStruct" }); } @Test @@ -663,48 +666,34 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe @Test public void testRefreshBuiltins() throws Exception { + + try { + doTestRefreshBuiltins(); + } + finally { + cleanupTestBuiltin(); + } + } + + private void doTestRefreshBuiltins() throws Exception { GTreeNode treeRoot = tree.getModelRoot(); GTreeNode builtInNode = treeRoot.getChild("BuiltInTypes"); assertNull("Test setup Error: ghidra.app.test.TestDataType was not removed!", builtInNode.getChild("TestDataType")); - compileJavaFile(); + compileJavaDataType(); DockingActionIf action = getAction(plugin, "Refresh BuiltInTypes"); assertTrue(action.isEnabledForContext(treeContext)); DataTypeTestUtils.performAction(action, tree, false); waitForTasks(); - waitForProgram(); - waitForActionToBeEnabled(action); builtInNode = treeRoot.getChild("BuiltInTypes"); assertNotNull(builtInNode.getChild("TestDataType")); - - ClassFilter filter = new BuiltInDataTypeClassExclusionFilter(); - ArrayList listOne = - new ArrayList<>(ClassSearcher.getInstances(BuiltInDataType.class, filter)); - - DataTypeManager bdtm = plugin.getBuiltInDataTypesManager(); - ArrayList listTwo = new ArrayList<>(); - Iterator iter = bdtm.getAllDataTypes(); - while (iter.hasNext()) { - DataType dt = iter.next(); - listTwo.add(dt); - } - for (DataType dt : listOne) { - boolean found = false; - for (DataType dt2 : listTwo) { - if (dt.isEquivalent(dt2)) { - found = true; - break; - } - } - assertTrue(found); - } } @Test @@ -793,10 +782,163 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe assertNotNull(message, action); } + @Test + public void testAction_FindStructureByOffset() { + + DockingActionIf action = getAction(plugin, FindStructuresByOffsetAction.NAME); + performAction(action, false); + + NumberRangeInputDialog dialog = waitForDialogComponent(NumberRangeInputDialog.class); + setText(dialog, "0x1"); + + pressButtonByText(dialog, "OK"); + + DataTypesProvider resultsProvider = waitForComponentProvider(DataTypesProvider.class, + FindStructuresByOffsetAction.NAME); + assertMatchingStructures(resultsProvider, "ArrayStruct"); + } + + @Test + public void testAction_FindStructureByOffset_NoMatches() { + + DockingActionIf action = getAction(plugin, FindStructuresByOffsetAction.NAME); + performAction(action, false); + + NumberRangeInputDialog dialog = waitForDialogComponent(NumberRangeInputDialog.class); + setText(dialog, "0x100"); + + pressButtonByText(dialog, "OK"); + + DataTypesProvider resultsProvider = waitForComponentProvider(DataTypesProvider.class, + FindStructuresByOffsetAction.NAME); + assertMatchingStructures(resultsProvider); + } + + @Test + public void testAction_FindStructureByOffset_Range() { + + DockingActionIf action = getAction(plugin, FindStructuresByOffsetAction.NAME); + performAction(action, false); + + NumberRangeInputDialog dialog = waitForDialogComponent(NumberRangeInputDialog.class); + setText(dialog, "0x1:0x3,20"); + + pressButtonByText(dialog, "OK"); + + DataTypesProvider resultsProvider = waitForComponentProvider(DataTypesProvider.class, + FindStructuresByOffsetAction.NAME); + assertMatchingStructures(resultsProvider, "ArrayStruct"); + } + + @Test + public void testAction_FindStructureByOffset_MixedInput() { + + createStructureWithOffset_0x4(); // 0x4 + createStructureWithOffset_0x8(); // 0x4, 0x8 + createStructureWithOffset_0x10(); // 0x4, 0x8, 0x10 + createStructureWithOffset_0x20(); // 0x8, 0x16, 0x20 + + DockingActionIf action = getAction(plugin, FindStructuresByOffsetAction.NAME); + performAction(action, false); + + NumberRangeInputDialog dialog = waitForDialogComponent(NumberRangeInputDialog.class); + setText(dialog, "0x8:0x10, 32"); + + pressButtonByText(dialog, "OK"); + + DataTypesProvider resultsProvider = waitForComponentProvider(DataTypesProvider.class, + FindStructuresByOffsetAction.NAME); + assertMatchingStructures(resultsProvider, "Structure_0x8", "Structure_0x10", + "Structure_0x20"); + } + + @Test + public void testAction_FindStructureBySize() { + + createStructureWithOffset_0x4(); // 6 + createStructureWithOffset_0x8(); // 10 + createStructureWithOffset_0x10(); // 12 + createStructureWithOffset_0x20(); // 22 + + DockingActionIf action = getAction(plugin, FindStructuresBySizeAction.NAME); + performAction(action, false); + + NumberRangeInputDialog dialog = waitForDialogComponent(NumberRangeInputDialog.class); + setText(dialog, "10"); + + pressButtonByText(dialog, "OK"); + + DataTypesProvider resultsProvider = waitForComponentProvider(DataTypesProvider.class, + FindStructuresBySizeAction.NAME); + assertMatchingStructures(resultsProvider, "Structure_0x8"); + } + + @Test + public void testAction_FindStructureBySize_Ranage() { + + createStructureWithOffset_0x4(); // 6 + createStructureWithOffset_0x8(); // 10 + createStructureWithOffset_0x10(); // 12 + createStructureWithOffset_0x20(); // 22 + + DockingActionIf action = getAction(plugin, FindStructuresBySizeAction.NAME); + performAction(action, false); + + NumberRangeInputDialog dialog = waitForDialogComponent(NumberRangeInputDialog.class); + setText(dialog, "12:22"); + + pressButtonByText(dialog, "OK"); + + DataTypesProvider resultsProvider = waitForComponentProvider(DataTypesProvider.class, + FindStructuresBySizeAction.NAME); + assertMatchingStructures(resultsProvider, "Structure_0x10", "Structure_0x20"); + } + //================================================================================================== // Private methods //================================================================================================== + private void createStructureWithOffset_0x4() { + + StructureDataType stuct = new StructureDataType("Structure_0x4", 0); + stuct.add(new DWordDataType()); + stuct.add(new WordDataType()); + builder.addDataType(stuct); + } + + private void createStructureWithOffset_0x8() { + + StructureDataType stuct = new StructureDataType("Structure_0x8", 0); + stuct.add(new DWordDataType()); + stuct.add(new DWordDataType()); + stuct.add(new WordDataType()); + builder.addDataType(stuct); + } + + private void createStructureWithOffset_0x10() { + + StructureDataType stuct = new StructureDataType("Structure_0x10", 0); + stuct.add(new DWordDataType()); + stuct.add(new DWordDataType()); + stuct.add(new WordDataType()); + stuct.add(new WordDataType()); + builder.addDataType(stuct); + } + + private void createStructureWithOffset_0x20() { + + StructureDataType stuct = new StructureDataType("Structure_0x20", 0); + stuct.add(new QWordDataType()); + stuct.add(new QWordDataType()); + stuct.add(new DWordDataType()); + stuct.add(new WordDataType()); + builder.addDataType(stuct); + } + + private void setText(NumberRangeInputDialog dialog, String text) { + runSwing(() -> dialog.setValue(text)); + } + private void editSignature(String name, String newSignature) { expandNode(programNode); GTreeNode child = programNode.getChild(name); @@ -916,6 +1058,41 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe } } + private void assertMatchingStructures(DataTypesProvider resultsProvider, String... names) { + + DataTypeArchiveGTree gTree = resultsProvider.getGTree(); + waitForTree(gTree); + Map structures = getStructures(resultsProvider); + assertEquals("Incorrect number of matches.\n\tExpected: " + Arrays.toString(names) + + "\n\tFound: " + structures.keySet(), names.length, structures.size()); + for (String name : names) { + if (!structures.containsKey(name)) { + fail("Structure not found in results: '" + name + "'.\nFound: " + + structures.keySet()); + } + } + } + + private Map getStructures(DataTypesProvider resultsProvider) { + + Map map = new HashMap<>(); + DataTypeArchiveGTree gTree = resultsProvider.getGTree(); + GTreeNode rootNode = gTree.getViewRoot(); + Iterator it = rootNode.iterator(true); + for (GTreeNode node : CollectionUtils.asIterable(it)) { + if (!(node instanceof DataTypeNode)) { + continue; + } + DataTypeNode dtNode = (DataTypeNode) node; + DataType dt = dtNode.getDataType(); + if (dt instanceof Structure) { + map.put(dt.getName(), (Structure) dt); + } + } + + return map; + } + private void assertEmptyTree() { final GTreeNode rootNode = tree.getViewRoot(); final Integer[] box = new Integer[1]; @@ -1061,7 +1238,7 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe } } - private void compileJavaFile() throws Exception { + private void compileJavaDataType() throws Exception { boolean success = false; try { @@ -1072,8 +1249,8 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe Assert.fail("Could not create directory " + binDir.getAbsolutePath()); } } - File javaFile = new File(binDir, "TestDataType.java"); + File javaFile = new File(binDir, "TestDataType.java"); FileUtilities.copyFile(file, javaFile, false, TaskMonitor.DUMMY); assertTrue(javaFile.exists()); @@ -1088,6 +1265,31 @@ public class DataTypeManagerPluginTest extends AbstractGhidraHeadedIntegrationTe } } + private void cleanupTestBuiltin() throws Exception { + File binDir = getClassesDirectory(); + File javaFile = new File(binDir, "TestDataType.java"); + File classFile = new File(binDir, "TestDataType.class"); + javaFile.delete(); + classFile.delete(); + + // force built-ins to get restored + Task task = new ClassSearchTask(); + task.run(TaskMonitor.DUMMY); + + DockingActionIf action = getAction(plugin, "Refresh BuiltInTypes"); + assertTrue(action.isEnabledForContext(treeContext)); + DataTypeTestUtils.performAction(action, tree, false); + + waitForTasks(); + waitForProgram(); + waitForActionToBeEnabled(action); + + GTreeNode treeRoot = tree.getModelRoot(); + GTreeNode builtInNode = treeRoot.getChild("BuiltInTypes"); + assertNull("Test setup Error: ghidra.app.test.TestDataType was not removed!", + builtInNode.getChild("TestDataType")); + } + private File getTestDataTypeFile() { URL url = getClass().getResource("TestDataType.txt"); try { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java index 740c34a088..8d302f73a7 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/test/AbstractDockingTest.java @@ -754,6 +754,26 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { return provider; } + /** + * Allows you to find a component provider with the given title. Most plugins will + * only ever have a single provider. In those cases, use + * {@link #waitForComponentProvider(Class)}. This version of that method is to allow you to + * differentiate between multiple instances of a given provider that have different titles. + * + * @param clazz The class of the ComponentProvider to locate + * @param title the title of the component provider + * @return The component provider, or null if one cannot be found + */ + public static T waitForComponentProvider(Class clazz, + String title) { + + DockingWindowManager dwm = findActiveDockingWindowManager(); + assertNotNull("Unable to find a DockingWindowManager - is there a tool showing?", dwm); + + T provider = doWaitForComponentProvider(dwm, clazz, title); + return provider; + } + @SuppressWarnings("unchecked") private static DockingWindowManager findActiveDockingWindowManager() { DockingWindowManager activeInstance = DockingWindowManager.getActiveInstance(); @@ -795,6 +815,25 @@ public abstract class AbstractDockingTest extends AbstractGenericTest { "Timed-out waiting for ComponentProvider of class: " + clazz); } + private static T doWaitForComponentProvider( + DockingWindowManager windowManager, Class clazz, String title) { + + Objects.requireNonNull(windowManager, "DockingWindowManager cannot be null"); + + int totalTime = 0; + while (totalTime <= DEFAULT_WAIT_TIMEOUT) { + + T t = getComponentProvider(windowManager, clazz); + if (Objects.deepEquals(title, t.getTitle())) { + return t; + } + totalTime += sleep(DEFAULT_WAIT_DELAY); + } + + throw new AssertionFailedError( + "Timed-out waiting for ComponentProvider of class: " + clazz); + } + /** These providers are those that appear in dialogs outside of the main frame **/ private static T getDetachedWindowProvider( final Class providerClass, final DockingWindowManager windowManager) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/NumberRangeInputDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/NumberRangeInputDialog.java new file mode 100644 index 0000000000..da34f0cad7 --- /dev/null +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/dialogs/NumberRangeInputDialog.java @@ -0,0 +1,252 @@ +/* ### + * 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 docking.widgets.dialogs; + +import java.awt.event.*; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.*; + +import docking.DialogComponentProvider; +import docking.DockingWindowManager; +import docking.widgets.label.GLabel; +import docking.widgets.textfield.HintTextField; +import ghidra.util.NumericUtilities; +import ghidra.util.datastruct.SortedRangeList; +import ghidra.util.layout.PairLayout; + +/** + * An input dialog that accepts number input as discrete values or a range of values using + * ':' as the range separator. + */ +public class NumberRangeInputDialog extends DialogComponentProvider { + + private static final String RANGE_DELIMITER = ":"; + private static final String DEFAULT_VALUE = ""; + private static final String HINT_TEXT = "e.g. 2,5 or 1,4:8"; + private static final int MAX_SIZE = 256; + + private boolean wasCancelled; + private String inputLabel; + private String initialValue = DEFAULT_VALUE; + private SortedRangeList rangeList = new SortedRangeList(); + private HintTextField textField; + private KeyListener keyListener; + + public NumberRangeInputDialog(String title, String label) { + super(title, true, true/* status */, true /* buttons */, + false /* no tasks */); + + keyListener = new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + int keyCode = e.getKeyCode(); + if (keyCode == KeyEvent.VK_ENTER) { + okCallback(); + } + } + }; + + this.inputLabel = label; + setTransient(true); + addOKButton(); + addCancelButton(); + buildMainPanel(); + + DocumentListener docListener = new DocumentListener() { + @Override + public void changedUpdate(DocumentEvent e) { + clearStatusText(); + } + + @Override + public void insertUpdate(DocumentEvent e) { + clearStatusText(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + clearStatusText(); + } + }; + textField.getDocument().addDocumentListener(docListener); + setFocusComponent(textField); + } + + private void buildMainPanel() { + + JPanel panel = new JPanel(new PairLayout(5, 5, 120)); + textField = new MyHintTextField(HINT_TEXT); + textField.setText(initialValue); + textField.addKeyListener(keyListener); + textField.setName("number.range.input.dialog.text.field"); + panel.add(new GLabel(inputLabel, SwingConstants.RIGHT)); + panel.add(textField); + + panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); + this.addWorkPanel(panel); + } + + /** + * show displays the dialog, gets the user input + * + * @return false if the user cancelled the operation + */ + public boolean show() { + DockingWindowManager.showDialog(this); + return !wasCancelled; + } + + @Override + protected void okCallback() { + wasCancelled = false; + + if (!parseRanges()) { + return; + } + + close(); + } + + JTextField getTextField() { + return textField; + } + + private boolean parseRanges() { + + // format: 1 + // 1,4 + // 1-4 + // 1-4,8 + // -8, 0xc, 0x10:0x20 + // -0x20:-0x10 + + String value = textField.getText(); + String[] parts = value.split(","); + for (String rangeText : parts) { + if (!addRange(rangeText)) { + return false; + } + } + return true; + } + + private boolean addRange(String rangeText) { + + String trimmed = rangeText.trim(); + if (!trimmed.contains(RANGE_DELIMITER)) { + try { + long parsedLong = NumericUtilities.parseLong(trimmed); + int intValue = (int) parsedLong; + rangeList.addRange(intValue, intValue); + } + catch (NumberFormatException e) { + setStatusText("Unable to parse as a number: '" + trimmed + "'"); + return false; + } + return true; + } + + // this must be a range + String[] startAndEnd = trimmed.split(RANGE_DELIMITER); + try { + long parsedLong = NumericUtilities.parseLong(startAndEnd[0]); + int startInt = (int) parsedLong; + + parsedLong = NumericUtilities.parseLong(startAndEnd[1]); + int endInt = (int) parsedLong; + + rangeList.addRange(startInt, endInt); + } + catch (NumberFormatException e) { + setStatusText("Unable to parse as a number: '" + trimmed + "'"); + return false; + } + return true; + } + + @Override + protected void cancelCallback() { + wasCancelled = true; + rangeList.clear(); + close(); + } + + /** + * Returns if this dialog is cancelled + * @return true if cancelled + */ + public boolean wasCancelled() { + return wasCancelled; + } + + /** + * Return the value of the first (and maybe only) text field + * @return the text field value + */ + public SortedRangeList getValue() { + return rangeList; + } + + /** + * Sets the text of the primary text field + * @param text the text + */ + public void setValue(String text) { + textField.setText(text); + } + + private class MyHintTextField extends HintTextField { + + MyHintTextField(String hintText) { + super(hintText); + setColumns(20); + } + + @Override + protected Document createDefaultModel() { + return new MyDocument(this); + } + + private class MyDocument extends PlainDocument { + private JTextField documentTf; + + private MyDocument(JTextField textField) { + super(); + this.documentTf = textField; + } + + @Override + public void insertString(int offs, String str, AttributeSet a) + throws BadLocationException { + if (str == null) { + return; + } + + String text = documentTf.getText(); + if (text.length() + str.length() > MAX_SIZE) { + int nTooMany = text.length() + str.length() - MAX_SIZE; + int len = str.length() - nTooMany; + str = str.substring(0, len); + } + super.insertString(offs, str, a); + } + } + } + +} diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassSearchTask.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassSearchTask.java index 2492484e7d..12fcadb34d 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassSearchTask.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/classfinder/ClassSearchTask.java @@ -29,10 +29,10 @@ public class ClassSearchTask extends Task { } @Override - public void run(final TaskMonitor taskMonitor) { + public void run(final TaskMonitor monitor) { try { - ClassSearcher.search(true, taskMonitor); + ClassSearcher.search(true, monitor); } catch (CancelledException e) { // user cancelled diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/SortedRangeList.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/SortedRangeList.java index 3ee18d4795..55d487f7e2 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/SortedRangeList.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/datastruct/SortedRangeList.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. @@ -30,7 +29,7 @@ public class SortedRangeList implements Iterable { * Creates a new empty sorted range list. */ public SortedRangeList() { - set = new TreeSet(); + set = new TreeSet<>(); } /** @@ -39,7 +38,7 @@ public class SortedRangeList implements Iterable { * @param list the sorted range list to make an equivalent copy of. */ public SortedRangeList(SortedRangeList list) { - set = new TreeSet(); + set = new TreeSet<>(); Iterator it = list.set.iterator(); while (it.hasNext()) { Range r = it.next(); @@ -98,7 +97,7 @@ public class SortedRangeList implements Iterable { return set.iterator(); } Iterator it = set.iterator(); - LinkedList ll = new LinkedList(); + LinkedList ll = new LinkedList<>(); while (it.hasNext()) { ll.addFirst(it.next()); } @@ -362,4 +361,8 @@ public class SortedRangeList implements Iterable { public Iterator iterator() { return getRanges(true); } + + public void clear() { + set.clear(); + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BuiltInDataTypeManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BuiltInDataTypeManager.java index c5a3a6e721..8f4588dad9 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BuiltInDataTypeManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/data/BuiltInDataTypeManager.java @@ -41,7 +41,8 @@ public class BuiltInDataTypeManager extends StandAloneDataTypeManager { private ChangeListener classSearcherListener = e -> refresh(); /** - * Returns shared instance of built-in data-type manager. + * Returns shared instance of built-in data type manager. + * @return the manager */ public static synchronized BuiltInDataTypeManager getDataTypeManager() { if (manager == null) { @@ -58,9 +59,6 @@ public class BuiltInDataTypeManager extends StandAloneDataTypeManager { return manager; } - /** - * Constructor - */ private BuiltInDataTypeManager() { super(BUILT_IN_DATA_TYPES_NAME); initialize(); @@ -104,8 +102,6 @@ public class BuiltInDataTypeManager extends StandAloneDataTypeManager { populateBuiltInTypes(); } - ///////////////////////////////////// - private void initialize() { try { populateBuiltInTypes(); @@ -123,7 +119,8 @@ public class BuiltInDataTypeManager extends StandAloneDataTypeManager { protected void populateBuiltInTypes() { int id = super.startTransaction("Populate"); try { - ArrayList list = new ArrayList<>(); + + List list = new ArrayList<>(); ClassFilter filter = new BuiltInDataTypeClassExclusionFilter(); List datatypes = ClassSearcher.getInstances(BuiltInDataType.class, filter); @@ -141,7 +138,7 @@ public class BuiltInDataTypeManager extends StandAloneDataTypeManager { datatype.getName() + "'"); } else if (list.size() != 1) { - throw new AssertException("Should be no duplicate named nuilt-in types"); + throw new AssertException("Should be no duplicate named built-in types"); } } } @@ -156,7 +153,8 @@ public class BuiltInDataTypeManager extends StandAloneDataTypeManager { return DataTypeManager.BUILT_IN_ARCHIVE_UNIVERSAL_ID; } throw new IllegalArgumentException( - "Only Built-In data types can be resolved by the BuiltInTypes manager."); + "Only Built-in data types can be resolved by the " + getClass().getSimpleName() + + " manager."); } @Override @@ -197,7 +195,6 @@ public class BuiltInDataTypeManager extends StandAloneDataTypeManager { @Override public void close() { - // do nothing - cannot close a built-in data type manager - // close performed automatically during shutdown + // cannot close a built-in data type manager; close performed automatically during shutdown } } diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/util/datastruct/Range.java b/Ghidra/Framework/Utility/src/main/java/ghidra/util/datastruct/Range.java index bf607e4ceb..5379451ff9 100644 --- a/Ghidra/Framework/Utility/src/main/java/ghidra/util/datastruct/Range.java +++ b/Ghidra/Framework/Utility/src/main/java/ghidra/util/datastruct/Range.java @@ -15,14 +15,18 @@ */ package ghidra.util.datastruct; +import java.util.Iterator; +import java.util.stream.IntStream; + /** * A class for holding a minimum and maximum signed int values that define a range. */ -public class Range implements Comparable { +public class Range implements Comparable, Iterable { /** The range's minimum extent. */ public int min; /** The range's maximum extent (inclusive). */ public int max; + /** * Creates a range whose extent is from min to max. * @param min the minimum extent. @@ -31,8 +35,8 @@ public class Range implements Comparable { */ public Range(int min, int max) { if (max < min) { - throw new IllegalArgumentException("Range max (" + max - + ") cannot be less than min (" + min + ")."); + throw new IllegalArgumentException( + "Range max (" + max + ") cannot be less than min (" + min + ")."); } this.min = min; this.max = max; @@ -50,11 +54,11 @@ public class Range implements Comparable { } @Override - public boolean equals(Object obj) { + public boolean equals(Object obj) { if (obj.getClass() != Range.class) { return false; } - Range other = (Range)obj; + Range other = (Range) obj; return other.min == min && other.max == max; } @@ -64,20 +68,29 @@ public class Range implements Comparable { } @Override - public String toString() { - return "("+min+","+max+")"; + public String toString() { + return "(" + min + "," + max + ")"; } + /** * Returns true if the value is within the ranges extent. * @param value the value to check. + * @return true if the value is within the ranges extent. */ public boolean contains(int value) { return value >= min && value <= max; } + /** * Returns the range's size. + * @return the size */ public long size() { - return (long)max - (long)min + 1; + return (long) max - (long) min + 1; + } + + @Override + public Iterator iterator() { + return IntStream.range(min, max + 1).iterator(); } } diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataTypeManagerPluginScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataTypeManagerPluginScreenShots.java index 7947f16ba2..5ca379b769 100644 --- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataTypeManagerPluginScreenShots.java +++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/DataTypeManagerPluginScreenShots.java @@ -31,6 +31,7 @@ import docking.widgets.table.GTableCellRenderer; import docking.widgets.tree.GTree; import docking.widgets.tree.GTreeNode; import ghidra.app.plugin.core.datamgr.*; +import ghidra.app.plugin.core.datamgr.actions.FindStructuresBySizeAction; import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler; import ghidra.app.plugin.core.datamgr.archive.InvalidFileArchive; import ghidra.app.plugin.core.datamgr.util.ConflictDialog; @@ -155,6 +156,14 @@ public class DataTypeManagerPluginScreenShots extends GhidraScreenShotGenerator pressButtonByText(d, "Cancel"); } + @Test + public void testFindDataTypesBySize() { + performAction(FindStructuresBySizeAction.NAME, "DataTypeManagerPlugin", false); + JDialog d = waitForJDialog(FindStructuresBySizeAction.NAME); + captureDialog(); + pressButtonByText(d, "Cancel"); + } + @Test public void testPreviewWindow() {