GP-5310 Created global search and replace feature

This commit is contained in:
ghidragon 2025-02-28 20:08:40 -05:00
parent cc1228bdaa
commit 6fb115358a
93 changed files with 7469 additions and 141 deletions

View file

@ -439,6 +439,13 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
return getCommentAddresses(addrSet).getAddresses(forward);
}
@Override
public long getCommentAddressCount() {
return program.viewport.unionedAddresses(
s -> program.trace.getCommentAdapter().getAddressSetView(Lifespan.at(s)))
.getNumAddresses();
}
@Override
public String getComment(int commentType, Address address) {
try (LockHold hold = program.trace.lockRead()) {
@ -447,6 +454,16 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
}
}
@Override
public CodeUnitComments getAllComments(Address address) {
CommentType[] types = CommentType.values();
String[] comments = new String[types.length];
for (CommentType type : types) {
comments[type.ordinal()] = getComment(type, address);
}
return new CodeUnitComments(comments);
}
@Override
public void setComment(Address address, int commentType, String comment) {
program.trace.getCommentAdapter()

View file

@ -249,4 +249,9 @@ public class DBTraceProgramViewFragment implements ProgramFragment {
public void move(Address min, Address max) throws NotFoundException {
throw new UnsupportedOperationException();
}
@Override
public boolean isDeleted() {
return false;
}
}

View file

@ -239,4 +239,9 @@ public class DBTraceProgramViewRootModule implements ProgramModule {
public long getTreeID() {
return 0;
}
@Override
public boolean isDeleted() {
return false;
}
}

View file

@ -516,7 +516,7 @@ src/main/help/help/topics/ScalarSearchPlugin/images/ScalarWindow.png||GHIDRA||||
src/main/help/help/topics/ScalarSearchPlugin/images/SearchAllScalarsDialog.png||GHIDRA||||END|
src/main/help/help/topics/Search/Instruction_Mnemonic_Search.htm||GHIDRA||||END|
src/main/help/help/topics/Search/Query_Results_Dialog.htm||GHIDRA||||END|
src/main/help/help/topics/Search/Regular_Expressions.htm||GHIDRA||||END|
src/main/help/help/topics/Search/SearchAndReplace.htm||GHIDRA||||END|
src/main/help/help/topics/Search/Search_Formats.htm||GHIDRA||||END|
src/main/help/help/topics/Search/Search_Instruction_Patterns.htm||GHIDRA||||END|
src/main/help/help/topics/Search/Search_Memory.htm||GHIDRA||||END|
@ -533,6 +533,8 @@ src/main/help/help/topics/Search/images/MemorySearchProviderWithOptionsOn.png||G
src/main/help/help/topics/Search/images/MemorySearchProviderWithScanPanelOn.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/MultipleSelectionError.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/QueryResultsSearch.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchAndReplaceDialog.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchAndReplaceResults.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchForAddressTables.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchInstructionPatterns.png||GHIDRA||||END|
src/main/help/help/topics/Search/images/SearchInstructionPatternsControlPanel.png||GHIDRA||||END|
@ -579,10 +581,6 @@ src/main/help/help/topics/SymbolTreePlugin/SymbolTree.htm||GHIDRA||||END|
src/main/help/help/topics/SymbolTreePlugin/images/CreateExternalLocation.png||GHIDRA||||END|
src/main/help/help/topics/SymbolTreePlugin/images/EditExternalLocation.png||GHIDRA||||END|
src/main/help/help/topics/SymbolTreePlugin/images/SymbolTree.png||GHIDRA||||END|
src/main/help/help/topics/Tables/GhidraTableHeaders.html||GHIDRA||||END|
src/main/help/help/topics/Tables/images/BytesSettingsDialog.png||GHIDRA||reviewed||END|
src/main/help/help/topics/Tables/images/MultipleColumnSortDialog.png||GHIDRA||||END|
src/main/help/help/topics/Tables/images/SelectColumnsDialog.png||GHIDRA||||END|
src/main/help/help/topics/Tool/Configure_Tool.htm||GHIDRA||||END|
src/main/help/help/topics/Tool/Ghidra_Tool_Administration.htm||GHIDRA||||END|
src/main/help/help/topics/Tool/ShowLog.htm||GHIDRA|||References wcbiema in screen snapshot|END|
@ -604,13 +602,6 @@ src/main/help/help/topics/Tool/images/SetToolAssociations.png||GHIDRA||||END|
src/main/help/help/topics/Tool/images/ShowLog.png||GHIDRA||||END|
src/main/help/help/topics/Tool/images/Tip.png||GHIDRA||||END|
src/main/help/help/topics/TranslateStringsPlugin/TranslateStringsPlugin.htm||GHIDRA||||END|
src/main/help/help/topics/Trees/GhidraTreeFilter.html||GHIDRA||||END|
src/main/help/help/topics/Trees/images/Filter.png||GHIDRA||||END|
src/main/help/help/topics/Trees/images/FilterClearButton.png||GHIDRA||||END|
src/main/help/help/topics/Trees/images/FilterOptions.png||GHIDRA||||END|
src/main/help/help/topics/Trees/images/TableColumnFilter.png||GHIDRA||||END|
src/main/help/help/topics/Trees/images/TableColumnFilterAfterFilterApplied.png||GHIDRA||||END|
src/main/help/help/topics/Trees/images/TableColumnFilterDialog.png||GHIDRA||||END|
src/main/help/help/topics/VSCodeIntegration/VSCodeIntegration.htm||GHIDRA||||END|
src/main/help/help/topics/ValidateProgram/ValidateProgram.html||GHIDRA||||END|
src/main/help/help/topics/ValidateProgram/images/ValidateProgram.png||GHIDRA||||END|

View file

@ -22,3 +22,4 @@ OverviewColorService
DWARFFunctionFixup
ElfInfoProducer
FSBFileHandler
SearchAndReplaceHandler

View file

@ -204,7 +204,7 @@ icon.plugin.merge.conflict.lock = lock.gif
icon.plugin.merge.conflict.unlock = unlock.gif
icon.plugin.merge.status.pending = bullet_green.png
icon.plugin.merge.status.in.progress = right.png
icon.plugin.merge.status.complete = checkmark_green.gif
icon.plugin.merge.status.complete = icon.checkmark.green
icon.plugin.myprogramchanges.merge = vcMerge.png
icon.plugin.myprogramchanges.checkin = vcCheckIn.png
@ -402,9 +402,7 @@ icon.base.mem.search.panel.options = view_left_right.png
icon.base.mem.search.panel.scan = view_bottom.png
icon.base.mem.search.panel.search = view_top_bottom.png
icon.base.plugin.quickfix.done = icon.checkmark.green
[Dark Defaults]

View file

@ -327,12 +327,13 @@
<tocdef id="Program Search" sortgroup="p" text="Program Search" target="help/topics/Search/Searching.htm" >
<tocdef id="Memory" sortgroup="a" text="Memory" target="help/topics/Search/Search_Memory.htm" />
<tocdef id="Text" sortgroup="b" text="Text" target="help/topics/Search/Search_Program_Text.htm" />
<tocdef id="Strings" sortgroup="c" text="Strings" target="help/topics/Search/Search_for_Strings.htm" />
<tocdef id="Scalars" sortgroup="d" text="Scalars" target="help/topics/ScalarSearchPlugin/The_Scalar_Table.htm" />
<tocdef id="Instruction Patterns" sortgroup="e" text="Instruction Patterns" target="help/topics/Search/Search_Instruction_Patterns.htm" />
<tocdef id="Address Tables" sortgroup="f" text="Address Tables" target="help/topics/Search/Search_for_AddressTables.htm" />
<tocdef id="Direct References" sortgroup="g" text="Direct References" target="help/topics/Search/Search_for_DirectReferences.htm" />
<tocdef id="Search Results Window" sortgroup="h" text="Query Results Window" target="help/topics/Search/Query_Results_Dialog.htm" />
<tocdef id="Search And Replace" sortgroup="c" text="Search and Replace" target="help/topics/Search/SearchAndReplace.htm" />
<tocdef id="Strings" sortgroup="d" text="Strings" target="help/topics/Search/Search_for_Strings.htm" />
<tocdef id="Scalars" sortgroup="e" text="Scalars" target="help/topics/ScalarSearchPlugin/The_Scalar_Table.htm" />
<tocdef id="Instruction Patterns" sortgroup="f" text="Instruction Patterns" target="help/topics/Search/Search_Instruction_Patterns.htm" />
<tocdef id="Address Tables" sortgroup="g" text="Address Tables" target="help/topics/Search/Search_for_AddressTables.htm" />
<tocdef id="Direct References" sortgroup="h" text="Direct References" target="help/topics/Search/Search_for_DirectReferences.htm" />
<tocdef id="Search Results Window" sortgroup="i" text="Query Results Window" target="help/topics/Search/Query_Results_Dialog.htm" />
</tocdef>
<tocdef id="DWARF External Debug Files" sortgroup="q" text="DWARF External Debug Files" target="help/topics/DWARFExternalDebugFilesPlugin/DWARFExternalDebugFilesPlugin.html" >

View file

@ -597,6 +597,7 @@
</P>
</BLOCKQUOTE>
<DIV align="left">
<TABLE border="0" width="100%">
<TBODY>
@ -608,6 +609,14 @@
</TBODY>
</TABLE>
</DIV>
<H3><A name="Navigation"></A><IMG alt="" src="images/locationIn.gif">&nbsp;Auto Updating Selection
by Location</H3>
<BLOCKQUOTE>
<P>The <IMG alt="" src="images/locationIn.gif"> &nbsp; button controls whether a memory
block is selected in the Memory Map table when the global program location changes such
as when you click in the CodeBrowser, Byte Viewer, or Decompiler.&nbsp;</P>
</BLOCKQUOTE>
<P class="providedbyplugin">Provided by: <I>Memory Map</I> Plugin<BR>
&nbsp;</P>

View file

@ -0,0 +1,478 @@
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<HTML>
<HEAD>
<META name="generator" content=
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
<TITLE>Search And Replace</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
<META name="generator" content="Microsoft FrontPage 4.0">
</HEAD>
<BODY lang="EN-US">
<BLOCKQUOTE>
<A name="Search_And_Replace"></A>
<H1>Search And Replace</H1>
<P>The search and replace feature allows to users to search for specific text sequences in
various Ghidra elements and replace that text sequence with a different text sequence. Using
this feature, many different elements in Ghidra can be renamed including labels, functions,
namespaces, parameters, datatypes, field elements, enum values, and others. This feature can
also be used to change comments placed on items such as instructions or data, structure field
element, or enum values.</P>
<P>By default, the search matches the text anywhere in an element ("contains"), but it has
full <A href="Search_Formats.htm#RegularExpressions">regular expression</A> support where
users can easily perform a "starts with" or "ends with" search. Regular expression capture
groups are also supported which allows for complex replacing of disjoint strings. See the <A
href="#Examples">examples</A> section below for details.</P>
<P>To initiate a search and replace operation, select <B>Search</B> <IMG alt="" border="0"
src="help/shared/arrow.gif"><B>Search and Replace</B> from the main tool menu.</P>
<H2>Search and Replace Dialog</H2>
<BLOCKQUOTE>
<P>The Search and Replace Dialog provides controls and options for performing and search
and replace operation.</P>
</BLOCKQUOTE>
<P align="center"><IMG src="images/SearchAndReplaceDialog.png" border="0" alt=""> &nbsp;</P>
<P align="center"><I>Search and Replace Dialog</I></P>
<BLOCKQUOTE>
<H3>Find</H3>
<BLOCKQUOTE>
<P>This is a text field for entering the text to be searched for in the selected Ghidra
elements. Elements are either the names of things such as labels, functions, datatypes,
or a comment associated with some item such as an instruction comment or structure field
comment.</P>
<P>Ghidra will find all the elements that contain the given text. To perform a "starts
with" or "ends with" search, you must enter a regular expression here and select the
regular expression option below.</P>
<P>There is also a drop down list of recent searches for this Ghidra session.</P>
</BLOCKQUOTE>
<H3>Replace</H3>
<BLOCKQUOTE>
<P>This is a text field for entering the replacement text. This text will be used to
replace the text that matched the search text. For example, if a function was named
"startCat" and the goal was to change it to "startDog", you would enter "Cat" in the
Find field and "Dog" in this field.</P>
<P>This field also include a drop drow of the most recently enter replacement
strings.</P>
</BLOCKQUOTE>
<H3>Options</H3>
<BLOCKQUOTE>
<H4>Regular Expression</H4>
<BLOCKQUOTE>
<P>If selected, the search text will be interpreted as a regular expression. This
allows for complex search and replace operations. See the general section on <A href=
"Search_Formats.htm#RegularExpressions">regular expressions</A> for more information. If
you want to anything other than a "contains" search, you must use a regular expression.
See below for examples on how to do this.</P>
</BLOCKQUOTE>
<H4>Case Sensitive</H4>
<BLOCKQUOTE>
<P>If selected, the entered search text must match exactly. Otherwise, the entered
search text can match regardless of the case of the search text or the target text.</P>
</BLOCKQUOTE>
<H4>Whole Word</H4>
<BLOCKQUOTE>
<P>If selected, the target text must be an entire word. In other words it must be
surrounded by white space or must be the first or last word in a word sequence. So,
when applied to renaming elements, the search text must match the entire name of the
element since element names cannot contain whitespace. But for comments, the search
text must entirely match one word withing the comment.</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H3>Search For</H3>
<BLOCKQUOTE>
<P>This section contains a group of checkboxes used to turn on or off the type of Ghidra
elements to search. There are also buttons for selecting and deselecting all the element
checkboxes. At least one checkbox must be selected to perform a search.</P>
<UL>
<LI><B>Classes</B> - Search the names of classes.</LI>
<LI><B>Comments</B> - Search instruction or data comments. This includes pre-comments,
plate comments, end of line comments, post-comments, and repeatable comments.</LI>
<LI><B>Datatype Categories</B> - Search for the names of categories in the data types
tree.</LI>
<LI><B>Datatype Comments</B> - Search comments associated with datatypes. This includes
descriptions on enums and structures, datatype field comments, and enum value
comments.</LI>
<LI><B>Datatype Fields</B> - Search the names of structure or union field elements.</LI>
<LI><B>Datatypes</B> - Search the names of any nameable datatype (i.e., does
not search built-in datatypes such as byte, word, string, etc.)</LI>
<LI><B>Enum Values</B> - Search the names of enum values.</LI>
<LI><B>Functions</B> - Search the names of functions. (Note: This search does not
include external function names.)</LI>
<LI><B>Labels</B> - Search the names of labels. (Note: This search does not include
external labels.)</LI>
<LI><B>Local Variables</B> - Search the names of function local variables. (Note: This
does not include local variables derived by the decompiler that haven't been committed
to the database.)</LI>
<LI><B>Memory Blocks</B> - Search the names of Memory Blocks.</LI>
<LI><B>Namespaces</B> - Search the names of namespaces.</LI>
<LI><B>Parameters</B> - Search the names of function parameters.</LI>
<LI><B>Program Trees</B> - Search the names of modules and fragments defined in program
trees.</LI>
</UL>
</BLOCKQUOTE>
</BLOCKQUOTE>
<A name="Search_And_Replace_Results"></A>
<H2>Results Window</H2>
<BLOCKQUOTE>
<P>After initiating a search and replace action, a results window will appear containing a
table showing each search match as an entry in the table. At this point, no changes have
been made to the program. This provides an opportunity to review the pending changes before
they are applied. The changes can now be applied all at once or individually.</P>
</BLOCKQUOTE>
</BLOCKQUOTE>
<P align="center"><IMG src="images/SearchAndReplaceResults.png" border="0" alt=""> &nbsp;</P>
<P align="center"><I>Search and Replace Results Window</I></P>
<BLOCKQUOTE>
<BLOCKQUOTE>
<H3>Table information</H3>
<BLOCKQUOTE>
<P>Each entry in the table represents one changes that can be applied.</P>
<H4>Standard Columns</H4>
<UL>
<LI><B>Original</B> - This column displays the original value of the matched
element.</LI>
<LI><B>Preview</B> - This column displays a preview of the value if this change is
applied.</LI>
<LI><B>Action</B> - The change to be applied. (either Rename for names or Update for
comments changes.)</LI>
<LI><B>Type</B> - This column displays the type of element being changed (label,
function, comment, etc.)</LI>
<LI><B>Status</B> - The icon displayed in this column indicates the status of this
change.</LI>
</UL>
<H4>Status Icons</H4>
<BLOCKQUOTE>
<P>The status column will have one of the following icons to indicate item's
status:</P>
</BLOCKQUOTE>
<UL>
<LI><B>Blank</B> - The change has not been applied.</LI>
<LI><IMG alt="" src="Icons.WARNING_ICON"> - The change has some associated warning.
Hover on the status to get a detailed message of the issue.</LI>
<LI><IMG alt="" src="Icons.ERROR_ICON"> - The change can't be applied. This status can
appear either before or after an attempt to apply has been made. Hover on the status
for more information.</LI>
<LI><IMG alt="" src="icon.base.plugin.quickfix.done"> - The changes has been applied.</LI>
</UL>
<H4>Optional Columns</H4>
<UL>
<LI>Current - Displays the current value of the element.</LI>
<LI>Address - Displays the elements address if applicable, blank otherwise</LI>
<LI>Path - Displays any path associated with the element, if applicable. The type of
path varies greatly with the element type.</LI>
</UL>
<H4>Path Column Information</H4>
<BLOCKQUOTE>
<P>The Path column shows different path information depending on the element type:</P>
</BLOCKQUOTE>
<UL>
<LI>Classes - the namespace path.</LI>
<LI>Datatype Categories - the parent category path.</LI>
<LI>Datatype Comments - the parent category path.</LI>
<LI>Datatype Names - the parent category path.</LI>
<LI>Enum Values - the category path of the enum.</LI>
<LI>Field Names - the category path of the structure or union.</LI>
<LI>Functions - the namespace path.</LI>
<LI>Labels - the namespace path.</LI>
<LI>Local Variables - the namespace path.</LI>
<LI>Namespaces - the parent namespace path.</LI>
<LI>Parameters - the namespace path.</LI>
<LI>Program Trees - the program tree module path</LI>
</UL>
</BLOCKQUOTE>
<H3>Applying Changes</H3>
<BLOCKQUOTE>
<P>Changes can be applied in bulk or individually.</P>
<H4>Apply All Button</H4>
<BLOCKQUOTE>
<P>Press this button to apply all items in the table, regardless of what is
selected.</P>
</BLOCKQUOTE>
<A name="Apply_Selected"></A>
<H4>Apply Selected Action</H4>
<BLOCKQUOTE>
<P>Press the <IMG alt="" src="icon.base.plugin.quickfix.done"> toolbar button or use the
popup action <B>Execute Selected Action(s)</B> to apply just the selected entries in
the table. If only one item is selected when this this is done, the selected item will
move to the next item in the table to facilitate a one at a time workflow.</P>
</BLOCKQUOTE>
<A name="Auto_Delete"></A>
<P><IMG alt="" border="0" src="help/shared/note.png"> There is also a popup toggle action
to turn on an option to auto delete applied entries from the table.</P>
</BLOCKQUOTE>
<H3>Navigation</H3>
<BLOCKQUOTE>
<P>If the <IMG alt="" src="Icons.NAVIGATE_ON_INCOMING_EVENT_ICON"> toolbar button is
selected, selecting items in the table will attempt to navigate to that item in the tool
if possible.</P>
<P>Double clicking (or pressing return key) will also attempt to navigate the tool to the
selected item. In addition, if the item is related to a datatype, an editor for that
datatype will be shown.</P>
</BLOCKQUOTE><A name="Examples"></A>
<H2>Search Examples</H2>
<BLOCKQUOTE>
<H3>Basic Searches</H3>
<BLOCKQUOTE>
<P>Without using regular expressions, you can find matches that contain the search text
or fully match the search text by turning on the "whole word" option. However, to
perform a "starts with" or "ends with" search, you must use a regular expression. Also,
you can do advanced match and replace using regular expressions capture groups.</P>
<P>The following examples assume we are trying to replace label names and we have the
following labels in our program:</P>
<UL>
<LI>Apple</LI>
<LI>myApple</LI>
<LI>AppleJuice</LI>
</UL>
<BLOCKQUOTE>
<TABLE border="1" cellspacing="2" cellpadding="6">
<TBODY>
<TR>
<TH valign="top" bgcolor="#c0c0c0" width="300">Search Type<BR>
</TH>
<TH valign="top" bgcolor="#c0c0c0" width="5">RegEx<BR>
</TH>
<TH valign="top" bgcolor="#c0c0c0" width="5">Whole Word<BR>
</TH>
<TH valign="top" bgcolor="#c0c0c0" width="10">Search For<BR>
</TH>
<TH valign="top" bgcolor="#c0c0c0" width="10">Replace With<BR>
</TH>
<TH valign="top" bgcolor="#c0c0c0" width="10">Matches<BR>
</TH>
<TH valign="top" bgcolor="#c0c0c0" width="10">Results<BR>
</TH>
</TR>
<TR>
<TD><B>Contains</B></TD>
<TD>Off</TD>
<TD>Off</TD>
<TD>Apple</TD>
<TD>Pear</TD>
<TD>Apple, myApple, AppleJuice</TD>
<TD>Pear, myPear, PearJuice</TD>
</TR>
<TR>
<TD><B>Matches Fully</B></TD>
<TD>Off</TD>
<TD>On</TD>
<TD>Apple</TD>
<TD>Pear</TD>
<TD>Apple</TD>
<TD>Pear</TD>
</TR>
<TR>
<TD><B>Starts With</B></TD>
<TD>On</TD>
<TD>N/A</TD>
<TD>^Apple</TD>
<TD>Pear</TD>
<TD>Apple, AppleJuice</TD>
<TD>Pear, PearJuice</TD>
</TR>
<TR>
<TD><B>Ends With</B></TD>
<TD>On</TD>
<TD>N/A</TD>
<TD>Apple$</TD>
<TD>Pear</TD>
<TD>Apple, MyApple</TD>
<TD>Pear, MyPear</TD>
</TR>
</TBODY>
</TABLE>
</BLOCKQUOTE>
</BLOCKQUOTE>
<H3>Advanced RegEx Searches</H3>
<BLOCKQUOTE>
<P>Regular Expression can do many advanced types of matching and replacing which is
beyond the scope of this document. However, a simple example using capture groups will
be given as follows:</P>
<BLOCKQUOTE>
<TABLE border="1" cellspacing="2" cellpadding="6">
<TBODY>
<TR>
<TH valign="top" bgcolor="#c0c0c0" width="10">Search For<BR>
</TH>
<TH valign="top" bgcolor="#c0c0c0" width="10">Replace With<BR>
</TH>
<TH valign="top" bgcolor="#c0c0c0" width="10">Matches<BR>
</TH>
<TH valign="top" bgcolor="#c0c0c0" width="10">Results<BR>
</TH>
</TR>
<TR>
<TD>Red(.*)Blue(.*)</TD>
<TD>Green$1Purple$2</TD>
<TD>RedApplesBlueBerries</TD>
<TD>GreenApplesPurpleBerries</TD>
</TR>
</TBODY>
</TABLE>
</BLOCKQUOTE>
</BLOCKQUOTE>
</BLOCKQUOTE>
</BLOCKQUOTE>
</BLOCKQUOTE>
<P class="providedbyplugin">Provided by: <I>SearchAndReplacePlugin</I><BR>
</P>
<P class="relatedtopic">Related Topics:</P>
<UL>
<LI>
<P class="relatedtopic"><A href=
"help/topics/Search/Searching.htm">Searching</A></P>
</LI>
<LI>
<P class="relatedtopic"><A href=
"help/topics/Search/Search_Program_Text.htm">Search Program Text</A></P>
</LI>
</UL><BR>
<BR>
<BR>
<BR>
<BR>
<BR>
</BODY>
</HTML>

View file

@ -17,9 +17,12 @@
<P>Ghidra offers a variety of searching capabilities.&nbsp; The <I>Search Program Memory</I>
feature performs fast searching for byte patterns in program memory.&nbsp; The <I><A href=
"Search_Program_Text.htm">Search Program Text</A></I> feature searches for text strings in
various parts of the listing such as comments, labels, mnemonics, and operands.&nbsp; The <I><A
href="Search_for_Strings.htm">Search For Strings</A></I> feature automatically finds potential
ascii strings within the program memory.</P>
various parts of the listing such as comments, labels, mnemonics, and operands.&nbsp; The
<I><A href="SearchAndReplace.htm">Search and Replace</A></I> features allows for globally
searching and replacing names or comments on many different types of program elements such as
Labels, Functions, Datatypes, Enum Values, and and many others. The
<I><A href="Search_for_Strings.htm">Search For Strings</A></I> feature
automatically finds potential ascii strings within the program memory.</P>
<P class="relatedtopic">Related Topics:</P>
@ -28,6 +31,8 @@
<LI><A href="Search_Program_Text.htm">Search Program Text</A></LI>
<LI><A href="SearchAndReplace.htm">Search And Replace</A></LI>
<LI><A href="Search_for_Strings.htm">Search For Strings</A></LI>
<LI><A href="Search_for_AddressTables.htm">Search For Address Tables</A></LI>

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View file

@ -55,7 +55,7 @@ public class DefaultDataTypeManagerService extends DefaultDataTypeArchiveService
}
@Override
public void edit(Structure structure, String fieldName) {
public void edit(Composite compposite, String fieldName) {
throw new UnsupportedOperationException();
}
@ -100,6 +100,11 @@ public class DefaultDataTypeManagerService extends DefaultDataTypeArchiveService
throw new UnsupportedOperationException();
}
@Override
public void setCategorySelected(Category category) {
throw new UnsupportedOperationException();
}
@Override
public List<DataType> getSelectedDatatypes() {
throw new UnsupportedOperationException();

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -98,7 +97,7 @@ public class CommentsActionFactory {
if (!isCommentSupported(loc)) {
return false;
}
return CommentType.isCommentAllowed(getCodeUnit(actionContext), loc);
return CommentTypeUtils.isCommentAllowed(getCodeUnit(actionContext), loc);
}
@Override
@ -132,7 +131,7 @@ public class CommentsActionFactory {
@Override
protected int getEditCommentType(ActionContext context) {
CodeUnit cu = getCodeUnit(context);
return CommentType.getCommentType(cu, getLocationForContext(context), CodeUnit.NO_COMMENT);
return CommentTypeUtils.getCommentType(cu, getLocationForContext(context), CodeUnit.NO_COMMENT);
}
}
}

View file

@ -129,7 +129,7 @@ public class CommentsPlugin extends Plugin implements OptionsChangeListener {
* @param loc the {@link ProgramLocation} for which to delete the comment
*/
void deleteComments(Program program, ProgramLocation loc) {
int commentType = CommentType.getCommentType(null, loc, CodeUnit.EOL_COMMENT);
int commentType = CommentTypeUtils.getCommentType(null, loc, CodeUnit.EOL_COMMENT);
SetCommentCmd cmd = new SetCommentCmd(loc.getByteAddress(), commentType, null);
tool.execute(cmd, program);
}
@ -138,7 +138,7 @@ public class CommentsPlugin extends Plugin implements OptionsChangeListener {
if (codeUnit == null) {
return false;
}
int commentType = CommentType.getCommentType(null, loc, CodeUnit.NO_COMMENT);
int commentType = CommentTypeUtils.getCommentType(null, loc, CodeUnit.NO_COMMENT);
return (commentType != CodeUnit.NO_COMMENT && codeUnit.getComment(commentType) != null);
}
@ -215,7 +215,7 @@ public class CommentsPlugin extends Plugin implements OptionsChangeListener {
else {
historyAction.getPopupMenuData().setMenuPath(HISTORY_MENUPATH);
}
historyAction.setEnabled(CommentType.isCommentAllowed(context.getCodeUnit(), loc));
historyAction.setEnabled(CommentTypeUtils.isCommentAllowed(context.getCodeUnit(), loc));
return true;
}
};
@ -227,7 +227,7 @@ public class CommentsPlugin extends Plugin implements OptionsChangeListener {
private void showCommentHistory(ListingActionContext context) {
CodeUnit cu = context.getCodeUnit();
ProgramLocation loc = context.getLocation();
int commentType = CommentType.getCommentType(null, loc, CodeUnit.EOL_COMMENT);
int commentType = CommentTypeUtils.getCommentType(null, loc, CodeUnit.EOL_COMMENT);
CommentHistoryDialog historyDialog = new CommentHistoryDialog(cu, commentType);
tool.showDialog(historyDialog, context.getComponentProvider());
}

View file

@ -214,6 +214,12 @@ public abstract class CompositeEditorProvider extends ComponentProviderAdapter
return new DefaultActionContext(this, null);
}
public void selectField(String fieldName) {
if (fieldName != null) {
editorPanel.selectField(fieldName);
}
}
@Override
public HelpLocation getHelpLocation() {
return new HelpLocation(getHelpTopic(), getHelpName());

View file

@ -97,12 +97,6 @@ public class StructureEditorProvider extends CompositeEditorProvider {
return "DataTypeEditors";
}
public void selectField(String fieldName) {
if (fieldName != null) {
editorPanel.selectField(fieldName);
}
}
@Override
protected void closeDependentEditors() {
if (bitFieldEditor != null && bitFieldEditor.isVisible()) {

View file

@ -498,8 +498,8 @@ public class DataTypeManagerPlugin extends ProgramPlugin
}
@Override
public void edit(Structure dt, String fieldName) {
editorManager.edit(dt, fieldName);
public void edit(Composite composite, String fieldName) {
editorManager.edit(composite, fieldName);
}
@Override
@ -620,6 +620,15 @@ public class DataTypeManagerPlugin extends ProgramPlugin
}
}
@Override
public void setCategorySelected(Category category) {
if (provider.isVisible()) {
// this is a service method, ensure it is on the Swing thread, since it interacts with
// Swing components
Swing.runIfSwingOrRunLater(() -> provider.setCategorySelected(category));
}
}
@Override
public List<DataType> getSelectedDatatypes() {
if (provider.isVisible()) {

View file

@ -781,6 +781,45 @@ public class DataTypesProvider extends ComponentProviderAdapter {
contextChanged();
}
/**
* Selects the given data type category in the tree of data types. This method will cause the
* data type tree to come to the front, scroll to the category and then to select the tree
* node that represents the category. If the category is null, the selection is cleared.
*
* @param category the category to select; may be null
*/
public void setCategorySelected(Category category) {
DataTypeArchiveGTree gTree = getGTree();
if (category == null) { // clear the selection
gTree.clearSelectionPaths();
return;
}
DataTypeManager dataTypeManager = category.getDataTypeManager();
if (dataTypeManager == null) {
return;
}
ArchiveRootNode rootNode = (ArchiveRootNode) gTree.getViewRoot();
ArchiveNode archiveNode = rootNode.getNodeForManager(dataTypeManager);
if (archiveNode == null) {
plugin.setStatus("Cannot find archive '" + dataTypeManager.getName() + "'. It may " +
"be filtered out of view or may have been closed (Data Type Manager)");
return;
}
// Note: passing 'true' here forces a load if needed. This could be slow for programs
// with many types. If this locks the UI, then put this work into a GTreeTask.
CategoryNode node = archiveNode.findCategoryNode(category, true);
if (node == null) {
return;
}
gTree.setSelectedNode(node);
gTree.scrollPathToVisible(node.getTreePath());
contextChanged();
}
/**
* Returns a list of all the data types selected in the data types tree
* @return a list of all the data types selected in the data types tree

View file

@ -126,20 +126,24 @@ public class DataTypeEditorManager implements EditorListener {
* Displays a data type editor for editing the given Structure. If the structure is already
* being edited then it is brought to the front. Otherwise, a new editor is created and
* displayed.
* @param structure the structure.
* @param composite the structure.
* @param fieldName the optional name of the field to select in the editor.
*/
public void edit(Structure structure, String fieldName) {
public void edit(Composite composite, String fieldName) {
StructureEditorProvider editor = (StructureEditorProvider) getEditor(structure);
CompositeEditorProvider editor = (CompositeEditorProvider) getEditor(composite);
if (editor != null) {
reuseExistingEditor(structure);
reuseExistingEditor(composite);
editor.selectField(fieldName);
return;
}
editor = new StructureEditorProvider(plugin, structure,
showStructureNumbersInHex());
if (composite instanceof Union) {
editor = new UnionEditorProvider(plugin, (Union) composite, showUnionNumbersInHex());
}
else if (composite instanceof Structure) {
editor = new StructureEditorProvider(plugin, (Structure) composite,
showStructureNumbersInHex());
}
editor.selectField(fieldName);
editor.addEditorListener(this);
editorList.add(editor);

View file

@ -129,6 +129,11 @@ public class MemoryMapPlugin extends ProgramPlugin implements DomainObjectListen
provider.setProgram(null);
}
@Override
protected void locationChanged(ProgramLocation location) {
provider.locationChanged(location);
}
MemoryMapManager getMemoryMapManager() {
return memManager;
}

View file

@ -25,9 +25,9 @@ import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.ToolBarData;
import docking.action.*;
import docking.action.builder.ActionBuilder;
import docking.action.builder.ToggleActionBuilder;
import docking.widgets.OptionDialog;
import docking.widgets.table.*;
import docking.widgets.textfield.GValidatedTextField.MaxLengthField;
@ -40,12 +40,13 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.address.OverlayAddressSpace;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.*;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.program.util.ProgramLocation;
import ghidra.util.*;
import ghidra.util.exception.UsrException;
import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel;
import ghidra.util.table.actions.MakeProgramSelectionAction;
import resources.Icons;
/**
* Provider for the memory map Component.
@ -72,6 +73,9 @@ class MemoryMapProvider extends ComponentProviderAdapter {
private Program program;
private MemoryMapManager memManager;
private boolean followLocationChanges;
private ToggleDockingAction toggleNavigateAction;
MemoryMapProvider(MemoryMapPlugin plugin) {
super(plugin.getTool(), "Memory Map", plugin.getName(), ProgramActionContext.class);
this.plugin = plugin;
@ -104,6 +108,17 @@ class MemoryMapProvider extends ComponentProviderAdapter {
return new ProgramActionContext(this, program, table);
}
void locationChanged(ProgramLocation location) {
if (!followLocationChanges || location == null || location.getAddress() == null) {
return;
}
Memory memory = program.getMemory();
MemoryBlock block = memory.getBlock(location.getAddress());
if (block != null) {
filterPanel.setSelectedItem(block);
}
}
void setStatusText(String msg) {
tool.setStatusInfo(msg);
}
@ -320,6 +335,15 @@ class MemoryMapProvider extends ComponentProviderAdapter {
MakeProgramSelectionAction action = new MakeProgramSelectionAction(plugin, table);
action.getToolBarData().setToolBarGroup("B"); // the other actions are in group 'A'
tool.addLocalAction(this, action);
toggleNavigateAction = new ToggleActionBuilder("Memory Map Navigation", plugin.getName())
.toolBarIcon(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON)
.selected(false)
.helpLocation(new HelpLocation("MemoryMapPlugin", "Navigation"))
.description(HTMLUtilities.toHTML("Toggle <b>on</b> means to select the block" +
" that contains the current location"))
.onAction(c -> followLocationChanges = toggleNavigateAction.isSelected())
.buildAndInstallLocal(this);
}
private boolean checkExclusiveAccess() {

View file

@ -52,6 +52,10 @@ import ghidra.util.task.TaskMonitor;
* that generated it so that the search can be constrained to specific fields such as the
* label or comment field.
*
* <p> NOTE: This only searches defined instructions or data, which is possibly
* a mistake since this is more of a WYSIWYG search. However, searching undefined code units could
* make this slow search even more so.
*
*/
class ListingDisplaySearcher implements Searcher {

View file

@ -39,10 +39,11 @@ import ghidra.program.util.ProgramLocation;
"in a tree hierarchy. All symbols (except for the global namespace symbol)" +
" have a parent symbol. From the tree, symbols can be renamed, deleted, or " +
"reorganized.",
eventsConsumed = { ProgramActivatedPluginEvent.class, ProgramLocationPluginEvent.class, ProgramClosedPluginEvent.class }
eventsConsumed = { ProgramActivatedPluginEvent.class, ProgramLocationPluginEvent.class, ProgramClosedPluginEvent.class },
servicesProvided = { SymbolTreeService.class }
)
//@formatter:on
public class SymbolTreePlugin extends Plugin {
public class SymbolTreePlugin extends Plugin implements SymbolTreeService {
public static final String PLUGIN_NAME = "SymbolTreePlugin";
@ -185,4 +186,10 @@ public class SymbolTreePlugin extends Plugin {
tool.showComponentProvider(newProvider, true);
return newProvider;
}
@Override
public void selectSymbol(Symbol symbol) {
connectedProvider.selectSymbol(symbol);
}
}

View file

@ -552,6 +552,10 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
return;
}
selectSymbol(symbol);
}
public void selectSymbol(Symbol symbol) {
SymbolTreeRootNode rootNode = (SymbolTreeRootNode) tree.getViewRoot();
tree.runTask(new SearchTask(tree, rootNode, symbol));
}
@ -805,4 +809,5 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
}
}
}
}

View file

@ -0,0 +1,29 @@
/* ###
* 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.symboltree;
import ghidra.program.model.symbol.Symbol;
/**
* Service to interact with the Symbol Tree.
*/
public interface SymbolTreeService {
/**
* Selects the given symbol in the symbol tree.
* @param symbol the symbol to select in the symbol tree
*/
public void selectSymbol(Symbol symbol);
}

View file

@ -96,14 +96,14 @@ public interface DataTypeManagerService extends DataTypeQueryService, DataTypeAr
public void edit(DataType dt);
/**
* Pop up an editor window for the given structure.
* Pop up an editor window for the given structure or union
*
* @param structure the structure
* @param fieldName the optional structure field name to select in the editor window
* @param composite the structure or union
* @param fieldName the optional field name to select in the editor window
* @throws IllegalArgumentException if the given has not been resolved by a DataTypeManager;
* in other words, if {@link DataType#getDataTypeManager()} returns null
*/
public void edit(Structure structure, String fieldName);
public void edit(Composite composite, String fieldName);
/**
* Selects the given data type in the display of data types. A null <code>dataType</code>
@ -113,6 +113,15 @@ public interface DataTypeManagerService extends DataTypeQueryService, DataTypeAr
*/
public void setDataTypeSelected(DataType dataType);
/**
* Selects the given data type category in the tree of data types. This method will cause the
* data type tree to come to the front, scroll to the category and then to select the tree
* node that represents the category. If the category is null, the selection is cleared.
*
* @param category the category to select; may be null
*/
public void setCategorySelected(Category category);
/**
* Returns the list of data types that are currently selected in the data types tree
* @return the list of data types that are currently selected in the data types tree

View file

@ -28,7 +28,10 @@ import ghidra.program.util.GroupPath;
*
*
*/
@ServiceInfo(defaultProvider = ProgramTreePlugin.class, description = "Get the currently viewed address set")
@ServiceInfo(
defaultProvider = ProgramTreePlugin.class,
description = "Get the currently viewed address set"
)
public interface ProgramTreeService {
/**
@ -51,7 +54,7 @@ public interface ProgramTreeService {
/**
* Set the selection to the given group paths.
* @param gps paths to select
* @param groupPaths paths to select
*/
public void setGroupSelection(GroupPath[] gps);
public void setGroupSelection(GroupPath... groupPaths);
}

View file

@ -0,0 +1,356 @@
/* ###
* 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.features.base.quickfix;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.table.TableModel;
import docking.*;
import docking.action.ToggleDockingAction;
import docking.action.builder.ActionBuilder;
import docking.action.builder.ToggleActionBuilder;
import docking.widgets.table.GTable;
import docking.widgets.table.threaded.ThreadedTableModel;
import generic.theme.GIcon;
import ghidra.app.nav.Navigatable;
import ghidra.app.services.GoToService;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramTask;
import ghidra.util.HelpLocation;
import ghidra.util.table.*;
import ghidra.util.table.actions.DeleteTableRowAction;
import ghidra.util.table.actions.MakeProgramSelectionAction;
import ghidra.util.task.TaskLauncher;
import ghidra.util.task.TaskMonitor;
/**
* Component Provider for displaying lists of {@link QuickFix}s and the actions to execute them
* in bulk or individually.
*/
public class QuckFixTableProvider extends ComponentProvider {
private static final Icon EXECUTE_ICON = new GIcon("icon.base.plugin.quickfix.done");
private JComponent component;
private QuickFixTableModel tableModel;
private GhidraThreadedTablePanel<QuickFix> threadedPanel;
private GhidraTableFilterPanel<QuickFix> tableFilterPanel;
private GhidraTable table;
private ToggleDockingAction toggleAutoDeleteAction;
private boolean autoDelete;
public QuckFixTableProvider(PluginTool tool, String title, String owner, Program program,
TableDataLoader<QuickFix> loader) {
super(tool, title, owner);
setIcon(new GIcon("icon.plugin.table.service"));
setTransient();
setTitle(title);
tableModel = new QuickFixTableModel(program, title, tool, loader);
tableModel.addInitialLoadListener(b -> tableLoaded(b, loader));
component = buildMainPanel();
createActions(owner);
tableModel.addTableModelListener(e -> tableDataChanged());
}
protected void tableLoaded(boolean wasCancelled, TableDataLoader<QuickFix> loader) {
// used by subclasses
}
private void updateSubTitle() {
StringBuilder builder = new StringBuilder();
builder.append(" ");
int count = tableModel.getUnfilteredRowCount();
if (count > 0) {
builder.append("(");
builder.append(count);
builder.append(count == 1 ? " item)" : " items)");
}
setSubTitle(builder.toString());
}
protected void createActions(String owner) {
new ActionBuilder("Apply Action", owner)
.popupMenuPath("Apply Selected Items(s)")
.popupMenuIcon(EXECUTE_ICON)
.popupMenuGroup("aaa")
.toolBarIcon(EXECUTE_ICON)
.description("Applies the selected items")
.helpLocation(new HelpLocation("Search", "Apply_Selected"))
.keyBinding("ctrl e")
.withContext(QuickFixActionContext.class)
.enabledWhen(c -> c.getSelectedRowCount() > 0)
.onAction(this::applySelectedItems)
.buildAndInstallLocal(this);
toggleAutoDeleteAction = new ToggleActionBuilder("Toggle Auto Delete", owner)
.popupMenuPath("Auto Delete Completed Items")
.popupMenuGroup("settings")
.helpLocation(new HelpLocation("Search", "Auto_Delete"))
.description("If on, automatically remove completed items from the list")
.onAction(this::toggleAutoDelete)
.buildAndInstallLocal(this);
addLocalAction(new SelectionNavigationAction(owner, table));
GoToService service = dockingTool.getService(GoToService.class);
if (service != null) {
Navigatable navigatable = service.getDefaultNavigatable();
addLocalAction(new MakeProgramSelectionAction(navigatable, owner, table, "bbb"));
}
DeleteTableRowAction deleteAction = new DeleteTableRowAction(table, owner, "bbb") {
@Override
public void actionPerformed(ActionContext context) {
super.actionPerformed(context);
updateSubTitle();
}
};
addLocalAction(deleteAction);
}
@Override
public ActionContext getActionContext(MouseEvent event) {
return new QuickFixActionContext();
}
private void tableDataChanged() {
updateTitle();
}
private void updateTitle() {
int rowCount = tableModel.getRowCount();
int filteredRowCount = tableFilterPanel.getRowCount();
setSubTitle("(" + filteredRowCount + " of " + rowCount + ")");
}
@Override
public void closeComponent() {
super.closeComponent();
tableFilterPanel.dispose();
}
@Override
public JComponent getComponent() {
return component;
}
protected JPanel buildMainPanel() {
JPanel panel = new JPanel(new BorderLayout());
threadedPanel = new GhidraThreadedTablePanel<>(tableModel) {
protected GTable createTable(ThreadedTableModel<QuickFix, ?> model) {
return new QuickFixGhidraTable(model);
}
};
table = threadedPanel.getTable();
table.getSelectionModel().addListSelectionListener(e -> {
if (e.getValueIsAdjusting()) {
return;
}
dockingTool.contextChanged(QuckFixTableProvider.this);
});
table.setActionsEnabled(true);
table.installNavigation(dockingTool);
panel.add(threadedPanel, BorderLayout.CENTER);
panel.add(createFilterFieldPanel(), BorderLayout.SOUTH);
panel.setPreferredSize(new Dimension(1000, 600));
return panel;
}
private JPanel createFilterFieldPanel() {
tableFilterPanel = new GhidraTableFilterPanel<>(table, tableModel);
return tableFilterPanel;
}
public boolean isBusy(TableModel model) {
if (!(model instanceof ThreadedTableModel)) {
return false;
}
ThreadedTableModel<?, ?> threadedModel = (ThreadedTableModel<?, ?>) model;
if (threadedModel.isBusy()) {
return true;
}
return false;
}
private void applySelectedItems(QuickFixActionContext context) {
List<QuickFix> selectedItems = tableFilterPanel.getSelectedItems();
int nextIndex = selectedItems.size() == 1 ? table.getSelectedRow() : -1;
applyItems(selectedItems);
if (nextIndex >= 0) {
int index = nextIndex + 1;
if (index < table.getRowCount()) {
table.selectRow(nextIndex + 1);
}
}
if (autoDelete) {
removeCompletedItems(selectedItems);
}
tableModel.fireTableDataChanged();
}
private void toggleAutoDelete(ActionContext context) {
autoDelete = toggleAutoDeleteAction.isSelected();
if (autoDelete) {
removeCompletedItems(tableModel.getModelData());
}
}
private void removeCompletedItems(List<QuickFix> items) {
List<QuickFix> toDelete = new ArrayList<>();
for (QuickFix item : items) {
if (item.getStatus() == QuickFixStatus.DONE) {
toDelete.add(item);
}
}
for (QuickFix quickFix : toDelete) {
tableModel.removeObject(quickFix);
}
}
private void applyItems(List<QuickFix> quickFixList) {
Program program = tableModel.getProgram();
ProgramTask task = new ApplyItemsTask(program, getTaskTitle(), quickFixList);
TaskLauncher.launch(task);
}
public void executeAll() {
List<QuickFix> allItems = tableModel.getModelData();
applyItems(allItems);
tableModel.fireTableDataChanged();
}
protected String getTaskTitle() {
return "Applying Items";
}
public void programClosed(Program program) {
if (program == tableModel.getProgram()) {
this.closeComponent();
}
}
private static class ApplyItemsTask extends ProgramTask {
private List<QuickFix> quickFixList;
public ApplyItemsTask(Program program, String title, List<QuickFix> quickFixList) {
super(program, title, true, true, true);
this.quickFixList = quickFixList;
}
@Override
protected void doRun(TaskMonitor monitor) {
for (QuickFix quickFix : quickFixList) {
quickFix.performAction();
}
}
}
/**
* Returns the table model.
* @return the table model
*/
public QuickFixTableModel getTableModel() {
return tableModel;
}
/**
* Sets the selected rows in the table
* @param start the index of the first row to select
* @param end the index of the last row to select
*/
public void setSelection(int start, int end) {
table.setRowSelectionInterval(start, end);
}
/**
* Returns the selected row in the table
* @return the selected row in the table
*/
public int getSelectedRow() {
return table.getSelectedRow();
}
/**
* Applies all the selected items.
*/
public void applySelected() {
applySelectedItems(null);
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private class QuickFixActionContext extends DefaultActionContext {
QuickFixActionContext() {
super(QuckFixTableProvider.this, table);
}
public int getSelectedRowCount() {
return table.getSelectedRowCount();
}
}
private class QuickFixGhidraTable extends GhidraTable {
boolean fromSelectionChange = false;
public QuickFixGhidraTable(ThreadedTableModel<QuickFix, ?> model) {
super(model);
}
@Override
public void navigate(int row, int column) {
if (!doSpecialNavigate(row)) {
super.navigate(row, column);
}
}
@Override
protected void navigateOnCurrentSelection(int row, int column) {
fromSelectionChange = true;
try {
super.navigateOnCurrentSelection(row, column);
}
finally {
fromSelectionChange = false;
}
}
private boolean doSpecialNavigate(int row) {
QuickFix quickFix = tableFilterPanel.getRowObject(row);
return quickFix.navigateSpecial(dockingTool, fromSelectionChange);
}
}
}

View file

@ -0,0 +1,231 @@
/* ###
* 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.features.base.quickfix;
import java.util.Map;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
/**
* Generic base class for executable items to be displayed in a table that can be executed in bulk or
* individually.
*/
public abstract class QuickFix {
private long modificationNumber;
private QuickFixStatus status = QuickFixStatus.NONE;
private String statusMessage;
protected final Program program;
protected final String original;
protected final String replacement;
protected String current;
protected QuickFix(Program program, String original, String replacement) {
this.program = program;
this.original = original;
this.replacement = replacement;
this.current = original;
this.modificationNumber = program.getModificationNumber();
}
/**
* Returns the general name of the action to be performed.
* @return the general name of the action to be performed
*/
public abstract String getActionName();
/**
* Returns the type of program element being affected (function, label, comment, etc.)
* @return the type of program element being affected
*/
public abstract String getItemType();
/**
* Returns the address of the affected program element if applicable or null otherwise.
* @return the address of the affected program element if applicable or null otherwise
*/
public abstract Address getAddress();
/**
* Returns a path (the meaning of the path varies with the item type) associated with the
* affected program element if applicable or null otherwise.
* @return a path associated with the affected program if applicable or null otherwise
*/
public abstract String getPath();
/**
* Returns the original value of the affected program element.
* @return the original value of the affected program element.
*/
public String getOriginal() {
return original;
}
/**
* Returns the current value of the affected program element.
* @return the current value of the affected program element.
*/
public final String getCurrent() {
refresh();
return current;
}
protected void refresh() {
if (program.getModificationNumber() == modificationNumber) {
return;
}
modificationNumber = program.getModificationNumber();
// once in an error status, it must stay that way (to distinguish it from the
// "not done" state, otherwise we would clear it when refresh the status)
if (status == QuickFixStatus.ERROR) {
return;
}
current = doGetCurrent();
updateStatus();
}
/**
* Returns a preview of what the affected element will be if this item is applied.
* @return a preview of what the affected element will be if this item is applied
*/
public final String getPreview() {
return replacement;
}
/**
* Executes the primary action of this QuickFix.
*/
public final void performAction() {
if (status == QuickFixStatus.ERROR || status == QuickFixStatus.DONE) {
return;
}
execute();
}
/**
* Returns the current {@link QuickFixStatus} of this item.
* @return the current {@link QuickFixStatus} of this item
*/
public final QuickFixStatus getStatus() {
refresh();
return status;
}
/**
* Returns the current status message of this item.
* @return the current status message of this item
*/
public String getStatusMessage() {
if (statusMessage != null) {
return statusMessage;
}
switch (status) {
case DONE:
return "Applied";
case ERROR:
return "Error";
case NONE:
return "Not Applied";
case WARNING:
return "Warning";
case CHANGED:
return "Target changed externally";
case DELETED:
return "Target no longer exists";
default:
return "";
}
}
/**
* Sets the status of this item
* @param status the new {@link QuickFixStatus} for this item.
*/
public void setStatus(QuickFixStatus status) {
setStatus(status, null);
}
/**
* Sets both the {@link QuickFixStatus} and the status message for this item. Typically, used
* to indicate a warning or error.
* @param status the new QuickFixStatus
* @param message the status message associated with the new status.
*/
public void setStatus(QuickFixStatus status, String message) {
this.status = status;
this.statusMessage = message;
}
public abstract ProgramLocation getProgramLocation();
public Map<String, String> getCustomToolTipData() {
return null;
// do nothing - for subclasses to put specific info into the tooltip
}
/**
* Returns the current value of the item.
* @return the current value of the item
*/
protected abstract String doGetCurrent();
/**
* Executes the action.
*/
protected abstract void execute();
/**
* QuickFix items can override this method if they want to do some special navigation when the
* table selection changes or the user double clicks (or presses <return> key) to navigate.
* @param services the tool service provider
* @param fromSelectionChange true if this call was caused by the table selection changing
* @return true if this item handles the navigation and false if the QuickFix did not
* handle the navigation and the client should attempt to do generic navigation.
*/
protected boolean navigateSpecial(ServiceProvider services, boolean fromSelectionChange) {
return false;
}
private void updateStatus() {
QuickFixStatus newStatus = computeStatus();
if (newStatus != status) {
setStatus(newStatus);
statusChanged(status);
}
}
protected void statusChanged(QuickFixStatus newStatus) {
// do nothing - used by subclasses
}
private QuickFixStatus computeStatus() {
if (current == null) {
return QuickFixStatus.DELETED;
}
if (current.equals(original)) {
return QuickFixStatus.NONE;
}
if (current.equals(replacement)) {
return QuickFixStatus.DONE;
}
return QuickFixStatus.CHANGED;
}
}

View file

@ -0,0 +1,28 @@
/* ###
* 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.features.base.quickfix;
/**
* Enum for the possible status values of a {@link QuickFix}.
*/
public enum QuickFixStatus {
NONE, // The item is unapplied and is ready to be executed
WARNING, // The item is unapplied and has an associated warning
CHANGED, // The item is unapplied, but has changed from its original value
DELETED, // The item's target program element no longer exists
ERROR, // The item can't be applied. This may occur before or after it is applied
DONE // The item has been applied
}

View file

@ -0,0 +1,78 @@
/* ###
* 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.features.base.quickfix;
import java.awt.Component;
import javax.swing.Icon;
import javax.swing.JLabel;
import docking.widgets.table.GTableCellRenderingData;
import generic.theme.GIcon;
import ghidra.docking.settings.Settings;
import ghidra.util.exception.AssertException;
import ghidra.util.table.column.AbstractGhidraColumnRenderer;
import resources.Icons;
/**
* Renderer for the {@link QuickFixStatus} column
*/
public class QuickFixStatusRenderer extends AbstractGhidraColumnRenderer<QuickFixStatus> {
private static final Icon DONE_ICON = new GIcon("icon.base.plugin.quickfix.done");
private static final Icon ERROR_ICON = Icons.ERROR_ICON;
private static final Icon WARNING_ICON = Icons.WARNING_ICON;
private static final Icon DELETE_ICON = Icons.DELETE_ICON;
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
JLabel renderer = (JLabel) super.getTableCellRendererComponent(data);
QuickFixStatus status = (QuickFixStatus) data.getValue();
Icon icon = getIcon(status);
renderer.setIcon(icon);
renderer.setText("");
QuickFix rowObject = (QuickFix) data.getRowObject();
renderer.setToolTipText(rowObject.getStatusMessage());
return renderer;
}
private Icon getIcon(QuickFixStatus status) {
switch (status) {
case DONE:
return DONE_ICON;
case WARNING:
case CHANGED:
return WARNING_ICON;
case ERROR:
return ERROR_ICON;
case DELETED:
return DELETE_ICON;
case NONE:
return null;
default:
throw new AssertException("Unexpected QuickFix status: " + status);
}
}
@Override
public String getFilterString(QuickFixStatus t, Settings settings) {
return t.toString();
}
}

