mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 09:49:23 +02:00
GP-5310 Created global search and replace feature
This commit is contained in:
parent
cc1228bdaa
commit
6fb115358a
93 changed files with 7469 additions and 141 deletions
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -239,4 +239,9 @@ public class DBTraceProgramViewRootModule implements ProgramModule {
|
|||
public long getTreeID() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeleted() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -22,3 +22,4 @@ OverviewColorService
|
|||
DWARFFunctionFixup
|
||||
ElfInfoProducer
|
||||
FSBFileHandler
|
||||
SearchAndReplaceHandler
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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" >
|
||||
|
|
|
@ -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"> Auto Updating Selection
|
||||
by Location</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>The <IMG alt="" src="images/locationIn.gif"> 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. </P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P class="providedbyplugin">Provided by: <I>Memory Map</I> Plugin<BR>
|
||||
</P>
|
||||
|
|
|
@ -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=""> </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=""> </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>
|
|
@ -17,9 +17,12 @@
|
|||
<P>Ghidra offers a variety of searching capabilities. The <I>Search Program Memory</I>
|
||||
feature performs fast searching for byte patterns in program memory. 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. 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. 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 |
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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")));
|
||||
}
|
||||
|
||||
}
|
|
@ -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"));
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
|
BIN
Ghidra/Framework/Gui/src/main/resources/images/tick.png
Normal file
BIN
Ghidra/Framework/Gui/src/main/resources/images/tick.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 537 B |
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -555,4 +555,9 @@ class FragmentDB extends DatabaseObject implements ProgramFragment {
|
|||
lock.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeleted() {
|
||||
return isDeleted(lock);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()];
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue