Merge remote-tracking branch

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

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

@@ -4,9 +4,9 @@
* 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.
@@ -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,13 +1,12 @@
/* ###
* 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.
* 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.
@@ -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);
}
}
}
@@ -4,9 +4,9 @@
* 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.
@@ -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);
@@ -4,9 +4,9 @@
* 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.
@@ -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() {
@@ -4,9 +4,9 @@
* 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.
@@ -51,6 +51,10 @@ import ghidra.util.task.TaskMonitor;
* for that address. The textual representation also maintains information about the field
* 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 {
@@ -4,9 +4,9 @@
* 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.
@@ -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);
}
@@ -4,9 +4,9 @@
* 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.
@@ -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
@@ -4,9 +4,9 @@
* 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.
@@ -28,30 +28,33 @@ 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 {
/**
* Get the name of the tree currently being viewed.
*/
public String getViewedTreeName();
/**
* Set the current view to that of the given name. If treeName is not
* a known view, then nothing happens.
* @param treeName name of the view
*/
public void setViewedTree(String treeName);
/**
* Get the address set of the current view (what is currently being shown in
* the Code Browser).
*/
public AddressSet getView();
public AddressSet getView();
/**
* 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());
}
}

Some files were not shown because too many files have changed in this diff Show More