View file

@ -0,0 +1,346 @@
/* ###
* 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.features.base.quickfix;
import java.awt.Component;
import java.util.Map;
import java.util.Map.Entry;
import javax.swing.JLabel;
import docking.widgets.table.*;
import ghidra.docking.settings.Settings;
import ghidra.framework.model.DomainObjectChangedEvent;
import ghidra.framework.model.DomainObjectListener;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.util.HTMLUtilities;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.table.GhidraProgramTableModel;
import ghidra.util.table.column.AbstractGhidraColumnRenderer;
import ghidra.util.table.column.GColumnRenderer;
import ghidra.util.task.SwingUpdateManager;
import ghidra.util.task.TaskMonitor;
/**
* Table model for {@link QuickFix}s
*/
public class QuickFixTableModel extends GhidraProgramTableModel<QuickFix>
implements DomainObjectListener {
private TableDataLoader<QuickFix> tableLoader;
private SwingUpdateManager updateManager = new SwingUpdateManager(1000, this::refreshItems);
private QuickFixRenderer quickFixRenderer = new QuickFixRenderer();
protected QuickFixTableModel(Program program, String modelName, ServiceProvider serviceProvider,
TableDataLoader<QuickFix> loader) {
super(modelName, serviceProvider, program, null);
this.tableLoader = loader;
program.addListener(this);
}
@Override
public void dispose() {
updateManager.dispose();
if (program != null) {
program.removeListener(this);
}
program = null;
super.dispose();
}
@Override
protected void doLoad(Accumulator<QuickFix> accumulator, TaskMonitor monitor)
throws CancelledException {
tableLoader.loadData(accumulator, monitor);
}
@Override
protected TableColumnDescriptor<QuickFix> createTableColumnDescriptor() {
TableColumnDescriptor<QuickFix> descriptor = new TableColumnDescriptor<>();
descriptor.addVisibleColumn(new OriginalValueColumn(), 1, true);
descriptor.addHiddenColumn(new CurrentValueColumn());
descriptor.addVisibleColumn(new PreviewColumn());
descriptor.addVisibleColumn(new ActionColumn());
descriptor.addVisibleColumn(new TypeColumn());
descriptor.addHiddenColumn(new AddressColumn());
descriptor.addHiddenColumn(new PathColumn());
descriptor.addVisibleColumn(new QuickFixStatusColumn());
return descriptor;
}
@Override
public ProgramLocation getProgramLocation(int modelRow, int modelColumn) {
if (modelRow < 0 || modelRow >= filteredData.size()) {
return null;
}
QuickFix quickFix = filteredData.get(modelRow);
return quickFix.getProgramLocation();
}
@Override
public void domainObjectChanged(DomainObjectChangedEvent ev) {
updateManager.update();
}
private void refreshItems() {
fireTableDataChanged();
}
private class QuickFixStatusColumn
extends AbstractDynamicTableColumnStub<QuickFix, QuickFixStatus> {
QuickFixStatusRenderer renderer = new QuickFixStatusRenderer();
@Override
public String getColumnName() {
return "Status";
}
@Override
public int getColumnPreferredWidth() {
return 25;
}
@Override
public QuickFixStatus getValue(QuickFix rowObject, Settings settings,
ServiceProvider sp) throws IllegalArgumentException {
return rowObject.getStatus();
}
@Override
public GColumnRenderer<QuickFixStatus> getColumnRenderer() {
return renderer;
}
}
private class TypeColumn
extends AbstractDynamicTableColumnStub<QuickFix, String> {
@Override
public String getColumnName() {
return "Type";
}
@Override
public int getColumnPreferredWidth() {
return 100;
}
@Override
public String getValue(QuickFix rowObject, Settings settings,
ServiceProvider sp) throws IllegalArgumentException {
return rowObject.getItemType();
}
}
private class ActionColumn
extends AbstractDynamicTableColumnStub<QuickFix, String> {
@Override
public String getColumnName() {
return "Action";
}
@Override
public int getColumnPreferredWidth() {
return 75;
}
@Override
public String getValue(QuickFix rowObject, Settings settings,
ServiceProvider sp) throws IllegalArgumentException {
return rowObject.getActionName();
}
}
private class CurrentValueColumn
extends AbstractDynamicTableColumnStub<QuickFix, String> {
@Override
public String getColumnName() {
return "Current";
}
@Override
public int getColumnPreferredWidth() {
return 150;
}
@Override
public String getValue(QuickFix rowObject, Settings settings,
ServiceProvider sp) throws IllegalArgumentException {
return rowObject.getCurrent();
}
@Override
public GColumnRenderer<String> getColumnRenderer() {
return quickFixRenderer;
}
}
private class OriginalValueColumn
extends AbstractDynamicTableColumnStub<QuickFix, String> {
@Override
public String getColumnName() {
return "Original";
}
@Override
public int getColumnPreferredWidth() {
return 150;
}
@Override
public String getValue(QuickFix rowObject, Settings settings,
ServiceProvider sp) throws IllegalArgumentException {
return rowObject.getOriginal();
}
@Override
public GColumnRenderer<String> getColumnRenderer() {
return quickFixRenderer;
}
}
private class PreviewColumn
extends AbstractDynamicTableColumnStub<QuickFix, String> {
@Override
public String getColumnName() {
return "Preview";
}
@Override
public int getColumnPreferredWidth() {
return 150;
}
@Override
public String getValue(QuickFix rowObject, Settings settings,
ServiceProvider sp) throws IllegalArgumentException {
return rowObject.getPreview();
}
@Override
public GColumnRenderer<String> getColumnRenderer() {
return quickFixRenderer;
}
}
private class AddressColumn
extends AbstractDynamicTableColumnStub<QuickFix, Address> {
@Override
public String getColumnName() {
return "Address";
}
@Override
public int getColumnPreferredWidth() {
return 50;
}
@Override
public Address getValue(QuickFix rowObject, Settings settings,
ServiceProvider sp) throws IllegalArgumentException {
return rowObject.getAddress();
}
}
private class PathColumn
extends AbstractDynamicTableColumnStub<QuickFix, String> {
@Override
public String getColumnName() {
return "Path";
}
@Override
public int getColumnPreferredWidth() {
return 150;
}
@Override
public String getValue(QuickFix rowObject, Settings settings,
ServiceProvider sp) throws IllegalArgumentException {
return rowObject.getPath();
}
}
public class QuickFixRenderer extends AbstractGhidraColumnRenderer<String> {
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
JLabel renderer = (JLabel) super.getTableCellRendererComponent(data);
QuickFix item = (QuickFix) data.getRowObject();
StringBuilder buf = new StringBuilder();
buf.append("<HTML>");
buf.append("<TABLE>");
addHtmlTableRow(buf, "Action", item.getActionName() + " " + item.getItemType());
Address address = item.getAddress();
if (address != null && address.isMemoryAddress()) {
addHtmlTableRow(buf, "Address", address.toString());
}
addCustomTableRows(buf, item.getCustomToolTipData());
addHtmlTableRow(buf, "Original", item.getOriginal());
addHtmlTableRow(buf, "Preview", item.getPreview());
addHtmlTableRow(buf, "Current", item.getCurrent());
buf.append("</TABLE></HTML>");
renderer.setToolTipText(buf.toString());
return renderer;
}
private void addCustomTableRows(StringBuilder buf, Map<String, String> dataMap) {
if (dataMap == null) {
return;
}
for (Entry<String, String> entry : dataMap.entrySet()) {
addHtmlTableRow(buf, entry.getKey(), entry.getValue());
}
}
private void addHtmlTableRow(StringBuilder buf, String name, String value) {
buf.append("<TR><TD><B>");
buf.append(HTMLUtilities.escapeHTML(name));
buf.append(":</B></TD><TD>");
buf.append(HTMLUtilities.escapeHTML(value));
buf.append("</TD></TR>");
}
@Override
public String getFilterString(String t, Settings settings) {
return t;
}
}
}

View file

@ -0,0 +1,58 @@
/* ###
* 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.features.base.quickfix;
import docking.widgets.table.threaded.ThreadedTableModel;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* Generates table data for a {@link ThreadedTableModel}. Subclasses
* of ThreadedTableModel can call a TableLoader to supply data in the model's doLoad() method. Also
* has methods for the client to get feedback on the success of the load.
* <P>
* The idea is that instead of having to subclass the table model to overload the doLoad() method,
* a general table model is sufficient and be handed a TableDataLoader to provide data to the model.
*
* @param <T> The type of objects to load into a table model.
*/
public interface TableDataLoader<T> {
/**
* Loads data into the given accumulator
* @param accumulator the the accumulator for storing table data
* @param monitor the {@link TaskMonitor}
* @throws CancelledException if the operation is cancelled
*/
public void loadData(Accumulator<T> accumulator, TaskMonitor monitor)
throws CancelledException;
/**
* Returns true if at least one item was added to the accumulator.
* @return true if at least one item was added to the accumulator
*/
public boolean didProduceData();
/**
* Returns true if the load was terminated because the maximum number of items was
* reached.
* @return true if the load was terminated because the maximum number of items was
* reached.
*/
public boolean maxDataSizeReached();
}

View file

@ -0,0 +1,49 @@
/* ###
* 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.features.base.replace;
import ghidra.features.base.quickfix.QuickFix;
import ghidra.features.base.quickfix.QuickFixStatus;
import ghidra.program.model.listing.Program;
/**
* Base class for QuickFix objects that rename Ghidra program elements.
*/
public abstract class RenameQuickFix extends QuickFix {
/**
* Constructor
* @param program the program this applies to
* @param name the original name of the element to rename
* @param newName the new name for the element when this QuickFix is applied.
*/
public RenameQuickFix(Program program, String name, String newName) {
super(program, name, newName);
validateReplacementName();
}
protected void validateReplacementName() {
if (replacement.isBlank()) {
setStatus(QuickFixStatus.ERROR, "Can't rename to \"\"");
}
}
@Override
public String getActionName() {
return "Rename";
}
}

View file

@ -0,0 +1,356 @@
/* ###
* 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.features.base.replace;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.regex.PatternSyntaxException;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import docking.DialogComponentProvider;
import docking.widgets.combobox.GhidraComboBox;
import ghidra.app.util.HelpTopics;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.HelpLocation;
import ghidra.util.layout.*;
/**
* Dialog for entering information to perform a search and replace operation.
*/
public class SearchAndReplaceDialog extends DialogComponentProvider {
private static final int MAX_HISTORY = 20;
private List<SearchType> allTypes;
private List<JCheckBox> typeCheckboxes = new ArrayList<>();
private SearchAndReplaceQuery query;
private Set<SearchType> selectedTypes = new HashSet<>();
private GhidraComboBox<String> searchTextComboBox;
private GhidraComboBox<String> replaceTextComboBox;
private JCheckBox regexCheckbox;
private JCheckBox caseSensitiveCheckbox;
private JCheckBox wholeWordCheckbox;
private int searchLimit;
/**
* Constructor
* @param searchLimit the maximum number of search matches to find before stopping.
*/
public SearchAndReplaceDialog(int searchLimit) {
super("Search And Replace");
this.searchLimit = searchLimit;
this.allTypes = new ArrayList<>(SearchType.getSearchTypes());
Collections.sort(allTypes);
addWorkPanel(buildMainPanel());
addOKButton();
addCancelButton();
updateDialogStatus();
setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Search And Replace"));
}
/**
* Sets a new maximum number of search matches to find before stopping.
* @param searchLimit the new maximum number of search matches to find before stopping.
*/
public void setSearchLimit(int searchLimit) {
this.searchLimit = searchLimit;
}
/**
* Convenience method for initializing the dialog, showing it and returning the query.
* @param tool the tool this dialog belongs to
* @return the SearchAndReplaceQuery generated by the information in the dialog when the
* "Ok" button is pressed, or null if the dialog was cancelled.
*/
public SearchAndReplaceQuery show(PluginTool tool) {
query = null;
searchTextComboBox.getTextField().selectAll();
replaceTextComboBox.setText("");
updateDialogStatus();
tool.showDialog(this);
return query;
}
/**
* Returns the query generated by the dialog when the "Ok" button is pressed or null if the
* dialog was cancelled.
* @return the SearchAndReplaceQuery generated by the information in the dialog when the
* "Ok" button is pressed, or null if the dialog was cancelled.
*/
public SearchAndReplaceQuery getQuery() {
return query;
}
private JComponent buildMainPanel() {
JPanel panel = new JPanel(new VerticalLayout(20));
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
panel.add(buildPatternsPanel());
panel.add(buildOptionsPanel());
panel.add(buildTypesPanel());
return panel;
}
private JComponent buildTypesPanel() {
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createTitledBorder("Search For:"));
panel.add(buildInnerTypesPanel(), BorderLayout.CENTER);
panel.add(buildSelectAllPanel(), BorderLayout.SOUTH);
return panel;
}
private JPanel buildInnerTypesPanel() {
JPanel panel = new JPanel(new GridLayout(0, 3, 10, 5));
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
for (SearchType type : allTypes) {
JCheckBox cb = new JCheckBox(type.getName());
typeCheckboxes.add(cb);
cb.setToolTipText(type.getDescription());
cb.addActionListener(e -> typeCheckBoxChanged(cb, type));
panel.add(cb);
}
return panel;
}
private Component buildSelectAllPanel() {
JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 30, 10));
JButton selectAllButton = new JButton("Select All");
JButton deselectAllButton = new JButton("Deselect All");
selectAllButton.addActionListener(e -> selectAllTypes());
deselectAllButton.addActionListener(e -> deselectAllTypes());
panel.add(selectAllButton);
panel.add(deselectAllButton);
return panel;
}
private void selectAllTypes() {
for (JCheckBox checkBox : typeCheckboxes) {
checkBox.setSelected(true);
}
selectedTypes.addAll(allTypes);
updateDialogStatus();
}
private void deselectAllTypes() {
for (JCheckBox checkBox : typeCheckboxes) {
checkBox.setSelected(false);
}
selectedTypes.clear();
updateDialogStatus();
}
private void typeCheckBoxChanged(JCheckBox cb, SearchType type) {
if (cb.isSelected()) {
selectedTypes.add(type);
}
else {
selectedTypes.remove(type);
}
updateDialogStatus();
}
private void updateDialogStatus() {
boolean isValid = hasValidInformation();
setOkEnabled(isValid);
}
private boolean hasValidInformation() {
clearStatusText();
String searchText = searchTextComboBox.getText().trim();
if (searchText.isBlank()) {
setStatusText("Please enter search text");
return false;
}
if (selectedTypes.isEmpty()) {
setStatusText("Please select at least one \"search for\" item to search!");
return false;
}
return createQuery() != null;
}
private JComponent buildOptionsPanel() {
regexCheckbox = new JCheckBox("Regular expression");
caseSensitiveCheckbox = new JCheckBox("Case sensitive");
wholeWordCheckbox = new JCheckBox("Whole word");
regexCheckbox.addActionListener(e -> regExCheckboxChanged());
regexCheckbox.setToolTipText("Interprets search and replace text as regular expressions");
caseSensitiveCheckbox.setToolTipText("Determines if search text is case sensitive");
wholeWordCheckbox.setToolTipText("Sets if the input pattern must match whole words. For" +
" names, this means the entire name. For comments, it means matching entire words.");
JPanel panel = new JPanel(new HorizontalLayout(10));
Border titleBorder = BorderFactory.createTitledBorder("Options:");
Border innerBorder = BorderFactory.createEmptyBorder(5, 5, 5, 5);
panel.setBorder(BorderFactory.createCompoundBorder(titleBorder, innerBorder));
panel.add(regexCheckbox);
panel.add(caseSensitiveCheckbox);
panel.add(wholeWordCheckbox);
return panel;
}
private void regExCheckboxChanged() {
wholeWordCheckbox.setEnabled(!regexCheckbox.isSelected());
updateDialogStatus();
}
private Component buildPatternsPanel() {
searchTextComboBox = new GhidraComboBox<>();
searchTextComboBox.setEditable(true);
searchTextComboBox.setToolTipText("Enter the text to search for");
searchTextComboBox.getTextField()
.getDocument()
.addDocumentListener(new DocumentListener() {
@Override
public void removeUpdate(DocumentEvent e) {
updateDialogStatus();
}
@Override
public void insertUpdate(DocumentEvent e) {
updateDialogStatus();
}
@Override
public void changedUpdate(DocumentEvent e) {
updateDialogStatus();
}
});
replaceTextComboBox = new GhidraComboBox<>();
replaceTextComboBox.setEditable(true);
replaceTextComboBox.setToolTipText("Enter the text to replace matched search text");
searchTextComboBox.addActionListener(e -> pressOk());
replaceTextComboBox.addActionListener(e -> pressOk());
JPanel panel = new JPanel(new PairLayout(10, 10));
panel.add(new JLabel("Find:", SwingConstants.RIGHT));
panel.add(searchTextComboBox);
panel.add(new JLabel("Replace with:", SwingConstants.RIGHT));
panel.add(replaceTextComboBox);
return panel;
}
private void pressOk() {
if (isOKEnabled()) {
okCallback();
}
}
@Override
protected void okCallback() {
query = createQuery();
updateHistory(searchTextComboBox);
updateHistory(replaceTextComboBox);
closeDialog();
}
private SearchAndReplaceQuery createQuery() {
String searchText = searchTextComboBox.getText().trim();
String replacePattern = replaceTextComboBox.getText().trim();
boolean isRegEx = regexCheckbox.isSelected();
boolean isCaseSensitive = caseSensitiveCheckbox.isSelected();
boolean isWholeWord = wholeWordCheckbox.isSelected();
Set<SearchType> searchTypes = getSelectedSearchTypes();
try {
return new SearchAndReplaceQuery(searchText, replacePattern, searchTypes, isRegEx,
isCaseSensitive, isWholeWord, searchLimit);
}
catch (PatternSyntaxException e) {
return null;
}
}
private Set<SearchType> getSelectedSearchTypes() {
Set<SearchType> set = new HashSet<>();
for (SearchType type : allTypes) {
if (selectedTypes.contains(type)) {
set.add(type);
}
}
return set;
}
private void updateHistory(GhidraComboBox<String> combo) {
String value = combo.getText();
if (value.isBlank()) {
return;
}
DefaultComboBoxModel<String> model =
(DefaultComboBoxModel<String>) combo.getModel();
model.removeElement(value);
if (model.getSize() > MAX_HISTORY) {
model.removeElementAt(model.getSize() - 1);
}
model.insertElementAt(value, 0);
model.setSelectedItem(value);
}
/**
* Sets the search and replace text fields with given values.
* @param searchText the text to be put in the search field
* @param replaceText the text to be put in the replace field
*/
public void setSarchAndReplaceText(String searchText, String replaceText) {
searchTextComboBox.setText(searchText);
replaceTextComboBox.setText(replaceText);
}
/**
* Sets the search type with the given name to be selected.
* @param searchType the name of the search type to select
*/
public void selectSearchType(String searchType) {
for (JCheckBox checkBox : typeCheckboxes) {
if (checkBox.getText().equals(searchType)) {
checkBox.setSelected(true);
break;
}
}
for (SearchType type : allTypes) {
if (type.getName().equals(searchType)) {
selectedTypes.add(type);
break;
}
}
updateDialogStatus();
}
/**
* Returns true if the "ok" button is enabled.
* @return true if the "ok" button is enabled.
*/
public boolean isOkEnabled() {
return super.isOKEnabled();
}
/**
* Selects the RegEx checkbox in the dialog.
* @param b true to select RegEx, false to turn deselect it
*/
public void selectRegEx(boolean b) {
regexCheckbox.setSelected(b);
updateDialogStatus();
}
}

View file

@ -0,0 +1,68 @@
/* ###
* 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.features.base.replace;
import java.util.HashSet;
import java.util.Set;
import ghidra.features.base.quickfix.QuickFix;
import ghidra.program.model.listing.Program;
import ghidra.util.classfinder.ExtensionPoint;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* Base class for discoverable SearchAndReplaceHandlers. A SearchAndReplaceHandler is responsible
* for searching one or more specific program elements (referred to as {@link SearchType}) for a
* given search pattern and generating the appropriate {@link QuickFix}.
* <P>
* Typically, one handler will handle related search elements for efficiency. For example, the
* DataTypesSearchAndReplaceHandler is responsible for datatype names, field names, field comments,
* etc. The idea is to only loop through all the datatypes once, regardless of what aspect of a
* datatype you are searching for.
*/
public abstract class SearchAndReplaceHandler implements ExtensionPoint {
private Set<SearchType> types = new HashSet<>();
/**
* Method to perform the search for the pattern and options as specified by the given
* SearchAndReplaceQuery. As matches are found, appropriate {@link QuickFix}s are added to
* the given accumulator.
* @param program the program being searched
* @param query contains the search pattern, replacement pattern, and options related to the
* query.
* @param accumulator the accumulator that resulting QuickFix items are added to as they are
* found.
* @param monitor a {@link TaskMonitor} for reporting progress and checking if the search has
* been cancelled.
* @throws CancelledException thrown if the operation has been cancelled via the taskmonitor
*/
public abstract void findAll(Program program, SearchAndReplaceQuery query,
Accumulator<QuickFix> accumulator, TaskMonitor monitor) throws CancelledException;
/**
* Returns the set of {@link SearchType}s this handler supports.
* @return the set of {@link SearchType}s this handler supports.
*/
public Set<SearchType> getSearchAndReplaceTypes() {
return types;
}
protected void addType(SearchType type) {
types.add(type);
}
}

View file

@ -0,0 +1,133 @@
/* ###
* 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.features.base.replace;
import static ghidra.app.util.SearchConstants.*;
import java.util.ArrayList;
import java.util.List;
import docking.action.builder.ActionBuilder;
import ghidra.app.CorePluginPackage;
import ghidra.app.context.NavigatableActionContext;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.services.GoToService;
import ghidra.app.services.ProgramManager;
import ghidra.app.util.HelpTopics;
import ghidra.app.util.SearchConstants;
import ghidra.framework.options.OptionsChangeListener;
import ghidra.framework.options.ToolOptions;
import ghidra.framework.plugintool.PluginInfo;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.PluginStatus;
import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
import ghidra.util.bean.opteditor.OptionsVetoException;
/**
* Plugin to perform search and replace operations for many different program element types such
* as labels, functions, classes, datatypes, memory blocks, and more.
*/
//@formatter:off
@PluginInfo(
status = PluginStatus.RELEASED,
packageName = CorePluginPackage.NAME,
category = PluginCategoryNames.SEARCH,
shortDescription = "Search and replace text on program element names or comments.",
description = "This plugin provides a search and replace capability for a variety of" +
"program elements including functions, classes, namespaces, datatypes, field names, and" +
"other.",
servicesRequired = { ProgramManager.class, GoToService.class }
)
//@formatter:on
public class SearchAndReplacePlugin extends ProgramPlugin {
private SearchAndReplaceDialog cachedDialog;
private int searchLimit = DEFAULT_SEARCH_LIMIT;
private OptionsChangeListener searchOptionsListener;
private List<SearchAndReplaceProvider> providers = new ArrayList<>();
public SearchAndReplacePlugin(PluginTool plugintool) {
super(plugintool);
createActions();
initializeOptions();
}
@Override
protected void programClosed(Program program) {
List<SearchAndReplaceProvider> copy = new ArrayList<>(providers);
for (SearchAndReplaceProvider provider : copy) {
provider.programClosed(program);
}
}
private void initializeOptions() {
ToolOptions options = tool.getOptions(SearchConstants.SEARCH_OPTION_NAME);
searchLimit = options.getInt(SEARCH_LIMIT_NAME, DEFAULT_SEARCH_LIMIT);
searchOptionsListener = this::searchOptionsChanged;
options.addOptionsChangeListener(searchOptionsListener);
}
private void searchOptionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) {
if (optionName.equals(SEARCH_LIMIT_NAME)) {
int limit = (int) newValue;
if (limit <= 0) {
throw new OptionsVetoException("Search limit must be greater than 0");
}
searchLimit = limit;
if (cachedDialog != null) {
cachedDialog.setSearchLimit(limit);
}
}
}
private void createActions() {
new ActionBuilder("Search And Replace", getName())
.menuPath("&Search", "Search And Replace...")
.menuGroup("search", "d")
.description("Search and replace names of various program elements")
.helpLocation(new HelpLocation(HelpTopics.SEARCH, "Search And Replace"))
.withContext(NavigatableActionContext.class, true)
.onAction(this::searchAndReplace)
.buildAndInstall(tool);
}
private void searchAndReplace(NavigatableActionContext c) {
SearchAndReplaceDialog dialog = getDialog();
SearchAndReplaceQuery query = dialog.show(tool);
if (query == null) {
return;
}
Program program = c.getProgram();
providers.add(new SearchAndReplaceProvider(this, program, query));
}
private SearchAndReplaceDialog getDialog() {
if (cachedDialog == null) {
cachedDialog = new SearchAndReplaceDialog(searchLimit);
}
return cachedDialog;
}
void providerClosed(SearchAndReplaceProvider provider) {
providers.remove(provider);
}
}

View file

@ -0,0 +1,106 @@
/* ###
* 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.features.base.replace;
import java.awt.*;
import javax.swing.*;
import ghidra.app.util.HelpTopics;
import ghidra.features.base.quickfix.*;
import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
/**
* Subclass of the {@link QuckFixTableProvider} that customizes it specifically for search and replace
* operations.
*/
public class SearchAndReplaceProvider extends QuckFixTableProvider {
private SearchAndReplacePlugin plugin;
private SearchAndReplaceQuery query;
public SearchAndReplaceProvider(SearchAndReplacePlugin plugin, Program program,
SearchAndReplaceQuery query) {
super(plugin.getTool(), "Search And Replace", plugin.getName(), program,
new SearchAndReplaceQuckFixTableLoader(program, query));
this.plugin = plugin;
this.query = query;
setTitle(generateTitle());
setTabText(getTabTitle());
addToTool();
setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Search_And_Replace_Results"));
}
@Override
protected void tableLoaded(boolean wasCancelled, TableDataLoader<QuickFix> loader) {
if (!loader.didProduceData()) {
Msg.showInfo(getClass(), getComponent(), "No Results Found!",
"No results for \"" + query.getSearchText() + "\" found.");
closeComponent();
return;
}
setVisible(true);
if (loader.maxDataSizeReached()) {
Msg.showInfo(getClass(), getComponent(), "Search Limit Exceeded!",
"Stopped search after finding " + query.getSearchLimit() + " matches.\n" +
"The search limit can be changed at Edit->Tool Options, under Search.");
}
toFront();
}
@Override
public void closeComponent() {
super.closeComponent();
plugin.providerClosed(this);
}
private String getTabTitle() {
return "\"" + query.getSearchText() + "\" -> \"" +
query.getReplacementText() + "\"";
}
private String generateTitle() {
return "Search & Replace: " + getTabTitle();
}
@Override
protected JPanel buildMainPanel() {
JPanel quickFixPanel = super.buildMainPanel();
JPanel panel = new JPanel(new BorderLayout());
panel.add(quickFixPanel, BorderLayout.CENTER);
panel.add(buildButtonPanel(), BorderLayout.SOUTH);
return panel;
}
private Component buildButtonPanel() {
JButton replaceButton = new JButton("Replace All");
JButton dismissButton = new JButton("Dismiss");
replaceButton.addActionListener(e -> executeAll());
dismissButton.addActionListener(e -> closeComponent());
JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 0));
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
panel.add(replaceButton);
panel.add(dismissButton);
return panel;
}
}

View file

@ -0,0 +1,66 @@
/* ###
* 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.features.base.replace;
import ghidra.features.base.quickfix.QuickFix;
import ghidra.features.base.quickfix.TableDataLoader;
import ghidra.program.model.listing.Program;
import ghidra.util.datastruct.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* Class for loading search and replace items into a ThreadedTableModel.
*/
public class SearchAndReplaceQuckFixTableLoader implements TableDataLoader<QuickFix> {
private SearchAndReplaceQuery query;
private Program program;
private boolean hasResults;
private boolean searchLimitExceeded;
public SearchAndReplaceQuckFixTableLoader(Program program, SearchAndReplaceQuery query) {
this.program = program;
this.query = query;
}
@Override
public void loadData(Accumulator<QuickFix> accumulator, TaskMonitor monitor)
throws CancelledException {
Accumulator<QuickFix> wrappedAccumulator =
new SizeRestrictedAccumulatorWrapper<QuickFix>(accumulator, query.getSearchLimit());
try {
query.findAll(program, wrappedAccumulator, monitor);
}
catch (AccumulatorSizeException e) {
searchLimitExceeded = true;
}
finally {
hasResults = !accumulator.isEmpty();
}
}
@Override
public boolean didProduceData() {
return hasResults;
}
@Override
public boolean maxDataSizeReached() {
return searchLimitExceeded;
}
}

View file

@ -0,0 +1,152 @@
/* ###
* 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.features.base.replace;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
import ghidra.features.base.quickfix.QuickFix;
import ghidra.program.model.listing.Program;
import ghidra.util.UserSearchUtils;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* Immutable class for storing all related query information for performing a search and
* replace operation. It includes the search pattern, the search pattern text, the search lmiit,
* and the types of program elements to search.
*/
public class SearchAndReplaceQuery {
private final String searchText;
private final String replacementText;
private final Pattern pattern;
private final int searchLimit;
private final Set<SearchType> selectedTypes = new HashSet<>();
/**
* Constructor
* @param searchText the user entered search pattern text. It will be used to generate the
* actual Pattern based on the various options.
* @param replacementText the user entered replacement text.
* @param searchTypes the types of program elements to search
* @param isRegEx true if the given search text is to be interpreted as a regular expression.
* @param isCaseSensitive true if the search text should be case sensitive
* @param isWholeWord true, the search text should match the enter element in the case of a
* rename, or an entire word within a larger sentence in the case of a comment.
* @param searchLimit the maximum entries to find before terminating the search.
*/
public SearchAndReplaceQuery(String searchText, String replacementText,
Set<SearchType> searchTypes, boolean isRegEx, boolean isCaseSensitive,
boolean isWholeWord, int searchLimit) {
this.searchText = searchText;
this.replacementText = replacementText;
this.pattern = createPattern(isRegEx, isCaseSensitive, isWholeWord);
this.searchLimit = searchLimit;
selectedTypes.addAll(searchTypes);
}
/**
* Method to initiate the search.
* @param program the program to search
* @param accumulator the accumulator to store the generated {@link QuickFix}s
* @param monitor the {@link TaskMonitor}
* @throws CancelledException if the search is cancelled.
*/
public void findAll(Program program, Accumulator<QuickFix> accumulator,
TaskMonitor monitor) throws CancelledException {
Set<SearchAndReplaceHandler> handlers = getHandlers();
for (SearchAndReplaceHandler handler : handlers) {
handler.findAll(program, this, accumulator, monitor);
}
}
/**
* Returns the search {@link Pattern} used to search program elements.
* @return the search {@link Pattern} used to search program elements
*/
public Pattern getSearchPattern() {
return pattern;
}
/**
* Returns true if the given SearchType is to be included in the search.
* @param searchType the SearchType to check if it is included in the search
* @return true if the given SearchType is to be included in the search.
*/
public boolean containsSearchType(SearchType searchType) {
return selectedTypes.contains(searchType);
}
/**
* Returns the search text used to generate the pattern for this query.
* @return the search text used to generate the pattern for this query
*/
public String getSearchText() {
return searchText;
}
/**
* Returns the replacement text that will replace matched elements.
* @return the replacement text that will replace matched elements
*/
public String getReplacementText() {
return replacementText;
}
/**
* Returns a set of all the SearchTypes to be included in this query.
* @return a set of all the SearchTypes to be included in this query
*/
public Set<SearchType> getSelectedSearchTypes() {
return selectedTypes;
}
/**
* Returns the maximum number of search matches to be found before stopping early.
* @return the maximum number of search matches to be found before stopping early.
*/
public int getSearchLimit() {
return searchLimit;
}
private Pattern createPattern(boolean isRegEx, boolean isCaseSensitive, boolean isWholeWord) {
int regExFlags = Pattern.DOTALL;
if (!isCaseSensitive) {
regExFlags |= Pattern.CASE_INSENSITIVE;
}
if (isRegEx) {
return Pattern.compile(searchText, regExFlags);
}
String converted = UserSearchUtils.convertUserInputToRegex(searchText, false);
if (isWholeWord) {
converted = "\\b" + converted + "\\b";
}
return Pattern.compile(converted, regExFlags);
}
private Set<SearchAndReplaceHandler> getHandlers() {
Set<SearchAndReplaceHandler> handlers = new HashSet<>();
for (SearchType type : selectedTypes) {
handlers.add(type.getHandler());
}
return handlers;
}
}

View file

@ -0,0 +1,93 @@
/* ###
* 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.features.base.replace;
import java.util.*;
import ghidra.util.classfinder.ClassSearcher;
/**
* Represents a ghidra program element type that can be individually included or excluded when doing
* a search and replace operation. The {@link SearchAndReplaceDialog} will include a checkbox for
* each of these types.
*/
public class SearchType implements Comparable<SearchType> {
private final SearchAndReplaceHandler handler;
private final String name;
private final String description;
/**
* Constructor
* @param handler The {@link SearchAndReplaceHandler} that actually has the logic for doing
* the search for this program element type.
* @param name the name of element type that is searchable
* @param description a description of this type which would be suitable to display as a tooltip
*/
public SearchType(SearchAndReplaceHandler handler, String name, String description) {
this.handler = handler;
this.name = name;
this.description = description;
}
/**
* Returns the {@link SearchAndReplaceHandler} that can process this type.
* @return the handler for processing this type
*/
public SearchAndReplaceHandler getHandler() {
return handler;
}
/**
* Returns the name of this search type.
* @return the name of this search type
*/
public String getName() {
return name;
}
/**
* Returns a description of this search type.
* @return a description of this search type
*/
public String getDescription() {
return description;
}
/**
* Static convenience method for finding all known SearchTypes. It uses the
* {@link ClassSearcher} to find all {@link SearchAndReplaceHandler}s and then gathers up
* all the SearchTypes that each handler supports.
*
* @return The set of all Known SearchTypes
*/
public static Set<SearchType> getSearchTypes() {
List<SearchAndReplaceHandler> handlers =
ClassSearcher.getInstances(SearchAndReplaceHandler.class);
Set<SearchType> types = new HashSet<>();
for (SearchAndReplaceHandler handler : handlers) {
types.addAll(handler.getSearchAndReplaceTypes());
}
return types;
}
@Override
public int compareTo(SearchType o) {
return name.compareTo(o.name);
}
}

View file

@ -0,0 +1,274 @@
/* ###
* 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.features.base.replace.handler;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ghidra.features.base.quickfix.QuickFix;
import ghidra.features.base.replace.*;
import ghidra.features.base.replace.items.*;
import ghidra.program.model.data.*;
import ghidra.program.model.data.Enum;
import ghidra.program.model.listing.Program;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* {@link SearchAndReplaceHandler} for handling search and replace for datatype names,
* structure and union field names, structure and union field comments, enum value names,
* and enum value comments.
*/
public class DataTypesSearchAndReplaceHandler extends SearchAndReplaceHandler {
DataTypeSearchType nameType;
DataTypeSearchType datatypeCommentsType;
DataTypeSearchType fieldNameType;
DataTypeSearchType enumValueNameType;
public DataTypesSearchAndReplaceHandler() {
nameType = new NameSearchType(this);
datatypeCommentsType = new DataTypeCommentsSearchType(this);
fieldNameType = new FieldNameSearchType(this);
enumValueNameType = new EnumValueSearchType(this);
addType(nameType);
addType(datatypeCommentsType);
addType(fieldNameType);
addType(enumValueNameType);
}
@Override
public void findAll(Program program, SearchAndReplaceQuery query,
Accumulator<QuickFix> accumulator, TaskMonitor monitor) throws CancelledException {
ProgramBasedDataTypeManager dataTypeManager = program.getDataTypeManager();
List<DataType> allDataTypes = new ArrayList<>();
dataTypeManager.getAllDataTypes(allDataTypes);
monitor.initialize(allDataTypes.size(), "Searching DataTypes...");
boolean doNames = query.containsSearchType(nameType);
boolean doDatatypeComments = query.containsSearchType(datatypeCommentsType);
boolean doFieldNames = query.containsSearchType(fieldNameType);
boolean doEnumValueNames = query.containsSearchType(enumValueNameType);
for (DataType dataType : allDataTypes) {
monitor.increment();
if (dataType instanceof Pointer || dataType instanceof Array) {
continue;
}
if (doNames) {
nameType.search(program, dataType, query, accumulator);
}
if (doDatatypeComments) {
datatypeCommentsType.search(program, dataType, query, accumulator);
}
if (doFieldNames) {
fieldNameType.search(program, dataType, query, accumulator);
}
if (doEnumValueNames) {
enumValueNameType.search(program, dataType, query, accumulator);
}
}
}
private abstract static class DataTypeSearchType extends SearchType {
public DataTypeSearchType(SearchAndReplaceHandler handler, String name,
String description) {
super(handler, name, description);
}
protected abstract void search(Program program, DataType dataType,
SearchAndReplaceQuery query, Accumulator<QuickFix> accumulator);
}
private static class NameSearchType extends DataTypeSearchType {
public NameSearchType(SearchAndReplaceHandler handler) {
super(handler, "Datatypes", "Search and replace datatype names");
}
@Override
protected void search(Program program, DataType dataType, SearchAndReplaceQuery query,
Accumulator<QuickFix> accumulator) {
Pattern searchPattern = query.getSearchPattern();
Matcher matcher = searchPattern.matcher(dataType.getName());
if (matcher.find()) {
String newName = matcher.replaceAll(query.getReplacementText());
RenameDataTypeQuickFix item =
new RenameDataTypeQuickFix(program, dataType, newName);
accumulator.add(item);
}
}
}
private static class FieldNameSearchType extends DataTypeSearchType {
public FieldNameSearchType(SearchAndReplaceHandler handler) {
super(handler, "Datatype Fields",
"Search and replace structure and union member names");
}
@Override
protected void search(Program program, DataType dataType, SearchAndReplaceQuery query,
Accumulator<QuickFix> accumulator) {
if (!(dataType instanceof Composite composite)) {
return;
}
DataTypeComponent[] definedComponents = composite.getDefinedComponents();
Pattern searchPattern = query.getSearchPattern();
for (int i = 0; i < definedComponents.length; i++) {
DataTypeComponent component = definedComponents[i];
String name = getFieldName(component);
Matcher matcher = searchPattern.matcher(name);
if (matcher.find()) {
String newName = matcher.replaceAll(query.getReplacementText());
int ordinal = component.getOrdinal();
QuickFix item =
new RenameFieldQuickFix(program, composite, ordinal, name, newName);
accumulator.add(item);
}
}
}
private String getFieldName(DataTypeComponent component) {
String fieldName = component.getFieldName();
return fieldName == null ? component.getDefaultFieldName() : fieldName;
}
}
private static class DataTypeCommentsSearchType extends DataTypeSearchType {
public DataTypeCommentsSearchType(SearchAndReplaceHandler handler) {
super(handler, "Datatype Comments", "Search and replace comments on datatypes");
}
@Override
protected void search(Program program, DataType dataType, SearchAndReplaceQuery query,
Accumulator<QuickFix> accumulator) {
searchDescriptions(program, dataType, query, accumulator);
if (dataType instanceof Composite composite) {
searchFieldComments(program, composite, query, accumulator);
}
else if (dataType instanceof Enum enumm) {
searchEnumComments(program, enumm, query, accumulator);
}
}
private void searchEnumComments(Program program, Enum enumm, SearchAndReplaceQuery query,
Accumulator<QuickFix> accumulator) {
String[] names = enumm.getNames();
Pattern searchPattern = query.getSearchPattern();
for (int i = 0; i < names.length; i++) {
String valueName = names[i];
String comment = enumm.getComment(valueName);
Matcher matcher = searchPattern.matcher(comment);
if (matcher.find()) {
String newValueName = matcher.replaceAll(query.getReplacementText());
QuickFix item =
new UpdateEnumCommentQuickFix(program, enumm, valueName, newValueName);
accumulator.add(item);
}
}
}
private void searchFieldComments(Program program, Composite composite,
SearchAndReplaceQuery query, Accumulator<QuickFix> accumulator) {
DataTypeComponent[] definedComponents = composite.getDefinedComponents();
Pattern searchPattern = query.getSearchPattern();
for (int i = 0; i < definedComponents.length; i++) {
DataTypeComponent component = definedComponents[i];
String comment = component.getComment();
if (comment == null) {
continue;
}
Matcher matcher = searchPattern.matcher(comment);
if (matcher.find()) {
String newComment = matcher.replaceAll(query.getReplacementText());
QuickFix item =
new UpdateFieldCommentQuickFix(program, composite, component.getFieldName(),
component.getOrdinal(), comment, newComment);
accumulator.add(item);
}
}
}
protected void searchDescriptions(Program program, DataType dataType,
SearchAndReplaceQuery query, Accumulator<QuickFix> accumulator) {
String description = getDescription(dataType);
if (description == null || description.isBlank()) {
return;
}
Pattern searchPattern = query.getSearchPattern();
Matcher matcher = searchPattern.matcher(description);
if (matcher.find()) {
String newName = matcher.replaceAll(query.getReplacementText());
UpdateDataTypeDescriptionQuickFix item =
new UpdateDataTypeDescriptionQuickFix(program, dataType, newName);
accumulator.add(item);
}
}
private String getDescription(DataType dataType) {
if (dataType instanceof Composite composite) {
return composite.getDescription();
}
if (dataType instanceof Enum enumDataType) {
return enumDataType.getDescription();
}
return null;
}
}
private static class EnumValueSearchType extends DataTypeSearchType {
public EnumValueSearchType(SearchAndReplaceHandler handler) {
super(handler, "Enum Values", "Search and replace enum value names");
}
@Override
protected void search(Program program, DataType dataType, SearchAndReplaceQuery query,
Accumulator<QuickFix> accumulator) {
if (!(dataType instanceof Enum enumm)) {
return;
}
String[] names = enumm.getNames();
Pattern searchPattern = query.getSearchPattern();
for (int i = 0; i < names.length; i++) {
String valueName = names[i];
Matcher matcher = searchPattern.matcher(valueName);
if (matcher.find()) {
String newValueName = matcher.replaceAll(query.getReplacementText());
QuickFix item =
new RenameEnumValueQuickFix(program, enumm, valueName, newValueName);
accumulator.add(item);
}
}
}
}
}

View file

@ -0,0 +1,78 @@
/* ###
* 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.features.base.replace.handler;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ghidra.features.base.quickfix.QuickFix;
import ghidra.features.base.replace.*;
import ghidra.features.base.replace.items.RenameCategoryQuickFix;
import ghidra.program.model.data.Category;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.listing.Program;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import utility.function.ExceptionalConsumer;
/**
* {@link SearchAndReplaceHandler} for handling search and replace for datatype category names.
*/
public class DatatypeCategorySearchAndReplaceHandler extends SearchAndReplaceHandler {
public DatatypeCategorySearchAndReplaceHandler() {
addType(new SearchType(this, "Datatype Categories",
"Search and replace datatype category names"));
}
@Override
public void findAll(Program program, SearchAndReplaceQuery query,
Accumulator<QuickFix> accumulator, TaskMonitor monitor) throws CancelledException {
int categoryCount = program.getDataTypeManager().getCategoryCount();
monitor.initialize(categoryCount, "Searching Datatype categories...");
Pattern pattern = query.getSearchPattern();
DataTypeManager dtm = program.getDataTypeManager();
Category rootCategory = dtm.getRootCategory();
visitRecursively(rootCategory, category -> {
monitor.increment();
Matcher matcher = pattern.matcher(category.getName());
if (matcher.find()) {
String newName = matcher.replaceAll(query.getReplacementText());
RenameCategoryQuickFix item = new RenameCategoryQuickFix(program, category, newName);
accumulator.add(item);
if (accumulator.size() >= query.getSearchLimit()) {
return;
}
}
});
}
private void visitRecursively(Category category,
ExceptionalConsumer<Category, CancelledException> callback) throws CancelledException {
callback.accept(category);
Category[] categories = category.getCategories();
for (Category childCategory : categories) {
visitRecursively(childCategory, callback);
}
}
}

View file

@ -0,0 +1,75 @@
/* ###
* 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.features.base.replace.handler;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ghidra.features.base.quickfix.QuickFix;
import ghidra.features.base.replace.*;
import ghidra.features.base.replace.items.UpdateCommentQuickFix;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* {@link SearchAndReplaceHandler} for handling search and replace for listing comments on
* instructions or data.
*/
public class ListingCommentsSearchAndReplaceHandler extends SearchAndReplaceHandler {
public ListingCommentsSearchAndReplaceHandler() {
addType(new SearchType(this, "Comments", "Search and replace in listing comments"));
}
@Override
public void findAll(Program program, SearchAndReplaceQuery query,
Accumulator<QuickFix> accumulator, TaskMonitor monitor) throws CancelledException {
Listing listing = program.getListing();
long count = listing.getCommentAddressCount();
monitor.initialize(count, "Searching Comments...");
Pattern pattern = query.getSearchPattern();
String replaceMentText = query.getReplacementText();
for (Address address : listing.getCommentAddressIterator(program.getMemory(), true)) {
monitor.checkCancelled();
CodeUnitComments comments = listing.getAllComments(address);
for (CommentType type : CommentType.values()) {
String comment = comments.getComment(type);
String newComment = checkMatch(pattern, comment, replaceMentText);
if (newComment != null) {
accumulator.add(
new UpdateCommentQuickFix(program, address, type, comment, newComment));
}
}
}
}
private String checkMatch(Pattern pattern, String comment, String replacementText) {
if (comment == null) {
return null;
}
Matcher matcher = pattern.matcher(comment);
if (matcher.find()) {
return matcher.replaceAll(replacementText);
}
return null;
}
}

View file

@ -0,0 +1,64 @@
/* ###
* 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.features.base.replace.handler;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ghidra.features.base.quickfix.QuickFix;
import ghidra.features.base.replace.*;
import ghidra.features.base.replace.items.RenameMemoryBlockQuickFix;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* {@link SearchAndReplaceHandler} for handling search and replace for memory block names.
*/
public class MemoryBlockSearchAndReplaceHandler extends SearchAndReplaceHandler {
public MemoryBlockSearchAndReplaceHandler() {
addType(new SearchType(this, "Memory Blocks", "Search and replace memory block names"));
}
@Override
public void findAll(Program program, SearchAndReplaceQuery query,
Accumulator<QuickFix> accumulator, TaskMonitor monitor) throws CancelledException {
Memory memory = program.getMemory();
MemoryBlock[] blocks = memory.getBlocks();
monitor.initialize(blocks.length, "Searching MemoryBlocks...");
Pattern pattern = query.getSearchPattern();
for (MemoryBlock block : blocks) {
monitor.increment();
Matcher matcher = pattern.matcher(block.getName());
if (matcher.find()) {
String newName = matcher.replaceAll(query.getReplacementText());
RenameMemoryBlockQuickFix item =
new RenameMemoryBlockQuickFix(program, block, newName);
accumulator.add(item);
}
}
}
}

View file

@ -0,0 +1,100 @@
/* ###
* 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.features.base.replace.handler;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ghidra.features.base.quickfix.QuickFix;
import ghidra.features.base.replace.*;
import ghidra.features.base.replace.items.RenameProgramTreeGroupQuickFix;
import ghidra.program.model.listing.*;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* {@link SearchAndReplaceHandler} for handling search and replace for program tree modules and
* fragments.
*/
public class ProgramTreeSearchAndReplaceHandler extends SearchAndReplaceHandler {
public ProgramTreeSearchAndReplaceHandler() {
addType(new SearchType(this, "Program Trees",
"Search and replace program tree module and fragment names"));
}
@Override
public void findAll(Program program, SearchAndReplaceQuery query,
Accumulator<QuickFix> accumulator, TaskMonitor monitor) throws CancelledException {
Listing listing = program.getListing();
String[] treeNames = listing.getTreeNames();
monitor.initialize(treeNames.length, "Search Program Trees");
for (String treeName : treeNames) {
monitor.increment();
findAll(program, treeName, query, accumulator, monitor);
}
}
private void findAll(Program program, String treeName, SearchAndReplaceQuery query,
Accumulator<QuickFix> accumulator, TaskMonitor monitor) throws CancelledException {
Set<Group> set = gatherProgramTreeGroups(program, treeName, monitor);
Pattern pattern = query.getSearchPattern();
/**
* Check all the modules and fragments in the tree
*/
for (Group group : set) {
String name = group.getName();
Matcher matcher = pattern.matcher(name);
if (matcher.find()) {
String newName = matcher.replaceAll(query.getReplacementText());
QuickFix item = new RenameProgramTreeGroupQuickFix(program, group, newName);
accumulator.add(item);
}
}
}
private Set<Group> gatherProgramTreeGroups(Program program, String treeName,
TaskMonitor monitor) throws CancelledException {
monitor.checkCancelled();
Listing listing = program.getListing();
ProgramModule rootModule = listing.getRootModule(treeName);
Set<Group> set = new HashSet<>();
addProgramTreeGroupsRecursively(set, rootModule, monitor);
// The root module name is the name of the program. Don't allow to change it here.
set.remove(rootModule);
return set;
}
private void addProgramTreeGroupsRecursively(Set<Group> set, Group group, TaskMonitor monitor) {
set.add(group);
if (group instanceof ProgramModule module) {
Group[] children = module.getChildren();
for (Group child : children) {
addProgramTreeGroupsRecursively(set, child, monitor);
}
}
}
}

View file

@ -0,0 +1,115 @@
/* ###
* 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.features.base.replace.handler;
import static ghidra.program.model.symbol.SymbolType.*;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ghidra.features.base.quickfix.QuickFix;
import ghidra.features.base.replace.*;
import ghidra.features.base.replace.items.RenameSymbolQuickFix;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.*;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* {@link SearchAndReplaceHandler} for handling search and replace for symbols. Specifically, it
* provides {@link SearchType}s for renaming labels, functions, namespaces, classes, local
* variables, and parameters.
*/
public class SymbolsSearchAndReplaceHandler extends SearchAndReplaceHandler {
public SymbolsSearchAndReplaceHandler() {
//@formatter:off
addType(new SymbolSearchType(LABEL, "Labels", "Search and replace label names"));
addType(new SymbolSearchType(FUNCTION, "Functions", "Search and replace function names"));
addType(new SymbolSearchType(NAMESPACE, "Namespaces", "Search and replace generic namespace names"));
addType(new SymbolSearchType(CLASS, "Classes", "Search and replace class names"));
addType(new SymbolSearchType(LOCAL_VAR, "Local Variables", "Search and replace local variable names"));
addType(new SymbolSearchType(PARAMETER, "Parameters", "Search and replace parameter names"));
//@formatter:on
}
@Override
public void findAll(Program program, SearchAndReplaceQuery query,
Accumulator<QuickFix> accumulator, TaskMonitor monitor) throws CancelledException {
SymbolTable symbolTable = program.getSymbolTable();
int symbolCount = symbolTable.getNumSymbols();
monitor.initialize(symbolCount, "Searching Labels...");
Pattern pattern = query.getSearchPattern();
Set<SymbolType> selectedSymbolTypes = getSelectedSymbolTypes(query);
for (Symbol symbol : symbolTable.getDefinedSymbols()) {
monitor.increment();
if (symbol.isExternal()) {
continue;
}
SymbolType symbolType = symbol.getSymbolType();
if (selectedSymbolTypes.contains(symbolType)) {
if (symbolType == SymbolType.FUNCTION) {
Function function = (Function) symbol.getObject();
// Thunks can't be renamed directly
if (function.isThunk()) {
continue;
}
}
Matcher matcher = pattern.matcher(symbol.getName());
if (matcher.find()) {
String newName = matcher.replaceAll(query.getReplacementText());
RenameSymbolQuickFix item = new RenameSymbolQuickFix(symbol, newName);
accumulator.add(item);
}
}
}
}
private Set<SymbolType> getSelectedSymbolTypes(SearchAndReplaceQuery query) {
Set<SymbolType> symbolTypes = new HashSet<>();
Set<SearchType> selectedSearchTypes = query.getSelectedSearchTypes();
for (SearchType searchType : selectedSearchTypes) {
if (searchType instanceof SymbolSearchType symbolSearchType) {
symbolTypes.add(symbolSearchType.getSymbolType());
}
}
return symbolTypes;
}
private class SymbolSearchType extends SearchType {
private final SymbolType symbolType;
SymbolSearchType(SymbolType symbolType, String name, String description) {
super(SymbolsSearchAndReplaceHandler.this, name, description);
this.symbolType = symbolType;
}
SymbolType getSymbolType() {
return symbolType;
}
}
}

View file

@ -0,0 +1,116 @@
/* ###
* 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.features.base.replace.items;
import java.util.Map;
import ghidra.app.services.DataTypeManagerService;
import ghidra.features.base.quickfix.QuickFix;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.Composite;
import ghidra.program.model.data.DataTypeComponent;
import ghidra.program.model.listing.Program;
/**
* Base class for Composite field Quick Fixes. Primarily exists to host the logic for finding
* components in a composite even as it is changing.
*/
public abstract class CompositeFieldQuickFix extends QuickFix {
protected Composite composite;
private int ordinal;
/**
* Constructor
* @param program the program containing the composite.
* @param composite the composite being changed
* @param ordinal the ordinal of the field within the composite
* @param original the original name of the field
* @param newName the new name for the field
*/
public CompositeFieldQuickFix(Program program, Composite composite, int ordinal,
String original, String newName) {
super(program, original, newName);
this.composite = composite;
this.ordinal = ordinal;
}
@Override
public Address getAddress() {
return null;
}
@Override
public String getPath() {
return composite.getPathName();
}
protected DataTypeComponent findComponent(String name) {
DataTypeComponent component = getComponentByOrdinal();
if (component != null) {
if (name.equals(component.getFieldName())) {
return component;
}
}
// perhaps it moved (has a different ordinal now)?
DataTypeComponent[] components = composite.getDefinedComponents();
for (int i = 0; i < components.length; i++) {
if (name.equals(components[i].getFieldName())) {
ordinal = i;
return components[i];
}
}
return null;
}
protected DataTypeComponent getComponentByOrdinal() {
if (composite.isDeleted()) {
return null;
}
if (ordinal >= composite.getNumComponents()) {
return null;
}
return composite.getComponent(ordinal);
}
@Override
public Map<String, String> getCustomToolTipData() {
return Map.of("DataType", composite.getPathName());
}
@Override
protected boolean navigateSpecial(ServiceProvider services, boolean fromSelectionChange) {
DataTypeManagerService dtmService = services.getService(DataTypeManagerService.class);
if (dtmService == null) {
return false;
}
dtmService.setDataTypeSelected(composite);
if (!fromSelectionChange) {
dtmService.edit(composite, getFieldName());
}
return true;
}
protected abstract String getFieldName();
protected void editComposite(DataTypeManagerService dtmService) {
dtmService.edit(composite);
}
}

View file

@ -0,0 +1,121 @@
/* ###
* 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.features.base.replace.items;
import java.util.Map;
import ghidra.app.services.DataTypeManagerService;
import ghidra.features.base.quickfix.QuickFixStatus;
import ghidra.features.base.replace.RenameQuickFix;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.Category;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.util.InvalidNameException;
import ghidra.util.exception.DuplicateNameException;
/**
* QuickFix for renaming datatype categories.
*/
public class RenameCategoryQuickFix extends RenameQuickFix {
private Category category;
/**
* Constructor
* @param program the program containing the category to be renamed
* @param category the category to be renamed
* @param newName the new name for the category
*/
public RenameCategoryQuickFix(Program program, Category category, String newName) {
super(program, category.getName(), newName);
this.category = category;
checkForDuplicates();
}
private void checkForDuplicates() {
Category parent = category.getParent();
if (parent == null) {
return;
}
if (parent.getCategory(replacement) != null) {
setStatus(QuickFixStatus.WARNING,
"The name \"" + replacement + "\" already exists in category \"" +
parent.getCategoryPathName() + "\"");
}
}
@Override
public void statusChanged(QuickFixStatus newStatus) {
if (newStatus == QuickFixStatus.NONE) {
checkForDuplicates();
}
}
@Override
public String getItemType() {
return "datatype category";
}
@Override
public Address getAddress() {
return null;
}
@Override
public String getPath() {
return category.getParent().getCategoryPathName();
}
@Override
protected String doGetCurrent() {
return category.getName();
}
@Override
protected void execute() {
try {
category.setName(replacement);
}
catch (DuplicateNameException | InvalidNameException e) {
setStatus(QuickFixStatus.ERROR, "Rename Failed! " + e.getMessage());
}
}
@Override
public ProgramLocation getProgramLocation() {
return null;
}
@Override
protected boolean navigateSpecial(ServiceProvider services, boolean fromSelectionChange) {
DataTypeManagerService dtmService = services.getService(DataTypeManagerService.class);
if (dtmService == null) {
return false;
}
dtmService.setCategorySelected(category);
return true;
}
@Override
public Map<String, String> getCustomToolTipData() {
return Map.of("Parent Path", category.getParent().getCategoryPathName());
}
}

View file

@ -0,0 +1,125 @@
/* ###
* 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.features.base.replace.items;
import java.util.Map;
import ghidra.app.services.DataTypeManagerService;
import ghidra.features.base.quickfix.QuickFixStatus;
import ghidra.features.base.replace.RenameQuickFix;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
/**
* QuickFix for renaming datatypes.
*/
public class RenameDataTypeQuickFix extends RenameQuickFix {
private DataType dataType;
/**
* Constructor
* @param program the program containing the datatype to be renamed
* @param dataType the datatype being renamed
* @param newName the new name for the datatype
*/
public RenameDataTypeQuickFix(Program program, DataType dataType, String newName) {
super(program, dataType.getName(), newName);
this.dataType = dataType;
if (!canRename()) {
setStatus(QuickFixStatus.ERROR, "This datatype doesn't support renaming");
}
checkDuplicate();
}
private void checkDuplicate() {
CategoryPath categoryPath = dataType.getCategoryPath();
DataTypeManager dtm = dataType.getDataTypeManager();
Category category = dtm.getCategory(categoryPath);
DataType existing = category.getDataType(replacement);
if (existing != null) {
setStatus(QuickFixStatus.WARNING, "Datatype with name \"" + replacement +
"\" already exists in category \"" + category.getCategoryPathName() + "\"");
}
}
@Override
public String getItemType() {
return "Datatype";
}
@Override
public Address getAddress() {
return null;
}
private boolean canRename() {
return !(dataType instanceof BuiltInDataType ||
dataType instanceof MissingBuiltInDataType || dataType instanceof Array ||
dataType instanceof Pointer);
}
@Override
public String getPath() {
return dataType.getCategoryPath().getPath();
}
@Override
public String doGetCurrent() {
if (dataType.isDeleted()) {
return null;
}
return dataType.getName();
}
@Override
public void execute() {
try {
dataType.setName(replacement);
}
catch (Exception e) {
setStatus(QuickFixStatus.ERROR, "Rename datatype failed: " + e.getMessage());
}
}
@Override
public ProgramLocation getProgramLocation() {
return null;
}
@Override
protected boolean navigateSpecial(ServiceProvider services, boolean fromSelectionChange) {
DataTypeManagerService dtmService = services.getService(DataTypeManagerService.class);
if (dtmService == null) {
return false;
}
dtmService.setDataTypeSelected(dataType);
if (!fromSelectionChange) {
dtmService.edit(dataType);
}
return true;
}
@Override
public Map<String, String> getCustomToolTipData() {
return Map.of("Category", dataType.getCategoryPath().toString());
}
}

View file

@ -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.features.base.replace.items;
import java.util.Map;
import ghidra.app.services.DataTypeManagerService;
import ghidra.features.base.quickfix.QuickFixStatus;
import ghidra.features.base.replace.RenameQuickFix;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.Enum;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
/**
* QuickFix for renaming enum values.
*/
public class RenameEnumValueQuickFix extends RenameQuickFix {
private Enum enumm;
private long enumValue;
/**
* Constructor
* @param program the program containing the enum to be renamed
* @param enumDt the enum whose value is being renamed
* @param valueName the enum value name being changed
* @param newName the new name for the enum value
*/
public RenameEnumValueQuickFix(Program program, Enum enumDt, String valueName,
String newName) {
super(program, valueName, newName);
this.enumm = enumDt;
this.enumValue = enumDt.getValue(valueName);
validate();
}
@Override
public String getItemType() {
return "Enum Value";
}
private void validate() {
if (enumm.contains(replacement)) {
setStatus(QuickFixStatus.WARNING,
"New name not allowed because it duplicates an existing value name");
}
}
@Override
protected void statusChanged(QuickFixStatus newStatus) {
if (newStatus == QuickFixStatus.NONE) {
validate();
}
}
@Override
public Address getAddress() {
return null;
}
@Override
public String getPath() {
return enumm.getPathName();
}
@Override
public String doGetCurrent() {
if (enumm.contains(original)) {
return original;
}
else if (enumm.contains(replacement)) {
return replacement;
}
return null;
}
@Override
public void execute() {
try {
enumm.add(replacement, enumValue);
enumm.remove(original);
}
catch (Exception e) {
setStatus(QuickFixStatus.ERROR, "Rename enum value failed: " + e.getMessage());
}
}
@Override
public ProgramLocation getProgramLocation() {
return null;
}
@Override
public Map<String, String> getCustomToolTipData() {
return Map.of("Enum", enumm.getPathName());
}
@Override
protected boolean navigateSpecial(ServiceProvider services, boolean fromSelectionChange) {
DataTypeManagerService dtmService = services.getService(DataTypeManagerService.class);
if (dtmService == null) {
return false;
}
dtmService.setDataTypeSelected(enumm);
if (!fromSelectionChange) {
dtmService.edit(enumm);
}
return true;
}
}

View file

@ -0,0 +1,89 @@
/* ###
* 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.features.base.replace.items;
import ghidra.features.base.quickfix.QuickFixStatus;
import ghidra.program.model.data.Composite;
import ghidra.program.model.data.DataTypeComponent;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
/**
* QuickFix for renaming structure or union fields
*/
public class RenameFieldQuickFix extends CompositeFieldQuickFix {
/**
* Constructor
* @param program the program containing the structure or union field to be renamed
* @param composite the composite whose field is being renamed
* @param ordinal the ordinal of the field being renamed with its containing composite
* @param original the original name of the field
* @param newName the new name for the enum value
*/
public RenameFieldQuickFix(Program program, Composite composite, int ordinal, String original,
String newName) {
super(program, composite, ordinal, original, newName);
}
@Override
public String getActionName() {
return "Rename";
}
@Override
public String getItemType() {
return "Field Name";
}
@Override
public String doGetCurrent() {
DataTypeComponent component = getComponent();
return component == null ? null : component.getFieldName();
}
private DataTypeComponent getComponent() {
DataTypeComponent component = findComponent(original);
if (component == null) {
component = findComponent(replacement);
}
return component;
}
@Override
public void execute() {
try {
DataTypeComponent component = getComponent();
if (component != null) {
component.setFieldName(replacement);
}
}
catch (Exception e) {
setStatus(QuickFixStatus.ERROR, "Rename field failed: " + e.getMessage());
}
}
@Override
public ProgramLocation getProgramLocation() {
return null;
}
@Override
protected String getFieldName() {
return current;
}
}

View file

@ -0,0 +1,79 @@
/* ###
* 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.features.base.replace.items;
import ghidra.features.base.quickfix.QuickFixStatus;
import ghidra.features.base.replace.RenameQuickFix;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.MemoryBlockStartFieldLocation;
import ghidra.program.util.ProgramLocation;
/**
* QuickFix for renaming memory blocks.
*/
public class RenameMemoryBlockQuickFix extends RenameQuickFix {
private MemoryBlock block;
/**
* Constructor
* @param program the program containing the memory block to be renamed
* @param block the memory block to be renamed
* @param newName the new name for the memory block
*/
public RenameMemoryBlockQuickFix(Program program, MemoryBlock block, String newName) {
super(program, block.getName(), newName);
this.block = block;
}
@Override
public String getItemType() {
return "Memory Block";
}
@Override
public String doGetCurrent() {
return block.getName();
}
@Override
public void execute() {
try {
block.setName(replacement);
}
catch (Exception e) {
setStatus(QuickFixStatus.ERROR, "Rename Failed! " + e);
}
}
@Override
public Address getAddress() {
return block.getStart();
}
@Override
public String getPath() {
return null;
}
@Override
public ProgramLocation getProgramLocation() {
return new MemoryBlockStartFieldLocation(program, getAddress(), null, 0, 0, null, 0);
}
}

View file

@ -0,0 +1,144 @@
/* ###
* 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.features.base.replace.items;
import java.util.Map;
import ghidra.app.services.ProgramTreeService;
import ghidra.features.base.quickfix.QuickFixStatus;
import ghidra.features.base.replace.RenameQuickFix;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.*;
import ghidra.program.util.ProgramLocation;
/**
* QuickFix for renaming program tree groups (modules or fragments)
*/
public class RenameProgramTreeGroupQuickFix extends RenameQuickFix {
private String path;
private Group group;
/**
* Constructor
* @param program the program containing the program tree group to be renamed
* @param group the program tree module or fragment to be renamed
* @param newName the new name for the memory block
*/
public RenameProgramTreeGroupQuickFix(Program program, Group group,
String newName) {
super(program, group.getName(), newName);
this.group = group;
this.path = computePath();
checkForDuplicates();
}
private void checkForDuplicates() {
ProgramModule[] parents = group.getParents();
if (parents != null) {
for (ProgramModule module : parents) {
if (module.getIndex(replacement) >= 0) {
setStatus(QuickFixStatus.WARNING,
"The name \"" + replacement + "\" already exists in module \"" +
module.getName() + "\"");
}
}
}
}
@Override
public void statusChanged(QuickFixStatus newStatus) {
if (newStatus == QuickFixStatus.NONE) {
checkForDuplicates();
}
}
@Override
public String getItemType() {
if (group instanceof ProgramFragment) {
return "Program Tree Fragment";
}
return "Program Tree Module";
}
private String computePath() {
StringBuilder builder = new StringBuilder();
computePath(group, builder);
return builder.toString();
}
private void computePath(Group treeGroup, StringBuilder builder) {
ProgramModule[] parents = treeGroup.getParents();
if (parents.length > 0) {
computePath(parents[0], builder);
builder.append("/");
}
builder.append(treeGroup.getName());
}
@Override
public String getPath() {
return path;
}
@Override
protected String doGetCurrent() {
if (group.isDeleted()) {
return null;
}
return group.getName();
}
@Override
protected void execute() {
try {
group.setName(replacement);
}
catch (Exception e) {
setStatus(QuickFixStatus.ERROR, "Rename Failed! " + e.getMessage());
}
}
@Override
public ProgramLocation getProgramLocation() {
if (getAddress() != null) {
return new ProgramLocation(program, getAddress());
}
return null;
}
@Override
public Address getAddress() {
return group.getMinAddress();
}
@Override
protected boolean navigateSpecial(ServiceProvider services, boolean fromSelectionChange) {
ProgramTreeService service = services.getService(ProgramTreeService.class);
if (service == null) {
return false;
}
service.setViewedTree(group.getTreeName());
service.setGroupSelection(group.getGroupPath());
return true;
}
@Override
public Map<String, String> getCustomToolTipData() {
return Map.of("Program Tree Path", path);
}
}

View file

@ -0,0 +1,136 @@
/* ###
* 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.features.base.replace.items;
import java.util.List;
import java.util.Map;
import ghidra.app.plugin.core.symboltree.SymbolTreeService;
import ghidra.features.base.quickfix.QuickFixStatus;
import ghidra.features.base.replace.RenameQuickFix;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.symbol.*;
import ghidra.program.util.ProgramLocation;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
/**
* QuickFix for renaming symbols (labels, functions, namespaces, classes, parameters, or
* local variables).
*/
public class RenameSymbolQuickFix extends RenameQuickFix {
private Symbol symbol;
/**
* Constructor
* @param symbol the symbol to be renamed
* @param newName the new name for the symbol
*/
public RenameSymbolQuickFix(Symbol symbol, String newName) {
super(symbol.getProgram(), symbol.getName(), newName);
this.symbol = symbol;
performDuplicateCheck();
}
@Override
public String getItemType() {
return symbol.getSymbolType().toString();
}
private void performDuplicateCheck() {
Namespace parentNamespace = symbol.getParentNamespace();
SymbolTable symbolTable = program.getSymbolTable();
List<Symbol> symbols = symbolTable.getSymbols(replacement, parentNamespace);
if (!symbols.isEmpty()) {
setStatus(QuickFixStatus.WARNING,
"There is already a symbol named \"" + replacement +
"\" in namespace \"" + parentNamespace.getName() + "\"");
}
}
@Override
public Address getAddress() {
Address address = symbol.getAddress();
if (address == Address.NO_ADDRESS) {
address = null;
}
return address;
}
@Override
public void statusChanged(QuickFixStatus newStatus) {
if (newStatus == QuickFixStatus.NONE) {
performDuplicateCheck();
}
}
@Override
public ProgramLocation getProgramLocation() {
return symbol.getProgramLocation();
}
@Override
public String getPath() {
Namespace namespace = symbol.getParentNamespace();
return namespace.getName(true);
}
@Override
public String doGetCurrent() {
if (symbol.isDeleted()) {
return null;
}
return symbol.getName();
}
@Override
public void execute() {
try {
symbol.setName(replacement, SourceType.USER_DEFINED);
}
catch (DuplicateNameException | InvalidInputException e) {
setStatus(QuickFixStatus.ERROR, "Rename Failed! " + e.getMessage());
}
}
@Override
public Map<String, String> getCustomToolTipData() {
SymbolType symbolType = symbol.getSymbolType();
Namespace parentNamespace = symbol.getParentNamespace();
if (symbolType == SymbolType.PARAMETER || symbolType == SymbolType.LOCAL_VAR) {
return Map.of("Function", parentNamespace.getName(true));
}
return Map.of("Namespace", parentNamespace.getName(false));
}
@Override
protected boolean navigateSpecial(ServiceProvider services, boolean fromSelectionChange) {
if (symbol.getAddress().isMemoryAddress()) {
return false; // let default navigation handle it
}
// This is a symbol that can't be shown in the listing, so directly request the
// symbol tree to select this symbol
SymbolTreeService service = services.getService(SymbolTreeService.class);
if (service != null) {
service.selectSymbol(symbol);
return true;
}
return false;
}
}

View file

@ -0,0 +1,96 @@
/* ###
* 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.features.base.replace.items;
import ghidra.features.base.quickfix.QuickFix;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.CommentType;
import ghidra.program.model.listing.Program;
import ghidra.program.util.*;
/**
* QuickFix for updating listing comments.
*/
public class UpdateCommentQuickFix extends QuickFix {
private Address address;
private CommentType type;
/**
* Constructor
* @param program the program containing the comment to be renamed
* @param address The address where the comment is located
* @param type the type of comment (Pre, Post, EOL, etc.)
* @param comment the original comment text
* @param newComment the new comment text
*/
public UpdateCommentQuickFix(Program program, Address address, CommentType type, String comment,
String newComment) {
super(program, comment, newComment);
this.address = address;
this.type = type;
}
@Override
public String getActionName() {
return "Update";
}
@Override
public String getItemType() {
return "Code Comment";
}
@Override
public String doGetCurrent() {
return program.getListing().getComment(type, address);
}
@Override
public void execute() {
program.getListing().setComment(address, type, replacement);
}
@Override
public Address getAddress() {
return address;
}
@Override
public ProgramLocation getProgramLocation() {
switch (type) {
case EOL:
return new EolCommentFieldLocation(program, address, null, null, 0, 0, 0);
case PLATE:
return new PlateFieldLocation(program, address, null, 0, 0, null, 0);
case POST:
return new PostCommentFieldLocation(program, address, null, null, 0, 0);
case PRE:
return new CommentFieldLocation(program, address, null, null, type.ordinal(), 0, 0);
case REPEATABLE:
return new RepeatableCommentFieldLocation(program, address, null, null, 0, 0, 0);
default:
return null;
}
}
@Override
public String getPath() {
return null;
}
}

View file

@ -0,0 +1,128 @@
/* ###
* 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.features.base.replace.items;
import java.util.Map;
import ghidra.app.services.DataTypeManagerService;
import ghidra.features.base.quickfix.QuickFix;
import ghidra.features.base.quickfix.QuickFixStatus;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.Composite;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.Enum;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
/**
* QuickFix for updating a datatype's description (Only supported on structures, unions, or enums)
*/
public class UpdateDataTypeDescriptionQuickFix extends QuickFix {
private DataType dataType;
/**
* Constructor
* @param program the program containing the datatype description to be updated.
* @param dataType the datatype being renamed
* @param newDescription the new name for the datatype
*/
public UpdateDataTypeDescriptionQuickFix(Program program, DataType dataType,
String newDescription) {
super(program, getDescription(dataType), newDescription);
this.dataType = dataType;
}
@Override
public String getItemType() {
return "Datatype Description";
}
@Override
public Address getAddress() {
return null;
}
@Override
public String getPath() {
return dataType.getCategoryPath().getPath();
}
@Override
public String doGetCurrent() {
if (dataType.isDeleted()) {
return null;
}
return getDescription(dataType);
}
private static String getDescription(DataType dt) {
if (dt instanceof Composite composite) {
return composite.getDescription();
}
if (dt instanceof Enum enumDataType) {
return enumDataType.getDescription();
}
return null;
}
@Override
public void execute() {
try {
if (dataType instanceof Composite composite) {
composite.setDescription(replacement);
}
else if (dataType instanceof Enum enumDataType) {
enumDataType.setDescription(replacement);
}
}
catch (Exception e) {
setStatus(QuickFixStatus.ERROR, "Rename datatype failed: " + e.getMessage());
}
}
@Override
public ProgramLocation getProgramLocation() {
return null;
}
@Override
protected boolean navigateSpecial(ServiceProvider services, boolean fromSelectionChange) {
DataTypeManagerService dtmService = services.getService(DataTypeManagerService.class);
if (dtmService == null) {
return false;
}
dtmService.setDataTypeSelected(dataType);
if (!fromSelectionChange) {
dtmService.edit(dataType);
}
return true;
}
@Override
public Map<String, String> getCustomToolTipData() {
return Map.of("Category", dataType.getCategoryPath().toString());
}
@Override
public String getActionName() {
return "Update";
}
}

View file

@ -0,0 +1,112 @@
/* ###
* 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.features.base.replace.items;
import java.util.Map;
import ghidra.app.services.DataTypeManagerService;
import ghidra.features.base.quickfix.QuickFix;
import ghidra.features.base.quickfix.QuickFixStatus;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.Enum;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
/**
* QuickFix for updating enum value comments
*/
public class UpdateEnumCommentQuickFix extends QuickFix {
private Enum enumm;
private String valueName;
/**
* Constructor
* @param program the program containing the enum value whose comment is to be updated
* @param enumDt the enum whose field value comment is to be changed
* @param valueName the enum value name whose comment is to be changed
* @param newComment the new comment for the enum value
*/
public UpdateEnumCommentQuickFix(Program program, Enum enumDt, String valueName,
String newComment) {
super(program, enumDt.getComment(valueName), newComment);
this.enumm = enumDt;
this.valueName = valueName;
}
@Override
public String getActionName() {
return "Update";
}
@Override
public String getItemType() {
return "Enum Comment";
}
@Override
public Address getAddress() {
return null;
}
@Override
public String getPath() {
return enumm.getCategoryPath().getPath();
}
@Override
public String doGetCurrent() {
return enumm.getComment(valueName);
}
@Override
public void execute() {
try {
long value = enumm.getValue(valueName);
enumm.remove(valueName);
enumm.add(valueName, value, replacement);
}
catch (Exception e) {
setStatus(QuickFixStatus.ERROR, "Update enum comment failed: " + e.getMessage());
}
}
@Override
public ProgramLocation getProgramLocation() {
return null;
}
@Override
protected boolean navigateSpecial(ServiceProvider services, boolean fromSelectionChange) {
DataTypeManagerService dtmService = services.getService(DataTypeManagerService.class);
if (dtmService == null) {
return false;
}
dtmService.setDataTypeSelected(enumm);
if (!fromSelectionChange) {
dtmService.edit(enumm);
}
return true;
}
@Override
public Map<String, String> getCustomToolTipData() {
return Map.of("Datatype", enumm.getPathName());
}
}

View file

@ -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.features.base.replace.items;
import ghidra.features.base.quickfix.QuickFixStatus;
import ghidra.program.model.data.Composite;
import ghidra.program.model.data.DataTypeComponent;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
/**
* QuickFix for updating structure or union field comments
*/
public class UpdateFieldCommentQuickFix extends CompositeFieldQuickFix {
private String fieldName;
/**
* Constructor
* @param program the program containing the enum value whose comment is to be updated
* @param composite the structure or union whose field comment is to be changed
* @param fieldName the field name whose comment is to be changed
* @param ordinal the ordinal of the field being renamed with its containing composite
* @param original the original comment of the field
* @param newComment the new comment for the field
*/
public UpdateFieldCommentQuickFix(Program program, Composite composite, String fieldName,
int ordinal, String original, String newComment) {
super(program, composite, ordinal, original, newComment);
this.fieldName = fieldName;
}
@Override
public String getActionName() {
return "Update";
}
@Override
public String getItemType() {
return "Field Comment";
}
@Override
public String doGetCurrent() {
DataTypeComponent component = findComponent(fieldName);
return component == null ? null : component.getComment();
}
@Override
public void execute() {
DataTypeComponent component = findComponent(fieldName);
try {
component.setComment(replacement);
}
catch (Exception e) {
setStatus(QuickFixStatus.ERROR, "Update field comment failed: " + e.getMessage());
}
}
@Override
public ProgramLocation getProgramLocation() {
return null;
}
@Override
protected String getFieldName() {
return fieldName;
}
}

View file

@ -639,7 +639,7 @@ public class ProgramBuilder {
});
}
public Namespace createClassNamespace(String name, String parentNamespace, SourceType type)
public GhidraClass createClassNamespace(String name, String parentNamespace, SourceType type)
throws Exception {
return tx(() -> {
Namespace ns = getNamespace(parentNamespace);

View file

@ -276,7 +276,7 @@ public class GhidraTable extends GTable {
* This method differs from {@link #navigate(int, int)} in that this method will not
* navigate if {@link #navigateOnSelection} is <code>false</code>.
*/
private void navigateOnCurrentSelection(int row, int column) {
protected void navigateOnCurrentSelection(int row, int column) {
if (!navigateOnSelection) {
return;
}

View file

@ -72,17 +72,22 @@ public class DeleteTableRowAction extends DockingAction {
}
public DeleteTableRowAction(GTable table, String owner) {
this(NAME, owner, DEFAULT_KEYSTROKE);
this(table, owner, null);
}
public DeleteTableRowAction(GTable table, String owner, String menuGroup) {
this(NAME, owner, DEFAULT_KEYSTROKE, menuGroup);
this.table = table;
}
private DeleteTableRowAction(String name, String owner, KeyStroke defaultkeyStroke) {
private DeleteTableRowAction(String name, String owner, KeyStroke defaultkeyStroke,
String menuGroup) {
super(name, owner, KeyBindingType.SHARED);
setDescription("Remove the selected rows from the table");
setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Remove_Items"));
setToolBarData(new ToolBarData(ICON, null));
setPopupMenuData(new MenuData(new String[] { "Remove Items" }, ICON, null));
setPopupMenuData(new MenuData(new String[] { "Remove Items" }, ICON, menuGroup));
initKeyStroke(defaultkeyStroke);
}

View file

@ -62,7 +62,7 @@ public class MakeProgramSelectionAction extends DockingAction {
public MakeProgramSelectionAction(String owner, GhidraTable table) {
super("Make Selection", owner, KeyBindingType.SHARED);
this.table = Objects.requireNonNull(table);
init();
init(null);
}
/**
@ -74,10 +74,24 @@ public class MakeProgramSelectionAction extends DockingAction {
* @param table the table needed for this action
*/
public MakeProgramSelectionAction(Navigatable navigatable, String owner, GhidraTable table) {
this(navigatable, owner, table, null);
}
/**
* Special constructor for clients that do not have a plugin. Clients using this
* constructor must override {@link #makeProgramSelection(ProgramSelection, ActionContext)}.
*
* @param navigatable the navigatable that will be used to make selections; may not be null
* @param owner the action's owner
* @param table the table needed for this action
* @param menuGroup The popup menu group for this action
*/
public MakeProgramSelectionAction(Navigatable navigatable, String owner, GhidraTable table,
String menuGroup) {
super("Make Selection", owner, KeyBindingType.SHARED);
this.navigatable = Objects.requireNonNull(navigatable);
this.table = Objects.requireNonNull(table);
init();
init(menuGroup);
}
/**
@ -91,12 +105,12 @@ public class MakeProgramSelectionAction extends DockingAction {
super("Make Selection", plugin.getName(), KeyBindingType.SHARED);
this.plugin = Objects.requireNonNull(plugin);
this.table = Objects.requireNonNull(table);
init();
init(null);
}
private void init() {
private void init(String menuGroup) {
setPopupMenuData(
new MenuData(new String[] { "Make Selection" }, Icons.MAKE_SELECTION_ICON));
new MenuData(new String[] { "Make Selection" }, Icons.MAKE_SELECTION_ICON, menuGroup));
setToolBarData(new ToolBarData(Icons.MAKE_SELECTION_ICON));
setDescription("Make a program selection from the selected rows");

View file

@ -100,7 +100,7 @@ public class TestDoubleDataTypeManagerService implements DataTypeManagerService
}
@Override
public void edit(Structure structure, String fieldName) {
public void edit(Composite composite, String fieldName) {
throw new UnsupportedOperationException();
}
@ -131,6 +131,11 @@ public class TestDoubleDataTypeManagerService implements DataTypeManagerService
throw new UnsupportedOperationException();
}
@Override
public void setCategorySelected(Category category) {
throw new UnsupportedOperationException();
}
@Override
public List<DataType> getSelectedDatatypes() {
throw new UnsupportedOperationException();

View file

@ -0,0 +1,201 @@
/* ###
* 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.features.base.replace;
import static org.junit.Assert.*;
import java.util.*;
import org.junit.Before;
import ghidra.features.base.quickfix.QuickFix;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.model.symbol.*;
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
import ghidra.util.datastruct.ListAccumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
/**
* Tests user input with search options for both one word searchers (renaming labels, datatypes,
* etc.) and updating multi-word matches (comments)
*/
public class AbstractSearchAndReplaceTest extends AbstractGhidraHeadedIntegrationTest {
protected static final boolean CASE_SENSITIVE_ON = true;
protected static final boolean CASE_SENSITIVE_OFF = false;
protected static final boolean WHOLE_WORD_ON = true;
protected static final boolean WHOLE_WORD_OFF = false;
protected static final int SEARCH_LIMIT = 100;
protected Program program;
protected ProgramBuilder builder;
private Set<SearchType> querySearchTypes = new HashSet<>();
protected SearchType labels;
protected SearchType functions;
protected SearchType namespaces;
protected SearchType classes;
protected SearchType parameters;
protected SearchType localVariables;
protected SearchType comments;
protected SearchType memoryBlocks;
protected SearchType dataTypes;
protected SearchType dataTypeComments;
protected SearchType fieldNames;
protected SearchType enumValues;
protected SearchType programTrees;
protected SearchType categories;
@Before
public void setUp() throws Exception {
program = buildProgram();
Map<String, SearchType> typesMap = gatherSearchTypes();
labels = typesMap.get("Labels");
namespaces = typesMap.get("Namespaces");
functions = typesMap.get("Functions");
classes = typesMap.get("Classes");
parameters = typesMap.get("Parameters");
localVariables = typesMap.get("Local Variables");
comments = typesMap.get("Comments");
memoryBlocks = typesMap.get("Memory Blocks");
dataTypes = typesMap.get("Datatypes");
dataTypeComments = typesMap.get("Datatype Comments");
fieldNames = typesMap.get("Datatype Fields");
enumValues = typesMap.get("Enum Values");
programTrees = typesMap.get("Program Trees");
categories = typesMap.get("Datatype Categories");
}
private Map<String, SearchType> gatherSearchTypes() {
Set<SearchType> searchTypes = SearchType.getSearchTypes();
Map<String, SearchType> typesMap = new HashMap<>();
for (SearchType searchType : searchTypes) {
typesMap.put(searchType.getName(), searchType);
}
return typesMap;
}
protected Program buildProgram() throws Exception {
builder = new ProgramBuilder("TestX86", ProgramBuilder._X86);
builder.createMemory(".text", Long.toHexString(000), 0x1000);
return builder.getProgram();
}
protected void assertQuickFix(long address, String original, String preview, QuickFix item) {
assertQuickFix(addr(address), original, preview, item);
}
protected void assertQuickFix(String original, String preview, QuickFix item) {
assertEquals(original, item.getOriginal());
assertEquals(preview, item.getPreview());
}
protected void assertQuickFix(Address address, String original, String preview, QuickFix item) {
assertEquals(address, item.getAddress());
assertEquals(original, item.getOriginal());
assertEquals(preview, item.getPreview());
}
protected Address addr(long address) {
return program.getAddressFactory().getDefaultAddressSpace().getAddress(address);
}
protected List<QuickFix> queryRegEx(String search, String replace, boolean caseSensitive)
throws CancelledException {
return query(search, replace, true, caseSensitive, false);
}
protected List<QuickFix> query(String search, String replace, boolean caseSensitive,
boolean wholeWord) throws CancelledException {
return query(search, replace, false, caseSensitive, wholeWord);
}
private List<QuickFix> query(String search, String replace, boolean isRegEx,
boolean isCaseSensitive, boolean isWholeWord) throws CancelledException {
SearchAndReplaceQuery query =
new SearchAndReplaceQuery(search, replace, querySearchTypes, isRegEx, isCaseSensitive,
isWholeWord, SEARCH_LIMIT);
ListAccumulator<QuickFix> accumulator = new ListAccumulator<>();
query.findAll(program, accumulator, TaskMonitor.DUMMY);
return accumulator.asList();
}
protected Symbol createLabel(long address, String name) {
return builder.createLabel(Long.toHexString(address), name);
}
protected Function createFunction(long address, String name, String... paramNames)
throws Exception {
Parameter[] params = createParameters(paramNames);
return builder.createEmptyFunction(name, Long.toHexString(address), 1,
new Integer16DataType(), params);
}
private Parameter[] createParameters(String[] paramNames) throws InvalidInputException {
Parameter[] params = new Parameter[paramNames.length];
for (int i = 0; i < paramNames.length; i++) {
params[i] = new ParameterImpl(paramNames[i], new ByteDataType(), program);
}
return params;
}
protected Namespace createNamespace(Namespace parent, String name) {
return builder.createNamespace(name, parent.getName(), SourceType.USER_DEFINED);
}
protected MemoryBlock createBlock(String name, int address) {
return builder.createMemory(name, Long.toHexString(address), 10);
}
protected GhidraClass createClass(Namespace parent, String name) throws Exception {
return builder.createClassNamespace(name, parent.getName(), SourceType.USER_DEFINED);
}
protected void createComment(long address, CommentType commentType, String comment) {
builder.createComment(Long.toHexString(address), comment, commentType.ordinal());
}
protected void setSearchTypes(SearchType... searchTypes) {
querySearchTypes.clear();
for (SearchType searchType : searchTypes) {
querySearchTypes.add(searchType);
}
}
protected void sortByAddress(List<QuickFix> results) {
Collections.sort(results, (a, b) -> a.getAddress().compareTo(b.getAddress()));
}
protected void sortByName(List<QuickFix> results) {
Collections.sort(results, (a, b) -> a.getOriginal().compareTo(b.getOriginal()));
}
protected void performAction(QuickFix item) {
program.withTransaction("test", () -> item.performAction());
}
protected DataType addDataType(DataType dt) {
DataTypeManager dtm = program.getDataTypeManager();
return program.withTransaction("test", () -> dtm.addDataType(dt, null));
}
}

View file

@ -0,0 +1,95 @@
/* ###
* 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.features.base.replace;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Test;
import ghidra.features.base.quickfix.QuickFix;
import ghidra.features.base.quickfix.QuickFixStatus;
import ghidra.program.model.data.CategoryPath;
public class CategoriesSearchAndReplaceTest extends AbstractSearchAndReplaceTest {
@Test
public void testSearchCategories() throws Exception {
builder.addCategory(new CategoryPath("/abc/foo1"));
builder.addCategory(new CategoryPath("/foo2/xxx"));
builder.addCategory(new CategoryPath("/abc/xxfoo3xx"));
setSearchTypes(categories);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(3, results.size());
sortByName(results);
assertQuickFix("foo1", "bar1", results.get(0));
assertQuickFix("foo2", "bar2", results.get(1));
assertQuickFix("xxfoo3xx", "xxbar3xx", results.get(2));
}
@Test
public void testRenamingCategory() throws Exception {
builder.addCategory(new CategoryPath("/abc/foo/def"));
setSearchTypes(categories);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("datatype category", item.getItemType());
assertEquals(null, item.getProgramLocation());
assertEquals("foo", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("bar", item.getCurrent());
assertNotNull(program.getDataTypeManager().getCategory(new CategoryPath("/abc/bar")));
}
@Test
public void testRenameCategoryDuplicate() throws Exception {
builder.addCategory(new CategoryPath("/abc/foo"));
builder.addCategory(new CategoryPath("/abc/bar"));
setSearchTypes(categories);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_ON);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.WARNING, item.getStatus());
assertEquals("The name \"bar\" already exists in category \"/abc\"",
item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("datatype category", item.getItemType());
assertEquals("foo", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.ERROR, item.getStatus());
assertEquals("Rename Failed! Category named bar already exists", item.getStatusMessage());
assertEquals("foo", item.getCurrent());
assertNotNull(program.getDataTypeManager().getCategory(new CategoryPath("/abc/foo")));
}
}

View file

@ -0,0 +1,454 @@
/* ###
* 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.features.base.replace;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Test;
import ghidra.features.base.quickfix.QuickFix;
import ghidra.features.base.quickfix.QuickFixStatus;
import ghidra.program.model.data.*;
import ghidra.program.model.data.Enum;
public class DataTypesSearchAndReplaceTest extends AbstractSearchAndReplaceTest {
@Test
public void testSearchDataTypes() throws Exception {
addDataType(new StructureDataType("fooStruct", 1));
addDataType(new UnionDataType("fooUnion"));
addDataType(new EnumDataType("fooEnum", 4));
addDataType(new TypedefDataType("fooTypeDef", new ByteDataType()));
setSearchTypes(dataTypes);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(4, results.size());
sortByName(results);
assertQuickFix("fooEnum", "barEnum", results.get(0));
assertQuickFix("fooStruct", "barStruct", results.get(1));
assertQuickFix("fooTypeDef", "barTypeDef", results.get(2));
assertQuickFix("fooUnion", "barUnion", results.get(3));
}
@Test
public void testRenamingDataType() throws Exception {
DataType dt = addDataType(new StructureDataType("fooStruct", 1));
setSearchTypes(dataTypes);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Datatype", item.getItemType());
assertEquals(null, item.getProgramLocation());
assertEquals("fooStruct", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("barStruct", item.getCurrent());
assertEquals("barStruct", dt.getName());
}
@Test
public void testRenamingDataTypeDuplicate() throws Exception {
DataType dt = addDataType(new StructureDataType("fooStruct", 1));
addDataType(new StructureDataType("barStruct", 1));
setSearchTypes(dataTypes);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.WARNING, item.getStatus());
assertEquals("Datatype with name \"barStruct\" already exists in category \"/\"",
item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Datatype", item.getItemType());
assertEquals(null, item.getProgramLocation());
assertEquals("fooStruct", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.ERROR, item.getStatus());
assertEquals(
"Rename datatype failed: DataType named barStruct already exists in category /",
item.getStatusMessage());
assertEquals("fooStruct", item.getCurrent());
assertEquals("fooStruct", dt.getName());
}
@Test
public void testDataTypeDescriptionsStruct() throws Exception {
StructureDataType struct = new StructureDataType("fooStruct", 1);
struct.setDescription("this is a foo description");
DataType structDB = addDataType(struct);
setSearchTypes(dataTypeComments);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Update", item.getActionName());
assertEquals("Datatype Description", item.getItemType());
assertEquals(null, item.getProgramLocation());
assertEquals("this is a foo description", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("this is a bar description", item.getCurrent());
assertEquals("this is a bar description", structDB.getDescription());
}
@Test
public void testDataTypeDescriptionsEnum() throws Exception {
EnumDataType enum1 = new EnumDataType("enum1", 4);
enum1.add("aaa", 1);
enum1.add("bbb", 2);
enum1.setDescription("this is a foo description");
DataType enumDB = addDataType(enum1);
setSearchTypes(dataTypeComments);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Update", item.getActionName());
assertEquals("Datatype Description", item.getItemType());
assertEquals(null, item.getProgramLocation());
assertEquals("this is a foo description", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("this is a bar description", item.getCurrent());
assertEquals("this is a bar description", enumDB.getDescription());
}
@Test
public void testSearchFieldNames() throws Exception {
StructureDataType dt1 = new StructureDataType("abc", 0);
dt1.add(new ByteDataType(), "fooStructField", null);
dt1.add(new ByteDataType(), "xxfooxxStructField", null);
UnionDataType dt2 = new UnionDataType("abc");
dt2.add(new ByteDataType(), "fooUnionField", null);
dt2.add(new ByteDataType(), "xxfooxxUnionField", null);
addDataType(dt1);
addDataType(dt2);
setSearchTypes(fieldNames);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(4, results.size());
sortByName(results);
assertQuickFix("fooStructField", "barStructField", results.get(0));
assertQuickFix("fooUnionField", "barUnionField", results.get(1));
assertQuickFix("xxfooxxStructField", "xxbarxxStructField", results.get(2));
assertQuickFix("xxfooxxUnionField", "xxbarxxUnionField", results.get(3));
}
@Test
public void testRenameStructureFieldNames() throws Exception {
StructureDataType struct = new StructureDataType("abc", 0);
struct.add(new ByteDataType(), "fooStructField", null);
Structure dt = (Structure) addDataType(struct);
setSearchTypes(fieldNames);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Field Name", item.getItemType());
assertEquals(null, item.getProgramLocation());
assertEquals("fooStructField", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("barStructField", item.getCurrent());
assertEquals("barStructField", dt.getComponent(0).getFieldName());
}
@Test
public void testRenameUnionFieldNames() throws Exception {
UnionDataType union = new UnionDataType("abc");
union.add(new ByteDataType(), "fooUnionField", null);
Union dt = (Union) addDataType(union);
setSearchTypes(fieldNames);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Field Name", item.getItemType());
assertEquals(null, item.getProgramLocation());
assertEquals("fooUnionField", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("barUnionField", item.getCurrent());
assertEquals("barUnionField", dt.getComponent(0).getFieldName());
}
@Test
public void testSearchFieldComments() throws Exception {
StructureDataType dt1 = new StructureDataType("abc", 0);
dt1.add(new ByteDataType(), "field1", "foo struct field1 comment");
dt1.add(new ByteDataType(), "field2", "foo struct field2 comment");
UnionDataType dt2 = new UnionDataType("abc");
dt2.add(new ByteDataType(), "field1", "foo union field1 comment");
dt2.add(new ByteDataType(), "field2", "foo union field2 comment");
addDataType(dt1);
addDataType(dt2);
setSearchTypes(dataTypeComments);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(4, results.size());
sortByName(results);
assertQuickFix("foo struct field1 comment", "bar struct field1 comment", results.get(0));
assertQuickFix("foo struct field2 comment", "bar struct field2 comment", results.get(1));
assertQuickFix("foo union field1 comment", "bar union field1 comment", results.get(2));
assertQuickFix("foo union field2 comment", "bar union field2 comment", results.get(3));
}
@Test
public void testUpdateStructureFieldComments() throws Exception {
StructureDataType dt1 = new StructureDataType("abc", 0);
dt1.add(new ByteDataType(), "field1", "foo struct field1 comment");
Structure dt = (Structure) addDataType(dt1);
setSearchTypes(dataTypeComments);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Update", item.getActionName());
assertEquals("Field Comment", item.getItemType());
assertEquals(null, item.getProgramLocation());
assertEquals("foo struct field1 comment", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("bar struct field1 comment", item.getCurrent());
assertEquals("bar struct field1 comment", dt.getComponent(0).getComment());
}
@Test
public void testUpdateUnionFieldComments() throws Exception {
UnionDataType dt1 = new UnionDataType("abc");
dt1.add(new ByteDataType(), "field1", "foo union field1 comment");
Union dt = (Union) addDataType(dt1);
setSearchTypes(dataTypeComments);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Update", item.getActionName());
assertEquals("Field Comment", item.getItemType());
assertEquals(null, item.getProgramLocation());
assertEquals("foo union field1 comment", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("bar union field1 comment", item.getCurrent());
assertEquals("bar union field1 comment", dt.getComponent(0).getComment());
}
@Test
public void testSearchEnumValueNames() throws Exception {
EnumDataType enum1 = new EnumDataType("enum1", 4);
enum1.add("foo", 1);
enum1.add("xxxfoo", 2);
EnumDataType enum2 = new EnumDataType("enum2", 4);
enum1.add("fooEnum2", 1);
enum1.add("xxxfooEnum2", 2);
addDataType(enum1);
addDataType(enum2);
setSearchTypes(enumValues);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(4, results.size());
sortByName(results);
assertQuickFix("foo", "bar", results.get(0));
assertQuickFix("fooEnum2", "barEnum2", results.get(1));
assertQuickFix("xxxfoo", "xxxbar", results.get(2));
assertQuickFix("xxxfooEnum2", "xxxbarEnum2", results.get(3));
}
@Test
public void testRenameEnumValueName() throws Exception {
EnumDataType enum1 = new EnumDataType("enum1", 4);
enum1.add("foo", 1);
enum1.add("xxx", 2);
Enum dt = (Enum) addDataType(enum1);
setSearchTypes(enumValues);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Enum Value", item.getItemType());
assertEquals(null, item.getProgramLocation());
assertEquals("foo", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("bar", item.getCurrent());
assertEquals("bar", dt.getName(1));
}
@Test
public void testRenameEnumValueNameDuplicate() throws Exception {
EnumDataType enum1 = new EnumDataType("enum1", 4);
enum1.add("foo", 1);
enum1.add("bar", 2);
Enum dt = (Enum) addDataType(enum1);
setSearchTypes(enumValues);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.WARNING, item.getStatus());
assertEquals("New name not allowed because it duplicates an existing value name",
item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Enum Value", item.getItemType());
assertEquals(null, item.getProgramLocation());
assertEquals("foo", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.ERROR, item.getStatus());
assertEquals("Rename enum value failed: bar already exists in this enum",
item.getStatusMessage());
assertEquals("foo", item.getCurrent());
assertEquals("foo", dt.getName(1));
}
@Test
public void testSearchEnumValueComments() throws Exception {
EnumDataType enum1 = new EnumDataType("enum1", 4);
enum1.add("ONE", 1, "ONE foo comment");
enum1.add("TWO", 2, "TWO foo comment");
EnumDataType enum2 = new EnumDataType("enum2", 4);
enum1.add("THREE", 3, "THREE foo comment");
enum1.add("FOUR", 4, "FOUR foo comment");
addDataType(enum1);
addDataType(enum2);
setSearchTypes(dataTypeComments);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(4, results.size());
sortByName(results);
assertQuickFix("FOUR foo comment", "FOUR bar comment", results.get(0));
assertQuickFix("ONE foo comment", "ONE bar comment", results.get(1));
assertQuickFix("THREE foo comment", "THREE bar comment", results.get(2));
assertQuickFix("TWO foo comment", "TWO bar comment", results.get(3));
}
@Test
public void testUpdateEnumValueComments() throws Exception {
EnumDataType enum1 = new EnumDataType("enum1", 4);
enum1.add("ONE", 1, "ONE foo comment");
Enum dt = (Enum) addDataType(enum1);
setSearchTypes(dataTypeComments);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Update", item.getActionName());
assertEquals("Enum Comment", item.getItemType());
assertEquals(null, item.getProgramLocation());
assertEquals("ONE foo comment", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("ONE bar comment", item.getCurrent());
assertEquals("ONE bar comment", dt.getComment("ONE"));
}
}

View file

@ -0,0 +1,194 @@
/* ###
* 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.features.base.replace;
import static ghidra.program.model.listing.CommentType.*;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Test;
import ghidra.features.base.quickfix.QuickFix;
import ghidra.features.base.quickfix.QuickFixStatus;
import ghidra.program.util.*;
public class ListingCommentsSearchAndReplaceTest extends AbstractSearchAndReplaceTest {
@Test
public void testSearchComments() throws Exception {
createComment(10, EOL, "EOLxxx abcxxxdef");
createComment(20, PLATE, "PLATE xxx");
createComment(30, PRE, "xxx PRE");
createComment(30, POST, "POST abcxxxdef");
createComment(30, REPEATABLE, "REPEATABLE xxx");
setSearchTypes(comments);
List<QuickFix> results = query("xxx", "zzz", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(5, results.size());
sortByAddress(results);
assertQuickFix(10, "EOLxxx abcxxxdef", "EOLzzz abczzzdef", results.get(0));
assertQuickFix(20, "PLATE xxx", "PLATE zzz", results.get(1));
assertQuickFix(30, "xxx PRE", "zzz PRE", results.get(2));
assertQuickFix(30, "POST abcxxxdef", "POST abczzzdef", results.get(3));
assertQuickFix(30, "REPEATABLE xxx", "REPEATABLE zzz", results.get(4));
}
@Test
public void testChangingListingEolComments() throws Exception {
createComment(10, EOL, "EOL xxx abcxxxdef");
setSearchTypes(comments);
List<QuickFix> results = query("xxx", "zzz", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Update", item.getActionName());
assertEquals("Code Comment", item.getItemType());
assertEquals(new EolCommentFieldLocation(program, addr(10), null, null, 0, 0, 0),
item.getProgramLocation());
assertEquals("EOL xxx abcxxxdef", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("EOL zzz abczzzdef", item.getCurrent());
}
@Test
public void testChangingListingPostComments() throws Exception {
createComment(10, POST, "POST xxx abcxxxdef");
setSearchTypes(comments);
List<QuickFix> results = query("xxx", "zzz", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Update", item.getActionName());
assertEquals("Code Comment", item.getItemType());
assertEquals(new PostCommentFieldLocation(program, addr(10), null, null, 0, 0),
item.getProgramLocation());
assertEquals("POST xxx abcxxxdef", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("POST zzz abczzzdef", item.getCurrent());
}
@Test
public void testChangingListingPlateComments() throws Exception {
createComment(10, PLATE, "PLATE xxx abcxxxdef");
setSearchTypes(comments);
List<QuickFix> results = query("xxx", "zzz", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Update", item.getActionName());
assertEquals("Code Comment", item.getItemType());
assertEquals(new PlateFieldLocation(program, addr(10), null, 0, 0, null, 0),
item.getProgramLocation());
assertEquals("PLATE xxx abcxxxdef", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("PLATE zzz abczzzdef", item.getCurrent());
}
@Test
public void testChangingListingRepeatableComments() throws Exception {
createComment(10, REPEATABLE, "REPEATABLE xxx abcxxxdef");
setSearchTypes(comments);
List<QuickFix> results = query("xxx", "zzz", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Update", item.getActionName());
assertEquals("Code Comment", item.getItemType());
assertEquals(new RepeatableCommentFieldLocation(program, addr(10), null, null, 0, 0, 0),
item.getProgramLocation());
assertEquals("REPEATABLE xxx abcxxxdef", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("REPEATABLE zzz abczzzdef", item.getCurrent());
}
@Test
public void testChangingListingPreComments() throws Exception {
createComment(10, PRE, "PRE xxx abcxxxdef");
setSearchTypes(comments);
List<QuickFix> results = query("xxx", "zzz", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Update", item.getActionName());
assertEquals("Code Comment", item.getItemType());
assertEquals(new CommentFieldLocation(program, addr(10), null, null, PRE.ordinal(), 0, 0),
item.getProgramLocation());
assertEquals("PRE xxx abcxxxdef", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("PRE zzz abczzzdef", item.getCurrent());
}
@Test
public void testChangingListingCommentsWholeWordOn() throws Exception {
createComment(10, EOL, "EOL xxx abcxxxdef");
setSearchTypes(comments);
List<QuickFix> results = query("xxx", "zzz", CASE_SENSITIVE_OFF, WHOLE_WORD_ON);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Update", item.getActionName());
assertEquals("Code Comment", item.getItemType());
assertEquals(addr(10), item.getProgramLocation().getAddress());
assertEquals("EOL xxx abcxxxdef", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("EOL zzz abcxxxdef", item.getCurrent());
}
}

View file

@ -0,0 +1,95 @@
/* ###
* 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.features.base.replace;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Test;
import ghidra.features.base.quickfix.QuickFix;
import ghidra.features.base.quickfix.QuickFixStatus;
import ghidra.program.model.mem.MemoryBlock;
import ghidra.program.util.MemoryBlockStartFieldLocation;
public class MemoryBlockSearchAndReplaceTest extends AbstractSearchAndReplaceTest {
@Test
public void testSearchMemoryBlocks() throws Exception {
createBlock("foo", 0x10000);
createBlock("xxfooxx", 0x20000);
setSearchTypes(memoryBlocks);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(2, results.size());
sortByName(results);
assertQuickFix(0x10000, "foo", "bar", results.get(0));
assertQuickFix(0x20000, "xxfooxx", "xxbarxx", results.get(1));
}
@Test
public void testRenamingMemoryBlock() throws Exception {
MemoryBlock foo = createBlock("foo", 0x10000);
setSearchTypes(memoryBlocks);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Memory Block", item.getItemType());
assertEquals(new MemoryBlockStartFieldLocation(program, addr(0x10000), null, 0, 0, null, 0),
item.getProgramLocation());
assertEquals("foo", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("bar", item.getCurrent());
assertEquals("bar", foo.getName());
}
@Test
public void testRenameMemoryBlockDuplicateOk() throws Exception {
MemoryBlock foo = createBlock("foo", 0x10000);
createBlock("bar", 0x20000);
setSearchTypes(memoryBlocks);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_ON);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Memory Block", item.getItemType());
assertEquals("foo", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("bar", item.getCurrent());
assertEquals("bar", foo.getName());
}
}

View file

@ -0,0 +1,176 @@
/* ###
* 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.features.base.replace;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Test;
import ghidra.features.base.quickfix.QuickFix;
import ghidra.features.base.quickfix.QuickFixStatus;
import ghidra.program.model.listing.*;
import ghidra.program.util.ProgramLocation;
public class ProgramTreeSearchAndReplaceTest extends AbstractSearchAndReplaceTest {
@Test
public void testSearchMemoryBlocks() throws Exception {
builder.createProgramTree("abc");
builder.getOrCreateModule("abc", "foo");
createFragment("abc", "foo", "xxfooxx", 10, 20);
setSearchTypes(programTrees);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(2, results.size());
sortByName(results);
assertQuickFix("foo", "bar", results.get(0));
assertQuickFix("xxfooxx", "xxbarxx", results.get(1));
}
@Test
public void testRenamingProgramTreeModule() throws Exception {
builder.createProgramTree("abc");
ProgramModule module = builder.getOrCreateModule("abc", "foo");
createFragment("abc", "foo", "frag1", 10, 20);
setSearchTypes(programTrees);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Program Tree Module", item.getItemType());
assertEquals(new ProgramLocation(program, addr(10)), item.getProgramLocation());
assertEquals("foo", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("bar", item.getCurrent());
assertEquals("bar", module.getName());
}
@Test
public void testRenamingProgramTreeFragment() throws Exception {
builder.createProgramTree("abc");
builder.getOrCreateModule("abc", "xxx");
ProgramFragment fragment = createFragment("abc", "xxx", "foo", 10, 20);
setSearchTypes(programTrees);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Program Tree Fragment", item.getItemType());
assertEquals(new ProgramLocation(program, addr(10)), item.getProgramLocation());
assertEquals("foo", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("bar", item.getCurrent());
assertEquals("bar", fragment.getName());
}
@Test
public void testRenameModuleDuplicate() throws Exception {
builder.createProgramTree("abc");
ProgramModule module = builder.getOrCreateModule("abc", "foo");
createFragment("abc", "foo", "frag1", 10, 20);
builder.getOrCreateModule("abc", "bar");
createFragment("abc", "bar", "frag2", 30, 40);
setSearchTypes(programTrees);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.WARNING, item.getStatus());
assertEquals("The name \"bar\" already exists in module \"TestX86\"",
item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Program Tree Module", item.getItemType());
assertEquals(new ProgramLocation(program, addr(10)), item.getProgramLocation());
assertEquals("foo", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.ERROR, item.getStatus());
assertEquals("Rename Failed! bar already exists", item.getStatusMessage());
assertEquals("foo", item.getCurrent());
assertEquals("foo", module.getName());
}
@Test
public void testRenameFragmentDuplicate() throws Exception {
builder.createProgramTree("abc");
builder.getOrCreateModule("abc", "module1");
ProgramFragment fragment = createFragment("abc", "module1", "foo", 10, 20);
createFragment("abc", "module1", "bar", 30, 40);
setSearchTypes(programTrees);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.WARNING, item.getStatus());
assertEquals("The name \"bar\" already exists in module \"module1\"",
item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Program Tree Fragment", item.getItemType());
assertEquals(new ProgramLocation(program, addr(10)), item.getProgramLocation());
assertEquals("foo", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.ERROR, item.getStatus());
assertEquals("Rename Failed! bar already exists", item.getStatusMessage());
assertEquals("foo", item.getCurrent());
assertEquals("foo", fragment.getName());
}
private ProgramFragment createFragment(String treeName, String moduleName, String fragmentName,
int start, int end) throws Exception {
String startAddress = Long.toHexString(start);
String endAddress = Long.toHexString(end);
builder.createFragment(treeName, moduleName, fragmentName, startAddress, endAddress);
Group[] children = program.getListing().getRootModule(treeName).getChildren();
for (Group group : children) {
if (group.getName().equals(moduleName) && group instanceof ProgramModule module) {
Group[] grandChildren = module.getChildren();
for (Group child : grandChildren) {
if (child.getName().equals(fragmentName) &&
child instanceof ProgramFragment fragment) {
return fragment;
}
}
}
}
return null;
}
}

View file

@ -0,0 +1,200 @@
/* ###
* 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.features.base.replace;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.*;
import docking.ActionContext;
import docking.action.DockingActionIf;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.features.base.quickfix.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.database.ProgramDB;
import ghidra.program.database.symbol.SymbolManager;
import ghidra.program.model.address.Address;
import ghidra.program.model.symbol.Symbol;
import ghidra.test.*;
public class SearchAndReplaceDialogTest extends AbstractGhidraHeadedIntegrationTest {
private TestEnv env;
private PluginTool tool;
private ProgramDB program;
private DockingActionIf searchAndReplaceAction;
private SearchAndReplaceDialog dialog;
@Before
public void setUp() throws Exception {
env = new TestEnv();
tool = env.getTool();
tool.addPlugin(SearchAndReplacePlugin.class.getName());
tool.addPlugin(CodeBrowserPlugin.class.getName());
SearchAndReplacePlugin plugin = getPlugin(tool, SearchAndReplacePlugin.class);
ToyProgramBuilder builder = new ToyProgramBuilder("Test", true);
builder.createLabel("0x100", "myFooLabel");
builder.createLabel("0x200", "myBarLabel");
program = builder.getProgram();
env.open(program);
env.showTool();
searchAndReplaceAction = getAction(plugin, "Search And Replace");
ActionContext actionContext = tool.getActiveComponentProvider().getActionContext(null);
performAction(searchAndReplaceAction, actionContext, false);
dialog = waitForDialogComponent(SearchAndReplaceDialog.class);
assertNotNull(dialog);
}
@After
public void tearDown() throws Exception {
env.dispose();
}
@Test
public void testBasicEnablementAndStatus() {
assertEquals("Please enter search text", getStatusText());
assertFalse(isOkEnabled());
enterText("search text", "replace text");
assertFalse(isOkEnabled());
assertEquals("Please select at least one \"search for\" item to search!", getStatusText());
selectSearchType("Labels");
assertEquals("", getStatusText());
assertTrue(isOkEnabled());
}
@Test
public void testInvalidRegex() {
enterText("(abc", ""); // "(abc" is valid for normal search, invalid for regex
selectSearchType("Labels");
assertEquals("", getStatusText());
assertTrue(isOkEnabled());
selectRegEx(true);
assertEquals("", getStatusText());
assertFalse(isOkEnabled());
selectRegEx(false);
assertEquals("", getStatusText());
assertTrue(isOkEnabled());
}
@Test
public void testResultsProviderAppearsWithResults() {
SearchAndReplaceProvider provider = executeBasicQuery();
List<QuickFix> data = provider.getTableModel().getModelData();
assertEquals(2, data.size());
assertEquals(QuickFixStatus.NONE, data.get(0).getStatus());
assertEquals(QuickFixStatus.NONE, data.get(1).getStatus());
}
@Test
public void testApplyResults() {
SearchAndReplaceProvider provider = executeBasicQuery();
List<QuickFix> data = provider.getTableModel().getModelData();
assertEquals(2, data.size());
executeAllItems(provider);
assertEquals(QuickFixStatus.DONE, data.get(0).getStatus());
assertEquals(QuickFixStatus.DONE, data.get(1).getStatus());
assertEquals("yourFooLabel", getSymbol(0x100).getName());
assertEquals("yourBarLabel", getSymbol(0x200).getName());
}
@Test
public void testApplyResultsToJustSelectedItem() {
SearchAndReplaceProvider provider = executeBasicQuery();
List<QuickFix> data = provider.getTableModel().getModelData();
assertEquals(2, data.size());
selectItem(provider, 0);
executeSelectedItems(provider);
assertEquals(QuickFixStatus.DONE, data.get(0).getStatus());
assertEquals(QuickFixStatus.NONE, data.get(1).getStatus());
assertEquals("myFooLabel", getSymbol(0x100).getName());
assertEquals("yourBarLabel", getSymbol(0x200).getName());
assertEquals(1, getSelectedRow(provider));
}
private int getSelectedRow(SearchAndReplaceProvider provider) {
return runSwing(() -> provider.getSelectedRow());
}
private void selectItem(SearchAndReplaceProvider provider, int index) {
runSwing(() -> provider.setSelection(index, index));
}
private Symbol getSymbol(long offset) {
SymbolManager symbolTable = program.getSymbolTable();
Symbol primarySymbol = symbolTable.getPrimarySymbol(addr(offset));
return primarySymbol;
}
private void executeAllItems(SearchAndReplaceProvider provider) {
runSwing(() -> provider.executeAll());
waitForTasks();
}
private void executeSelectedItems(SearchAndReplaceProvider provider) {
runSwing(() -> provider.applySelected());
waitForTasks();
}
private Address addr(long offset) {
return program.getAddressFactory().getDefaultAddressSpace().getAddress(offset);
}
private SearchAndReplaceProvider executeBasicQuery() {
enterText("my", "your");
selectSearchType("Labels");
pressOk();
SearchAndReplaceProvider provider =
waitForComponentProvider(SearchAndReplaceProvider.class);
assertNotNull(provider);
QuickFixTableModel tableModel = provider.getTableModel();
waitForTableModel(tableModel);
return provider;
}
private void pressOk() {
runSwing(() -> dialog.okCallback());
}
private void selectRegEx(boolean b) {
runSwing(() -> dialog.selectRegEx(b));
}
private boolean isOkEnabled() {
return runSwing(() -> dialog.isOkEnabled());
}
private void selectSearchType(String searchType) {
runSwing(() -> dialog.selectSearchType(searchType));
}
private void enterText(String searchText, String replaceText) {
runSwing(() -> dialog.setSarchAndReplaceText(searchText, replaceText));
}
private String getStatusText() {
return runSwing(() -> dialog.getStatusText().trim());
}
}

View file

@ -0,0 +1,553 @@
/* ###
* 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.features.base.replace;
import static org.junit.Assert.*;
import java.util.List;
import org.junit.Test;
import ghidra.features.base.quickfix.QuickFix;
import ghidra.features.base.quickfix.QuickFixStatus;
import ghidra.program.model.data.ByteDataType;
import ghidra.program.model.data.DataType;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Namespace;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.util.LabelFieldLocation;
import ghidra.util.exception.CancelledException;
public class SymbolsSearchAndReplaceTest extends AbstractSearchAndReplaceTest {
@Test
public void testLabelsSearchNotCaseSensitive() throws CancelledException {
createLabel(10, "foo");
createLabel(20, "fooxxxx");
createLabel(30, "xxxFoox");
createLabel(40, "xxxxfOO");
createLabel(50, "xxxfoxo");
setSearchTypes(labels);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(4, results.size());
sortByAddress(results);
assertQuickFix(10, "foo", "bar", results.get(0));
assertQuickFix(20, "fooxxxx", "barxxxx", results.get(1));
assertQuickFix(30, "xxxFoox", "xxxbarx", results.get(2));
assertQuickFix(40, "xxxxfOO", "xxxxbar", results.get(3));
}
@Test
public void testLabelsSearchWholeWord() throws CancelledException {
createLabel(10, "foo");
createLabel(20, "fooxxxx");
createLabel(30, "xxxFoox");
createLabel(40, "xxxxfOO");
createLabel(50, "xxxfoxo");
setSearchTypes(labels);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_ON);
assertEquals(1, results.size());
assertQuickFix(10, "foo", "bar", results.get(0));
}
@Test
public void testLabelsSearchCaseSensitiveSearch() throws CancelledException {
createLabel(10, "fooxxxx");
createLabel(20, "Fooxxxx");
createLabel(30, "xxFOOxxx");
createLabel(40, "foo");
createLabel(50, "xxxfoo");
setSearchTypes(labels);
List<QuickFix> results = query("Foo", "bar", CASE_SENSITIVE_ON, WHOLE_WORD_OFF);
assertEquals(1, results.size());
assertQuickFix(20, "Fooxxxx", "barxxxx", results.get(0));
}
@Test
public void testLabelsSearchRegEx() throws CancelledException {
createLabel(10, "fooxxxx");
createLabel(20, "Fooxxxx");
createLabel(30, "xxFOOxxx");
createLabel(40, "foo");
createLabel(50, "xxxfoo");
setSearchTypes(labels);
List<QuickFix> results = queryRegEx("^Foo$", "bar", CASE_SENSITIVE_OFF);
assertEquals(1, results.size());
assertQuickFix(40, "foo", "bar", results.get(0));
}
@Test
public void testLabelsSearchRegExCaptureGroups() throws CancelledException {
createLabel(10, "fooxxxx");
createLabel(20, "Fooxxxx");
createLabel(30, "xxFOOxxx");
createLabel(40, "foo");
createLabel(50, "xxBARxxx");
setSearchTypes(labels);
List<QuickFix> results = queryRegEx("xx(.*)xxx", "zz$1zzz", CASE_SENSITIVE_OFF);
sortByAddress(results);
assertEquals(2, results.size());
assertQuickFix(30, "xxFOOxxx", "zzFOOzzz", results.get(0));
assertQuickFix(50, "xxBARxxx", "zzBARzzz", results.get(1));
}
@Test
public void testRenamingLabel() throws CancelledException {
Symbol s = createLabel(10, "foo");
createLabel(20, "fooxxxx");
createLabel(30, "xxxFoox");
createLabel(40, "xxxxfOO");
createLabel(50, "xxxfoxo");
setSearchTypes(labels);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_ON);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Label", item.getItemType());
assertEquals("Global", item.getPath());
assertEquals(new LabelFieldLocation(s), item.getProgramLocation());
assertEquals("foo", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("bar", item.getCurrent());
}
@Test
public void testRenameLabelDuplicate() throws CancelledException {
Symbol s = createLabel(10, "foo");
createLabel(20, "bar");
setSearchTypes(labels);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_ON);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.WARNING, item.getStatus());
assertEquals("There is already a symbol named \"bar\" in namespace \"Global\"",
item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Label", item.getItemType());
assertEquals("Global", item.getPath());
assertEquals(new LabelFieldLocation(s), item.getProgramLocation());
assertEquals("foo", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("bar", item.getCurrent());
}
@Test
public void testSearchFunctions() throws Exception {
createFunction(10, "foo");
createFunction(20, "fooxxxx");
createFunction(30, "xxxFoox");
createFunction(40, "xxxxfOO");
createFunction(50, "xxxfoxo");
setSearchTypes(functions);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(4, results.size());
sortByAddress(results);
assertQuickFix(10, "foo", "bar", results.get(0));
assertQuickFix(20, "fooxxxx", "barxxxx", results.get(1));
assertQuickFix(30, "xxxFoox", "xxxbarx", results.get(2));
assertQuickFix(40, "xxxxfOO", "xxxxbar", results.get(3));
}
@Test
public void testRenamingFunction() throws Exception {
Function function = createFunction(10, "foo");
setSearchTypes(functions);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_ON);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Function", item.getItemType());
assertEquals("Global", item.getPath());
assertEquals(function.getSymbol().getProgramLocation(), item.getProgramLocation());
assertEquals("foo", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("bar", item.getCurrent());
assertEquals("bar", function.getName());
}
@Test
public void testRenameFunctionDuplicate() throws Exception {
Function function = createFunction(10, "foo");
createFunction(20, "bar");
setSearchTypes(functions);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_ON);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.WARNING, item.getStatus());
assertEquals("There is already a symbol named \"bar\" in namespace \"Global\"",
item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Function", item.getItemType());
assertEquals("Global", item.getPath());
assertEquals(function.getSymbol().getProgramLocation(), item.getProgramLocation());
assertEquals("foo", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("bar", item.getCurrent());
assertEquals("bar", function.getName());
}
@Test
public void testSearchNamespaces() throws Exception {
Namespace global = program.getGlobalNamespace();
Namespace aaa = createNamespace(global, "aaa");
createNamespace(global, "foo");
createNamespace(global, "fooxxxx");
createNamespace(aaa, "xxxFoox");
createNamespace(aaa, "xxxxfOO");
createNamespace(global, "xxxfoxo");
setSearchTypes(namespaces);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(4, results.size());
sortByName(results);
assertQuickFix("foo", "bar", results.get(0));
assertQuickFix("fooxxxx", "barxxxx", results.get(1));
assertQuickFix("xxxFoox", "xxxbarx", results.get(2));
assertQuickFix("xxxxfOO", "xxxxbar", results.get(3));
}
@Test
public void testRenamingNamespace() throws Exception {
Namespace global = program.getGlobalNamespace();
Namespace foo = createNamespace(global, "foo");
setSearchTypes(namespaces);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_ON);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Namespace", item.getItemType());
assertEquals("Global", item.getPath());
assertEquals(foo.getSymbol().getProgramLocation(), item.getProgramLocation());
assertEquals("foo", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("bar", item.getCurrent());
assertEquals("bar", foo.getName());
}
@Test
public void testRenameNamespaceDuplicate() throws Exception {
Namespace global = program.getGlobalNamespace();
Namespace foo = createNamespace(global, "foo");
createNamespace(global, "bar");
setSearchTypes(namespaces);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_ON);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.WARNING, item.getStatus());
assertEquals("There is already a symbol named \"bar\" in namespace \"Global\"",
item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Namespace", item.getItemType());
assertEquals("Global", item.getPath());
assertEquals(foo.getSymbol().getProgramLocation(), item.getProgramLocation());
assertEquals("foo", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.ERROR, item.getStatus());
assertEquals(
"Rename Failed! A Namespace symbol with name bar already exists in namespace Global",
item.getStatusMessage());
assertEquals("foo", item.getCurrent());
assertEquals("foo", foo.getName());
}
@Test
public void testSearchClasses() throws Exception {
Namespace global = program.getGlobalNamespace();
Namespace aaa = createNamespace(global, "aaa");
createClass(global, "foo");
createClass(global, "fooxxxx");
createClass(aaa, "xxxFoox");
createClass(aaa, "xxxxfOO");
createClass(global, "xxxfoxo");
setSearchTypes(classes);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(4, results.size());
sortByName(results);
assertQuickFix("foo", "bar", results.get(0));
assertQuickFix("fooxxxx", "barxxxx", results.get(1));
assertQuickFix("xxxFoox", "xxxbarx", results.get(2));
assertQuickFix("xxxxfOO", "xxxxbar", results.get(3));
}
@Test
public void testRenamingClass() throws Exception {
Namespace global = program.getGlobalNamespace();
GhidraClass foo = createClass(global, "foo");
setSearchTypes(classes);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_ON);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Class", item.getItemType());
assertEquals("Global", item.getPath());
assertEquals(foo.getSymbol().getProgramLocation(), item.getProgramLocation());
assertEquals("foo", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("bar", item.getCurrent());
assertEquals("bar", foo.getName());
}
@Test
public void testRenameClassWithDuplicate() throws Exception {
Namespace global = program.getGlobalNamespace();
Namespace foo = createClass(global, "foo");
createClass(global, "bar");
setSearchTypes(classes);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_ON);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.WARNING, item.getStatus());
assertEquals("There is already a symbol named \"bar\" in namespace \"Global\"",
item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Class", item.getItemType());
assertEquals("Global", item.getPath());
assertEquals(foo.getSymbol().getProgramLocation(), item.getProgramLocation());
assertEquals("foo", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.ERROR, item.getStatus());
assertEquals(
"Rename Failed! A Class symbol with name bar already exists in namespace Global",
item.getStatusMessage());
assertEquals("foo", item.getCurrent());
assertEquals("foo", foo.getName());
}
@Test
public void testSearchParameters() throws Exception {
createFunction(10, "aaa", "foo", "xxxfooxxx");
createFunction(20, "bbb", "foo");
setSearchTypes(parameters);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(3, results.size());
sortByAddress(results);
assertQuickFix("foo", "bar", results.get(0));
assertQuickFix("xxxfooxxx", "xxxbarxxx", results.get(1));
assertQuickFix("foo", "bar", results.get(2));
}
@Test
public void testRenamingParameter() throws Exception {
Function function = createFunction(10, "aaa", "xxxfooxxx");
Parameter parameter = function.getParameter(1);
setSearchTypes(parameters);
List<QuickFix> results = query("xxxfooxxx", "xxxbarxxx", CASE_SENSITIVE_OFF, WHOLE_WORD_ON);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Parameter", item.getItemType());
assertEquals("aaa", item.getPath());
assertEquals(parameter.getSymbol().getProgramLocation(), item.getProgramLocation());
assertEquals("xxxfooxxx", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("xxxbarxxx", item.getCurrent());
assertEquals("xxxbarxxx", parameter.getName());
}
@Test
public void testRenameParameterDuplicate() throws Exception {
Function function = createFunction(10, "aaa", "foo", "bar");
Parameter parameter = function.getParameter(1);
setSearchTypes(parameters);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_ON);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.WARNING, item.getStatus());
assertEquals("There is already a symbol named \"bar\" in namespace \"aaa\"",
item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Parameter", item.getItemType());
assertEquals("aaa", item.getPath());
assertEquals(parameter.getSymbol().getProgramLocation(), item.getProgramLocation());
assertEquals("foo", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.ERROR, item.getStatus());
assertEquals(
"Rename Failed! A Parameter symbol with name bar already exists in namespace aaa",
item.getStatusMessage());
assertEquals("foo", item.getCurrent());
assertEquals("foo", parameter.getName());
}
@Test
public void testSearchLocalVars() throws Exception {
Function f1 = createFunction(10, "aaa");
Function f2 = createFunction(20, "bbb");
DataType dt = new ByteDataType();
builder.createLocalVariable(f1, "foo", dt, 0);
builder.createLocalVariable(f1, "xxxfooxxx", dt, 4);
builder.createLocalVariable(f2, "foo", dt, 0);
setSearchTypes(localVariables);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_OFF);
assertEquals(3, results.size());
assertQuickFix("foo", "bar", results.get(0));
assertQuickFix("xxxfooxxx", "xxxbarxxx", results.get(1));
assertQuickFix("foo", "bar", results.get(2));
}
@Test
public void testRenamingLocalVariable() throws Exception {
Function f1 = createFunction(10, "aaa");
Function f2 = createFunction(20, "bbb");
DataType dt = new ByteDataType();
builder.createLocalVariable(f1, "foo", dt, 0);
builder.createLocalVariable(f2, "bar", dt, 4);
setSearchTypes(localVariables);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_ON);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.NONE, item.getStatus());
assertEquals("Not Applied", item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Local Var", item.getItemType());
assertEquals("aaa", item.getPath());
assertEquals("foo", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.DONE, item.getStatus());
assertEquals("Applied", item.getStatusMessage());
assertEquals("bar", item.getCurrent());
}
@Test
public void testRenameVariableDuplicate() throws Exception {
Function f1 = createFunction(10, "aaa");
DataType dt = new ByteDataType();
builder.createLocalVariable(f1, "foo", dt, 0);
builder.createLocalVariable(f1, "bar", dt, 4);
setSearchTypes(localVariables);
List<QuickFix> results = query("foo", "bar", CASE_SENSITIVE_OFF, WHOLE_WORD_ON);
assertEquals(1, results.size());
QuickFix item = results.get(0);
assertEquals(QuickFixStatus.WARNING, item.getStatus());
assertEquals("There is already a symbol named \"bar\" in namespace \"aaa\"",
item.getStatusMessage());
assertEquals("Rename", item.getActionName());
assertEquals("Local Var", item.getItemType());
assertEquals("aaa", item.getPath());
assertEquals("foo", item.getCurrent());
performAction(item);
assertEquals(QuickFixStatus.ERROR, item.getStatus());
assertEquals(
"Rename Failed! A Local Var symbol with name bar already exists in namespace aaa",
item.getStatusMessage());
assertEquals("foo", item.getCurrent());
}
}

View file

@ -83,7 +83,7 @@ public class DecompilerCommentsActionFactory extends CommentsActionFactory {
if (!isCommentSupported(loc)) {
return false;
}
return CommentType.isCommentAllowed(getCodeUnit(actionContext), loc);
return CommentTypeUtils.isCommentAllowed(getCodeUnit(actionContext), loc);
}
@Override
@ -139,7 +139,7 @@ public class DecompilerCommentsActionFactory extends CommentsActionFactory {
return CodeUnit.PRE_COMMENT;
}
CodeUnit cu = getCodeUnit(context);
return CommentType.getCommentType(cu, getLocationForContext(context), CodeUnit.NO_COMMENT);
return CommentTypeUtils.getCommentType(cu, getLocationForContext(context), CodeUnit.NO_COMMENT);
}
}
}

View file

@ -0,0 +1,30 @@
/* ###
* 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.util.datastruct;
public class AccumulatorSizeException extends RuntimeException {
private int maxSize;
public AccumulatorSizeException(int maxSize) {
super("Maximum capacity exceeded: " + maxSize);
this.maxSize = maxSize;
}
public int getMaxSize() {
return maxSize;
}
}

View file

@ -0,0 +1,71 @@
/* ###
* 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.util.datastruct;
import java.util.*;
public class SizeRestrictedAccumulatorWrapper<T> implements Accumulator<T> {
private Accumulator<T> accumulator;
private int maxSize;
/**
* Constructor.
*
* @param accumulator the accumulator to pass items to
* @param maxSize the maximum number of items this accumulator will hold
*/
public SizeRestrictedAccumulatorWrapper(Accumulator<T> accumulator, int maxSize) {
this.accumulator = Objects.requireNonNull(accumulator);
this.maxSize = maxSize;
}
@Override
public Iterator<T> iterator() {
return accumulator.iterator();
}
@Override
public void add(T t) {
if (accumulator.size() >= maxSize) {
throw new AccumulatorSizeException(maxSize);
}
accumulator.add(t);
}
@Override
public void addAll(Collection<T> collection) {
for (T t : collection) {
accumulator.add(t);
}
}
@Override
public boolean contains(T t) {
return accumulator.contains(t);
}
@Override
public Collection<T> get() {
return accumulator.get();
}
@Override
public int size() {
return accumulator.size();
}
}

View file

@ -64,6 +64,7 @@ src/main/resources/images/sortascending.png||GHIDRA||||END|
src/main/resources/images/sortdescending.png||GHIDRA||||END|
src/main/resources/images/stack.png||GHIDRA||||END|
src/main/resources/images/text_align_justify.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/resources/images/tick.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/resources/images/up.png||GHIDRA||||END|
src/main/resources/images/video-x-generic16.png||Tango Icons - Public Domain|||tango icon set|END|
src/main/resources/images/viewmagfit.png||Nuvola Icons - LGPL 2.1|||Nuvola icon set|END|

View file

@ -66,7 +66,7 @@ font.monospaced = monospaced-plain-12
// Icons files
icon.flag = flag.png
icon.lock = kgpg.png
icon.checkmark.green = checkmark_green.gif
icon.checkmark.green = tick.png
icon.empty = EmptyIcon16.gif
icon.empty.20 = EmptyIcon.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

View file

@ -495,6 +495,11 @@ class ListingDB implements Listing {
return codeMgr.getCommentAddressIterator(commentType, addrSet, forward);
}
@Override
public long getCommentAddressCount() {
return codeMgr.getCommentAddressCount();
}
@Override
public AddressIterator getCommentAddressIterator(AddressSetView addrSet, boolean forward) {
return codeMgr.getCommentAddressIterator(addrSet, forward);
@ -505,6 +510,11 @@ class ListingDB implements Listing {
return codeMgr.getComment(commentType, address);
}
@Override
public CodeUnitComments getAllComments(Address address) {
return codeMgr.getAllComments(address);
}
@Override
public void setComment(Address address, int commentType, String comment) {
codeMgr.setComment(address, commentType, comment);

View file

@ -1239,6 +1239,20 @@ public class CodeManager implements ErrorHandler, ManagerDB {
return new CommentTypeFilterIterator(it, commentType);
}
/**
* Returns the number of addresses that have associated comments.
* @return the number of addresses that have associated comments
*/
public long getCommentAddressCount() {
try {
return commentAdapter.getRecordCount();
}
catch (IOException e) {
program.dbError(e);
}
return 0;
}
/**
* Get a forward iterator over addresses that have comments of the given type.
* @param commentType comment type defined in CodeUnit
@ -2969,7 +2983,7 @@ public class CodeManager implements ErrorHandler, ManagerDB {
// flow following issues, for example creating a function body.
boolean isFallthrough =
(flowType.isJump() && flowAddr.equals(inst.getMaxAddress().next())) &&
inst.hasFallthrough();
inst.hasFallthrough();
if (!isFallthrough) {
mnemonicPrimaryRef = addDefaultMemoryReferenceIfMissing(inst,
@ -3289,6 +3303,30 @@ public class CodeManager implements ErrorHandler, ManagerDB {
return null;
}
/**
* Returns all the comments at the given address.
* @param address the address to get all comments for
* @return all the comments at the given address
*/
public CodeUnitComments getAllComments(Address address) {
try {
long addr = addrMap.getKey(address, false);
DBRecord commentRec = getCommentAdapter().getRecord(addr);
CommentType[] types = CommentType.values();
String[] comments = new String[types.length];
for (CommentType type : types) {
int index = type.ordinal();
comments[index] = commentRec.getString(index);
}
return new CodeUnitComments(comments);
}
catch (IOException e) {
dbError(e);
}
return null;
}
/**
* Set the comment for the given comment type at the specified address.
*

View file

@ -555,4 +555,9 @@ class FragmentDB extends DatabaseObject implements ProgramFragment {
lock.release();
}
}
@Override
public boolean isDeleted() {
return isDeleted(lock);
}
}

View file

@ -922,4 +922,9 @@ class ModuleDB extends DatabaseObject implements ProgramModule {
public String toString() {
return record.getString(ModuleDBAdapter.MODULE_NAME_COL);
}
@Override
public boolean isDeleted() {
return isDeleted(lock);
}
}

View file

@ -39,25 +39,36 @@ public interface CodeUnit extends MemBuffer, PropertySet {
public final static int MNEMONIC = -1;
public static final int NO_COMMENT = -1;
/**
* comment type for end of line
* @deprecated use {@link CommentType#EOL}
*/
@Deprecated
public static final int EOL_COMMENT = 0;
/**
* comment type that goes before a code unit
* @deprecated use {@link CommentType#PRE}
*/
@Deprecated
public static final int PRE_COMMENT = 1;
/**
* comment type that follows after a code unit
* @deprecated use {@link CommentType#POST}
*/
@Deprecated
public static final int POST_COMMENT = 2;
/**
* Property name for plate comment type
* @deprecated use {@link CommentType#POST}
*/
@Deprecated
public static final int PLATE_COMMENT = 3;
/**
* Property name for repeatable comment type
* @deprecated use {@link CommentType#REPEATABLE}
*/
@Deprecated
public static final int REPEATABLE_COMMENT = 4;
// /**
@ -135,9 +146,22 @@ public interface CodeUnit extends MemBuffer, PropertySet {
* that type exists for this codeunit
* @throws IllegalArgumentException if type is not one of the
* three types of comments supported
* @deprecated use {@link #getComment(CommentType)} instead
*/
@Deprecated
public String getComment(int commentType);
/**
* Get the comment for the given type
*
* @param type which type of comment to retrieve
* @return the comment string of the appropriate type or null if no comment of
* that type exists for this code unit
*/
public default String getComment(CommentType type) {
return getComment(type.ordinal());
}
/**
* Get the comment for the given type and parse it into an array of strings
* such that each line is its own string.
@ -149,9 +173,24 @@ public interface CodeUnit extends MemBuffer, PropertySet {
* is returned.
* @throws IllegalArgumentException if type is not one of the
* three types of comments supported
* @deprecated use {@link #getCommentAsArray(CommentType)} instead
*/
@Deprecated
public String[] getCommentAsArray(int commentType);
/**
* Get the comment for the given type and parse it into an array of strings
* such that each line is its own string.
*
* @param type the comment type to retrieve
* @return an array of strings where each item in the array is a line of text
* in the comment. If there is no comment of the requested type, an empty array
* is returned.
*/
public default String[] getCommentAsArray(CommentType type) {
return getCommentAsArray(type.ordinal());
}
/**
* Set the comment for the given comment type. Passing <code>null</code> clears the comment
*
@ -161,9 +200,21 @@ public interface CodeUnit extends MemBuffer, PropertySet {
*
* @throws IllegalArgumentException if type is not one of the
* three types of comments supported
* @deprecated use {@link #setComment(CommentType, String)} instead
*/
@Deprecated
public void setComment(int commentType, String comment);
/**
* Set the comment for the given comment type. Passing <code>null</code> clears the comment
*
* @param type of comment to set
* @param comment comment for code unit; null clears the comment
*/
public default void setComment(CommentType type, String comment) {
setComment(type.ordinal(), comment);
}
/**
* Set the comment (with each line in its own string) for the given comment type
*
@ -175,6 +226,10 @@ public interface CodeUnit extends MemBuffer, PropertySet {
*/
public void setCommentAsArray(int commentType, String[] comment);
public default void setCommentAsArray(CommentType type, String[] comment) {
setCommentAsArray(type.ordinal(), comment);
}
/**
* Get length of this code unit.
* NOTE: If an {@link Instruction#isLengthOverridden() instruction length-override} is

View file

@ -0,0 +1,40 @@
/* ###
* 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.program.model.listing;
/**
* Container for all the comments at an address
*/
public class CodeUnitComments {
private String[] comments;
public CodeUnitComments(String[] comments) {
if (comments.length != CommentType.values().length) {
throw new IllegalArgumentException("comment array size does not match enum size!");
}
this.comments = comments;
}
/**
* Get the comment for the given comment type
* @param type the {@link CommentType} to retrieve
* @return the comment of the given type or null if no comment of that type exists
*/
public String getComment(CommentType type) {
return comments[type.ordinal()];
}
}

View file

@ -0,0 +1,28 @@
/* ###
* 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.program.model.listing;
/**
* Types of comments that be placed at an address or on a {@link CodeUnit}
*/
public enum CommentType {
EOL, // comments that appear at the end of the line
PRE, // comments that appear before the code unit
POST, // comments that appear after the code unit
PLATE, // comments that appear before the code unit with a decorated border
REPEATABLE // comments that appear at locations that refer to the address
// where this comment is defined
}

View file

@ -15,6 +15,11 @@
*/
package ghidra.program.model.listing;
import java.util.ArrayList;
import java.util.List;
import ghidra.program.model.address.Address;
import ghidra.program.util.GroupPath;
import ghidra.util.exception.DuplicateNameException;
/**
@ -34,14 +39,14 @@ public interface Group {
*
* @param comment the comment.
*/
public void setComment(String comment);
public void setComment(String comment);
/**
* Obtains the name that has been associated with this fragment. A fragment will
* always have a name and it will be unique within the set of all fragment and
* module names.
*/
public String getName();
public String getName();
/**
* Sets the name of this fragment.
@ -52,7 +57,7 @@ public interface Group {
* thrown if the name being set is already in use by another fragment or a
* module.
*/
public void setName(String name) throws DuplicateNameException;
public void setName(String name) throws DuplicateNameException;
/**
* Returns whether this fragment contains the given code unit.
@ -61,7 +66,7 @@ public interface Group {
*
* @return true if the code unit is in the fragment, false otherwise.
*/
public boolean contains(CodeUnit codeUnit);
public boolean contains(CodeUnit codeUnit);
/**
* Obtains the number of parent's of this fragment. If a fragment is in a module
@ -71,21 +76,55 @@ public interface Group {
*
* @return the number of parents of this fragment.
*/
public int getNumParents();
public int getNumParents();
/**
* Returns a list of the modules which are parents for this group.
*/
public ProgramModule[] getParents();
public ProgramModule[] getParents();
/**
* Returns the names of the modules which are parents to this
* fragment.
*/
public String[] getParentNames();
public String[] getParentNames();
/**
* Returns the name of the tree that this group belongs to.
*/
public String getTreeName();
public String getTreeName();
/**
* Returns true if this group has been deleted from the program
* @return true if this group has been deleted from the program
*/
public boolean isDeleted();
public Address getMinAddress();
public Address getMaxAddress();
/**
* Returns one of many possible GroupPaths for this group. Since Fragments can belong in
* more than one module, there can be multiple legitimate group paths for a group. This method
* arbitrarily returns one valid group path.
* @return one of several possible group paths for this group
*/
public default GroupPath getGroupPath() {
List<String> parentNames = getParentNames(this);
return new GroupPath(parentNames.toArray(new String[parentNames.size()]));
}
private static List<String> getParentNames(Group group) {
Group[] parents = group.getParents();
if (parents == null || parents.length == 0) {
List<String> list = new ArrayList<>();
list.add(group.getName());
return list;
}
Group parent = parents[0];
List<String> names = getParentNames(parent);
names.add(group.getName());
return names;
}
}

View file

@ -144,19 +144,51 @@ public interface Listing {
*/
public CodeUnitIterator getCommentCodeUnitIterator(int commentType, AddressSetView addrSet);
/**
* Get a forward code unit iterator over code units that have the specified
* comment type.
*
* @param type the comment type
* @param addrSet address set to iterate code unit comments over
* @return a CodeUnitIterator that returns all code units from the indicated
* address set that have the specified comment type defined
*/
public default CodeUnitIterator getCommentCodeUnitIterator(CommentType type,
AddressSetView addrSet) {
return getCommentCodeUnitIterator(type.ordinal(), addrSet);
}
/**
* Get a forward iterator over addresses that have the specified comment
* type.
*
* @param commentType type defined in CodeUnit
* @param addrSet address set
* @param addrSet address set to iterate code unit comments over
* @param forward true to iterator from lowest address to highest, false
* highest to lowest
* @return an AddressIterator that returns all addresses from the indicated
* address set that have the specified comment type defined
* @deprecated use {@link #getCommentAddressIterator(CommentType, AddressSetView, boolean)}
*/
@Deprecated
public AddressIterator getCommentAddressIterator(int commentType, AddressSetView addrSet,
boolean forward);
/**
* Get a forward iterator over addresses that have the specified comment
* type.
*
* @param type the type of comment to iterate over
* @param addrSet address set to iterate code unit comments over
* @param forward true to iterator from lowest address to highest, false
* highest to lowest
* @return an AddressIterator that returns all addresses from the indicated
* address set that have the specified comment type defined
*/
public AddressIterator getCommentAddressIterator(int commentType, AddressSetView addrSet,
boolean forward);
public default AddressIterator getCommentAddressIterator(CommentType type,
AddressSetView addrSet, boolean forward) {
return getCommentAddressIterator(type.ordinal(), addrSet, forward);
}
/**
* Get a forward iterator over addresses that have any type of comment.
@ -179,9 +211,30 @@ public interface Listing {
* of that type exists for this code unit
* @throws IllegalArgumentException if type is not one of the types of
* comments supported
* @deprecated use {@link #getComment(CommentType, Address)}
*/
@Deprecated
public String getComment(int commentType, Address address);
/**
* Get all the comments at the given address.
* @param address the address get comments
* @return a CodeUnitComments object that has all the comments at the address.
*/
public CodeUnitComments getAllComments(Address address);
/**
* Get the comment for the given type at the specified address.
*
* @param type the comment type to retrieve
* @param address the address of the comment.
* @return the comment string of the appropriate type or null if no comment
* of that type exists for this code unit
*/
public default String getComment(CommentType type, Address address) {
return getComment(type.ordinal(), address);
}
/**
* Set the comment for the given comment type at the specified address.
*
@ -191,9 +244,24 @@ public interface Listing {
* @param comment comment to set at the address
* @throws IllegalArgumentException if type is not one of the types of
* comments supported
* @deprecated use {@link #setComment(Address, CommentType, String)}
*/
@Deprecated
public void setComment(Address address, int commentType, String comment);
/**
* Set the comment for the given comment type at the specified address.
*
* @param address the address of the comment.
* @param type the type of comment to set
* @param comment comment to set at the address
* @throws IllegalArgumentException if type is not one of the types of
* comments supported
*/
public default void setComment(Address address, CommentType type, String comment) {
setComment(address, type.ordinal(), comment);
}
/**
* get a CodeUnit iterator that will iterate over the entire address space.
*
@ -983,4 +1051,10 @@ public interface Listing {
*/
public CommentHistory[] getCommentHistory(Address addr, int commentType);
/**
* Returns the number of addresses where at least one comment type has been applied.
* @return the number of addresses where at least one comment type has been applied
*/
public long getCommentAddressCount();
}

View file

@ -90,11 +90,21 @@ public class StubListing implements Listing {
throw new UnsupportedOperationException();
}
@Override
public long getCommentAddressCount() {
throw new UnsupportedOperationException();
}
@Override
public String getComment(int commentType, Address address) {
throw new UnsupportedOperationException();
}
@Override
public CodeUnitComments getAllComments(Address address) {
throw new UnsupportedOperationException();
}
@Override
public void setComment(Address address, int commentType, String comment) {
throw new UnsupportedOperationException();

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,7 +17,7 @@ package ghidra.program.util;
import ghidra.program.model.listing.CodeUnit;
public class CommentType {
public class CommentTypeUtils {
/**
* Get the comment type from the current location. If the cursor

View file

@ -299,9 +299,15 @@ public class UserSearchUtils {
}
/**
* Escapes regex characters, optionally turning globbing characters into valid regex syntax.
* Convert user entered text into a regular expression, escaping regex characters,
* optionally turning globbing characters into valid regex syntax.
* @param input the user entered text to be converted to a regular expression.
* @param allowGlobbing if true, '*' and '?' will be converted to equivalent regular expression
* syntax for wildcard matching, otherwise they will be treated as literal characters to be
* part of the search text.
* @return a converted text string suitable for use in a regular expression.
*/
private static String convertUserInputToRegex(String input, boolean allowGlobbing) {
public static String convertUserInputToRegex(String input, boolean allowGlobbing) {
String escaped = input;
if (allowGlobbing) {

View file

@ -0,0 +1,92 @@
/* ###
* 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 help.screenshot;
import org.junit.Before;
import org.junit.Test;
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
import ghidra.features.base.replace.*;
/**
* Screenshots for help/topics/Search/Search_Memory.htm
*/
public class SearchAndReplaceScreenShots extends AbstractSearchScreenShots {
private CodeBrowserPlugin cb;
private SearchAndReplacePlugin plugin;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
plugin = env.getPlugin(SearchAndReplacePlugin.class);
cb = env.getPlugin(CodeBrowserPlugin.class);
env.showTool();
}
@Test
public void testSearchAndReplaceDialog() {
performAction("Search And Replace", "SearchAndReplacePlugin", false);
waitForSwing();
SearchAndReplaceDialog dialog =
(SearchAndReplaceDialog) getDialog(SearchAndReplaceDialog.class);
runSwing(() -> {
dialog.setSarchAndReplaceText("value", "amount");
dialog.selectSearchType("Labels");
dialog.selectSearchType("Functions");
dialog.selectSearchType("Comments");
dialog.selectSearchType("Datatypes");
dialog.selectSearchType("Datatype Fields");
dialog.selectSearchType("Datatype Comments");
dialog.selectSearchType("Parameters");
});
captureDialog(dialog);
}
@Test
public void testSearchAndReplaceResults() {
performAction("Search And Replace", "SearchAndReplacePlugin", false);
waitForSwing();
SearchAndReplaceDialog dialog =
(SearchAndReplaceDialog) getDialog(SearchAndReplaceDialog.class);
runSwing(() -> {
dialog.setSarchAndReplaceText("value", "amount");
dialog.selectSearchType("Labels");
dialog.selectSearchType("Functions");
dialog.selectSearchType("Comments");
dialog.selectSearchType("Datatypes");
dialog.selectSearchType("Datatype Fields");
dialog.selectSearchType("Datatype Comments");
dialog.selectSearchType("Parameters");
});
pressOkOnDialog();
SearchAndReplaceProvider provider =
waitForComponentProvider(SearchAndReplaceProvider.class);
captureIsolatedProvider(provider, 700, 500);
}
}