GP-5806 added namespace chooser to add/edit label/function dialog
@@ -516,6 +516,7 @@ src/main/help/help/topics/Intro/images/Simple_err_dialog.png||GHIDRA||||END|
|
|||||||
src/main/help/help/topics/LabelMgrPlugin/FieldNames.htm||GHIDRA||||END|
|
src/main/help/help/topics/LabelMgrPlugin/FieldNames.htm||GHIDRA||||END|
|
||||||
src/main/help/help/topics/LabelMgrPlugin/Labels.htm||GHIDRA||||END|
|
src/main/help/help/topics/LabelMgrPlugin/Labels.htm||GHIDRA||||END|
|
||||||
src/main/help/help/topics/LabelMgrPlugin/images/AddLabel.png||GHIDRA||||END|
|
src/main/help/help/topics/LabelMgrPlugin/images/AddLabel.png||GHIDRA||||END|
|
||||||
|
src/main/help/help/topics/LabelMgrPlugin/images/ChooseNamespace.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/LabelMgrPlugin/images/EditFieldNameDialog.png||GHIDRA||||END|
|
src/main/help/help/topics/LabelMgrPlugin/images/EditFieldNameDialog.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/LabelMgrPlugin/images/LabelHistoryInputDialog.png||GHIDRA||||END|
|
src/main/help/help/topics/LabelMgrPlugin/images/LabelHistoryInputDialog.png||GHIDRA||||END|
|
||||||
src/main/help/help/topics/LabelMgrPlugin/images/SetLabel.png||GHIDRA||||END|
|
src/main/help/help/topics/LabelMgrPlugin/images/SetLabel.png||GHIDRA||||END|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 17 KiB |
@@ -137,8 +137,6 @@
|
|||||||
<P><I><B>Enter Label</B></I></P>
|
<P><I><B>Enter Label</B></I></P>
|
||||||
|
|
||||||
<BLOCKQUOTE>
|
<BLOCKQUOTE>
|
||||||
<UL>
|
|
||||||
<LI>
|
|
||||||
Text field for entering the name of the label. A combo box is included which allows
|
Text field for entering the name of the label. A combo box is included which allows
|
||||||
selecting recently used label names.
|
selecting recently used label names.
|
||||||
|
|
||||||
@@ -150,35 +148,36 @@
|
|||||||
</PRE>
|
</PRE>
|
||||||
For example, the following string denotes a full namespace path that starts at the
|
For example, the following string denotes a full namespace path that starts at the
|
||||||
<B>Global</B> namespace and ends at the label name <TT>myLabel</TT>:<BR>
|
<B>Global</B> namespace and ends at the label name <TT>myLabel</TT>:<BR>
|
||||||
|
<PRE>
|
||||||
<PRE>
|
Global::foo::bar::myLabel
|
||||||
Global::foo::bar::myLabel
|
|
||||||
|
</PRE>
|
||||||
</PRE>
|
|
||||||
The namespace in the <I>Namespace</I> combo box will be used as the parent namespace
|
The namespace in the <I>Namespace</I> combo box will be used as the parent namespace
|
||||||
for the label name and any included namespaces. However, if the you provide a namespace
|
for the label name and any included namespaces. However, if the you provide a namespace
|
||||||
path that starts with <B>Global</B>, then the value of the <I>Namespace</I> combo box
|
path that starts with <B>Global</B>, then the value of the <I>Namespace</I> combo box
|
||||||
will be ignored.
|
will be ignored.
|
||||||
</LI>
|
|
||||||
</UL>
|
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
<P><I><B>Namespace</B></I></P>
|
<P><I><B>Namespace</B></I></P>
|
||||||
|
|
||||||
<BLOCKQUOTE>
|
<BLOCKQUOTE>
|
||||||
<UL>
|
<P>The containing namespace for the label or function. </P>
|
||||||
<LI>The defining scope of the label. The available namespaces are based upon the current
|
<P>The applicable namespaces are based upon the current address and symbol type.
|
||||||
address. When editing a label, the available namespaces are not necessarily those in the
|
The namespace combobox
|
||||||
namespace hierarchy in which the label is located, but rather are those based upon the
|
will initially be populated with the most obvious choices for that location, as well
|
||||||
address of that label. The <B>Global</B> namespace is always included by default, as well
|
any recently chosen namespaces.</P>
|
||||||
as the parent namespace of the current label, if one is being edited.</LI>
|
<P>Next to the combobox, there is a browse button which can be pressed to bring up the
|
||||||
</UL>
|
<A href="#NamespaceChooserDialog">Namespace Chooser Dialog</A>. From this chooser dialog,
|
||||||
|
any namespace in the program can be selected. However, not all namespaces are applicable
|
||||||
|
to all locations. For example, you can't put a function into another function's namespace.
|
||||||
|
If the namespace is not applicable to the current location, the selected namespace will
|
||||||
|
still appear in the namespace field, but you will get an error when the OK button is
|
||||||
|
pressed.</P>
|
||||||
|
|
||||||
<BLOCKQUOTE>
|
<BLOCKQUOTE>
|
||||||
<P><IMG src="help/shared/note.png" alt="Note:">This field is disabled, if there is a
|
<P><IMG src="help/shared/note.png" alt="Note:">This field is disabled when the namespace
|
||||||
function with a default name at this address. The namespace will stay set to the parent
|
at that location can't be changed. For example, parameters and local variables can only
|
||||||
namespace of the function and the label name you enter will become the new function
|
have the enclosing function as its namespace. <BR>
|
||||||
name.<BR>
|
|
||||||
</P>
|
</P>
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
@@ -186,38 +185,66 @@
|
|||||||
<P><I><B>Entry Point</B></I></P>
|
<P><I><B>Entry Point</B></I></P>
|
||||||
|
|
||||||
<BLOCKQUOTE>
|
<BLOCKQUOTE>
|
||||||
<UL>
|
Sets the entry point property for address associated with this label. Setting this
|
||||||
<LI>Sets the entry point property for address associated with this label. Setting this
|
|
||||||
property on one symbol, changes it for all symbols at the same address.</LI>
|
property on one symbol, changes it for all symbols at the same address.</LI>
|
||||||
</UL>
|
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
<P><I><B>Primary</B></I></P>
|
<P><I><B>Primary</B></I></P>
|
||||||
|
|
||||||
<BLOCKQUOTE>
|
<BLOCKQUOTE>
|
||||||
<UL>
|
Sets the primary property for this symbol. If there is only one symbol at this
|
||||||
<LI>Sets the primary property for this symbol. If there is only one symbol at this
|
|
||||||
address, the checkbox will be selected and disabled, since it must be primary. Whenever
|
address, the checkbox will be selected and disabled, since it must be primary. Whenever
|
||||||
the checkbox is selected, it will be disabled because the only way to make a symbol
|
the checkbox is selected, it will be disabled because the only way to make a symbol
|
||||||
become non-primary is to select another symbol at the same address and make it primary.
|
become non-primary is to select another symbol at the same address and make it primary.
|
||||||
This ensures that there will always be one label that is primary. If there is a function
|
This ensures that there will always be one label that is primary. If there is a function
|
||||||
at this address and you add a new label, the checkbox will be enabled such that if you
|
at this address and you add a new label, the checkbox will be enabled such that if you
|
||||||
select the checkbox, the function is renamed to the new label that you entered. The
|
select the checkbox, the function is renamed to the new label that you entered. The
|
||||||
function symbol must always be the primary symbol.</LI>
|
function symbol must always be the primary symbol.
|
||||||
</UL>
|
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
<P><I><B>Pinned</B></I></P>
|
<P><I><B>Pinned</B></I></P>
|
||||||
<BLOCKQUOTE>
|
<BLOCKQUOTE>
|
||||||
<UL>
|
Sets the label to pinned. A pinned label will not move if the image base is changed
|
||||||
<LI>Sets the label to pinned. A pinned label will not move if the image base is changed
|
|
||||||
or a memory block is moved. A label that is not pinned, will move with the code
|
or a memory block is moved. A label that is not pinned, will move with the code
|
||||||
or data if a memory block is moved or the image base is changed. Also, a pinned
|
or data if a memory block is moved or the image base is changed. Also, a pinned
|
||||||
label will not be removed if the memory block that contains it is removed. Only code,
|
label will not be removed if the memory block that contains it is removed. Only code,
|
||||||
data, or function labels may be pinned.</LI>
|
data, or function labels may be pinned.
|
||||||
</UL>
|
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
</BLOCKQUOTE>
|
</BLOCKQUOTE>
|
||||||
|
<H2><A name="NamespaceChooserDialog"></A>Namespace Chooser Dialog</H2>
|
||||||
|
<BLOCKQUOTE>
|
||||||
|
|
||||||
|
<P>The Namespace Chooser Dialog allows you to choose namespaces that have been defined
|
||||||
|
in the current program. It presents a drop-down text field where you can begin typing
|
||||||
|
the name of a namespace and a drop-down window will appear showing a list of
|
||||||
|
all namespaces that match the typed text.</P>
|
||||||
|
|
||||||
|
|
||||||
|
<P align="center"><IMG border="0" src="images/ChooseNamespace.png" alt=""><BR>
|
||||||
|
<I>Namespace Chooser Dialog</I></P>
|
||||||
|
|
||||||
|
<P>There are several modes that determine how the typed text is used to match against all
|
||||||
|
program namespaces. The mode can be changed by pressing on the symbol(s) shown on the far
|
||||||
|
right of the text field. You can also change the search mode using <B>Ctrl Down</B> and
|
||||||
|
<B>Ctrl Up</B> to change the mode forward and backward, respectively. Regardless of the mode,
|
||||||
|
the searches are not case sensitive.</P>
|
||||||
|
<P>These modes are:</P>
|
||||||
|
|
||||||
|
<UL>
|
||||||
|
<LI><B>Starts With ^</B> - In this mode, the typed text will match all namespaces that
|
||||||
|
have a base name (not path) that starts with the typed text.</LI>
|
||||||
|
<LI><B>Contains <I>()</I></B> - In this mode, the typed text will match all namespaces that
|
||||||
|
have the typed text anywhere in its full namespace path. </LI>
|
||||||
|
<LI><B>Wildcard <I>*?</I></B> - In this mode, the typed text may contain wildcard characters
|
||||||
|
which will be used to create a pattern for matching against the full namespace path. The
|
||||||
|
wildcard char '*' will match 0 or more characters, while the wildcard char '?' will match any
|
||||||
|
single character. </LI>
|
||||||
|
</UL>
|
||||||
|
|
||||||
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
|
|
||||||
<H2><A name="OperandLabelDialog"></A>Set Label Dialog</H2>
|
<H2><A name="OperandLabelDialog"></A>Set Label Dialog</H2>
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 9.6 KiB |
@@ -27,6 +27,7 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
import docking.ComponentProvider;
|
import docking.ComponentProvider;
|
||||||
import docking.ReusableDialogComponentProvider;
|
import docking.ReusableDialogComponentProvider;
|
||||||
import docking.widgets.OptionDialog;
|
import docking.widgets.OptionDialog;
|
||||||
|
import docking.widgets.button.BrowseButton;
|
||||||
import docking.widgets.checkbox.GCheckBox;
|
import docking.widgets.checkbox.GCheckBox;
|
||||||
import docking.widgets.combobox.GhidraComboBox;
|
import docking.widgets.combobox.GhidraComboBox;
|
||||||
import ghidra.app.cmd.label.*;
|
import ghidra.app.cmd.label.*;
|
||||||
@@ -46,6 +47,7 @@ import ghidra.util.layout.VerticalLayout;
|
|||||||
*/
|
*/
|
||||||
public class AddEditDialog extends ReusableDialogComponentProvider {
|
public class AddEditDialog extends ReusableDialogComponentProvider {
|
||||||
private static final int MAX_RETENTION = 10;
|
private static final int MAX_RETENTION = 10;
|
||||||
|
|
||||||
private PluginTool tool;
|
private PluginTool tool;
|
||||||
private TitledBorder nameBorder;
|
private TitledBorder nameBorder;
|
||||||
private GhidraComboBox<String> labelNameChoices;
|
private GhidraComboBox<String> labelNameChoices;
|
||||||
@@ -337,55 +339,65 @@ public class AddEditDialog extends ReusableDialogComponentProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method only gets the namespace associated with the current address
|
|
||||||
// and it's tree of namespaces. It does not walk the namespace tree of
|
|
||||||
// the symbol, which can be different than that of the address.
|
|
||||||
private void initNamespaces() {
|
private void initNamespaces() {
|
||||||
namespaceChoices.removeAllItems();
|
namespaceChoices.removeAllItems();
|
||||||
|
|
||||||
|
for (Namespace namespace : getSelectableNamespaces()) {
|
||||||
|
namespaceChoices.addItem(new NamespaceWrapper(namespace));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<Namespace> getSelectableNamespaces() {
|
||||||
if (!namespaceChoices.isEnabled()) {
|
if (!namespaceChoices.isEnabled()) {
|
||||||
namespaceChoices.addItem(new NamespaceWrapper(symbol.getParentNamespace()));
|
return List.of(symbol.getParentNamespace());
|
||||||
selectNamespace();
|
}
|
||||||
|
|
||||||
|
SequencedSet<Namespace> namespaces = new LinkedHashSet<>();
|
||||||
|
addGlobalNamespace(namespaces);
|
||||||
|
addCurrentNamespace(namespaces);
|
||||||
|
addSuggestedNamespace(namespaces);
|
||||||
|
addRecentNamespaces(namespaces);
|
||||||
|
|
||||||
|
return namespaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRecentNamespaces(SequencedSet<Namespace> namespaces) {
|
||||||
|
List<Namespace> recentNamespaces = NamespaceCache.get(program);
|
||||||
|
if (recentNamespaces == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
for (Namespace namespace : recentNamespaces) {
|
||||||
|
if (!namespaces.contains(namespace)) {
|
||||||
|
namespaces.add(namespace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Collection<NamespaceWrapper> collection = new HashSet<>();
|
private void addSuggestedNamespace(SequencedSet<Namespace> namespaces) {
|
||||||
|
Namespace namespace = program.getSymbolTable().getNamespace(addr);
|
||||||
|
if (namespace == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Don't include the currently edited symbol as a possible choice
|
||||||
|
if (symbol != null && namespace.equals(symbol.getObject())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!namespaces.contains(namespace)) {
|
||||||
|
namespaces.add(namespace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// we always add the global namespace
|
private void addGlobalNamespace(SequencedSet<Namespace> namespaces) {
|
||||||
Namespace globalNamespace = program.getGlobalNamespace();
|
Namespace globalNamespace = program.getGlobalNamespace();
|
||||||
|
if (!namespaces.contains(globalNamespace)) {
|
||||||
NamespaceWrapper composite = new NamespaceWrapper(globalNamespace);
|
namespaces.add(globalNamespace);
|
||||||
namespaceChoices.addItem(composite);
|
|
||||||
collection.add(composite);
|
|
||||||
|
|
||||||
Namespace currentNamespace = program.getSymbolTable().getNamespace(addr);
|
|
||||||
|
|
||||||
// no symbol or not editing a function symbol
|
|
||||||
if ((symbol == null) || (symbol != null && symbol.getSymbolType() != SymbolType.FUNCTION)) {
|
|
||||||
// walk the tree of namespaces and collect all of the items
|
|
||||||
for (; (currentNamespace != globalNamespace); currentNamespace =
|
|
||||||
currentNamespace.getParentNamespace()) {
|
|
||||||
composite = new NamespaceWrapper(currentNamespace);
|
|
||||||
|
|
||||||
if (!collection.contains(composite)) {
|
|
||||||
collection.add(composite);
|
|
||||||
namespaceChoices.addItem(composite);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addCurrentNamespace(SequencedSet<Namespace> namespaces) {
|
||||||
if (symbol != null) {
|
if (symbol != null) {
|
||||||
// we are adding the current namespace of the symbol if it is not in
|
namespaces.add(symbol.getParentNamespace());
|
||||||
// the namespace tree that belongs to the address
|
|
||||||
Namespace symbolNamespace = symbol.getParentNamespace();
|
|
||||||
composite = new NamespaceWrapper(symbolNamespace);
|
|
||||||
if (!collection.contains(composite)) {
|
|
||||||
collection.add(composite);
|
|
||||||
namespaceChoices.insertItemAt(composite, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
selectNamespace();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -478,6 +490,7 @@ public class AddEditDialog extends ReusableDialogComponentProvider {
|
|||||||
|
|
||||||
namespaceChoices.setEnabled(true);
|
namespaceChoices.setEnabled(true);
|
||||||
initNamespaces();
|
initNamespaces();
|
||||||
|
selectNamespace();
|
||||||
clearStatusText();
|
clearStatusText();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -538,6 +551,7 @@ public class AddEditDialog extends ReusableDialogComponentProvider {
|
|||||||
namespaceChoices.setEnabled(true);
|
namespaceChoices.setEnabled(true);
|
||||||
}
|
}
|
||||||
initNamespaces();
|
initNamespaces();
|
||||||
|
selectNamespace();
|
||||||
clearStatusText();
|
clearStatusText();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -550,12 +564,11 @@ public class AddEditDialog extends ReusableDialogComponentProvider {
|
|||||||
@Override
|
@Override
|
||||||
public Dimension getPreferredSize() {
|
public Dimension getPreferredSize() {
|
||||||
Dimension size = super.getPreferredSize();
|
Dimension size = super.getPreferredSize();
|
||||||
// change the preferred size to use the width determined by the # of columns in
|
// Change the preferred size to use a standard starting width. Previously, it
|
||||||
// combo box editor instead of the largest item in the combo box data model to
|
// was sized on the first label edited, but then it just remembered that size.
|
||||||
// prevent the dialog from growing huge when a large label gets added to its recent
|
// A width of 500 is a good starting width that will fit most reasonable
|
||||||
// items
|
// namespace names.
|
||||||
Dimension editorSize = getEditor().getEditorComponent().getPreferredSize();
|
size.width = 500;
|
||||||
size.width = editorSize.width;
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -598,7 +611,8 @@ public class AddEditDialog extends ReusableDialogComponentProvider {
|
|||||||
mainPanel.add(bottomPanel);
|
mainPanel.add(bottomPanel);
|
||||||
|
|
||||||
topPanel.add(labelNameChoices, BorderLayout.NORTH);
|
topPanel.add(labelNameChoices, BorderLayout.NORTH);
|
||||||
midPanel.add(namespaceChoices, BorderLayout.NORTH);
|
midPanel.add(namespaceChoices, BorderLayout.CENTER);
|
||||||
|
midPanel.add(buildBrowsePanel(), BorderLayout.EAST);
|
||||||
bottomPanel.add(entryPointCheckBox);
|
bottomPanel.add(entryPointCheckBox);
|
||||||
bottomPanel.add(primaryCheckBox);
|
bottomPanel.add(primaryCheckBox);
|
||||||
bottomPanel.add(pinnedCheckBox);
|
bottomPanel.add(pinnedCheckBox);
|
||||||
@@ -610,6 +624,26 @@ public class AddEditDialog extends ReusableDialogComponentProvider {
|
|||||||
return mainPanel;
|
return mainPanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Component buildBrowsePanel() {
|
||||||
|
JPanel panel = new JPanel(new BorderLayout());
|
||||||
|
JButton browseButton = new BrowseButton();
|
||||||
|
browseButton.setToolTipText("Choose Namespace");
|
||||||
|
browseButton.addActionListener(e -> showNamespaceChooser());
|
||||||
|
panel.add(browseButton, BorderLayout.CENTER);
|
||||||
|
panel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showNamespaceChooser() {
|
||||||
|
NamespaceChooserDialog dialog = new NamespaceChooserDialog();
|
||||||
|
Namespace namespace = dialog.getNameSpace(program);
|
||||||
|
if (namespace != null) {
|
||||||
|
NamespaceCache.add(program, namespace);
|
||||||
|
initNamespaces();
|
||||||
|
namespaceChoices.setSelectedItem(new NamespaceWrapper(namespace));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void addListeners() {
|
private void addListeners() {
|
||||||
labelNameChoices.addActionListener(e -> {
|
labelNameChoices.addActionListener(e -> {
|
||||||
if (program != null) {
|
if (program != null) {
|
||||||
@@ -636,6 +670,11 @@ public class AddEditDialog extends ReusableDialogComponentProvider {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for testing
|
||||||
|
public JComboBox<?> getNamespaceComboBox() {
|
||||||
|
return namespaceChoices;
|
||||||
|
}
|
||||||
|
|
||||||
public class NamespaceWrapper {
|
public class NamespaceWrapper {
|
||||||
private Namespace namespace;
|
private Namespace namespace;
|
||||||
|
|
||||||
@@ -672,4 +711,5 @@ public class AddEditDialog extends ReusableDialogComponentProvider {
|
|||||||
return namespace.hashCode();
|
return namespace.hashCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/* ###
|
||||||
|
* 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.util;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.program.model.symbol.Namespace;
|
||||||
|
import ghidra.util.datastruct.LRUSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static class for remember the last few namespaces used for a program.
|
||||||
|
*/
|
||||||
|
public class NamespaceCache {
|
||||||
|
public static final int MAX_RECENTS = 10;
|
||||||
|
private static Map<Program, LRUSet<Namespace>> recentNamespaces = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of recently used namespaces for the given program.
|
||||||
|
* @param program the program to get namespaces for
|
||||||
|
* @return the list of recently used namespaces for the given program
|
||||||
|
*/
|
||||||
|
public static List<Namespace> get(Program program) {
|
||||||
|
LRUSet<Namespace> recents = recentNamespaces.get(program);
|
||||||
|
return recents != null ? recents.toList() : Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a recently used namespace for a program.
|
||||||
|
* @param program the program to add a recently namespace
|
||||||
|
* @param namespace the recently used namespace to remember
|
||||||
|
*/
|
||||||
|
public static void add(Program program, Namespace namespace) {
|
||||||
|
// no need to cache global namespace, it is always available
|
||||||
|
if (namespace.isGlobal()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LRUSet<Namespace> recents = recentNamespaces.get(program);
|
||||||
|
if (recents == null) {
|
||||||
|
recents = new LRUSet<Namespace>(MAX_RECENTS);
|
||||||
|
recentNamespaces.put(program, recents);
|
||||||
|
program.addCloseListener(p -> recentNamespaces.remove(p));
|
||||||
|
}
|
||||||
|
recents.add(namespace);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
/* ###
|
||||||
|
* 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.util;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
import docking.DialogComponentProvider;
|
||||||
|
import docking.DockingWindowManager;
|
||||||
|
import docking.widgets.DropDownSelectionTextField;
|
||||||
|
import ghidra.program.model.listing.Function;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.program.model.symbol.Namespace;
|
||||||
|
import ghidra.program.model.symbol.Symbol;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
|
import ghidra.util.layout.PairLayout;
|
||||||
|
import ghidra.util.task.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for choosing a namespace
|
||||||
|
*/
|
||||||
|
public class NamespaceChooserDialog extends DialogComponentProvider {
|
||||||
|
|
||||||
|
private DropDownSelectionTextField<Namespace> dropDownField;
|
||||||
|
private NamespaceDropDownModel namespaceModel;
|
||||||
|
private Namespace chosenNamespace;
|
||||||
|
|
||||||
|
public NamespaceChooserDialog() {
|
||||||
|
super("Namespace Chooser");
|
||||||
|
namespaceModel = new NamespaceDropDownModel();
|
||||||
|
addWorkPanel(buildWorkPanel());
|
||||||
|
addOKButton();
|
||||||
|
addCancelButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Namespace getNameSpace(Program program) {
|
||||||
|
List<Namespace> namespaces = gatherNamespaces(program);
|
||||||
|
if (namespaces == null) {
|
||||||
|
// user cancelled while gathering namespaces
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
namespaceModel.setNamespaces(namespaces);
|
||||||
|
DockingWindowManager.showDialog(this);
|
||||||
|
return chosenNamespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void okCallback() {
|
||||||
|
chosenNamespace = dropDownField.getSelectedValue();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void cancelCallback() {
|
||||||
|
chosenNamespace = null;
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Namespace> gatherNamespaces(Program program) {
|
||||||
|
GatherNamespacesTask task = new GatherNamespacesTask(program);
|
||||||
|
TaskLauncher.launch(task);
|
||||||
|
return task.getNamespaces();
|
||||||
|
}
|
||||||
|
|
||||||
|
private JComponent buildWorkPanel() {
|
||||||
|
JPanel panel = new JPanel(new PairLayout());
|
||||||
|
panel.add(new JLabel("Namespace: "));
|
||||||
|
|
||||||
|
dropDownField = new DropDownSelectionTextField<>(namespaceModel);
|
||||||
|
panel.add(dropDownField);
|
||||||
|
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class GatherNamespacesTask extends Task {
|
||||||
|
private List<Namespace> namespaces;
|
||||||
|
private Program program;
|
||||||
|
|
||||||
|
GatherNamespacesTask(Program program) {
|
||||||
|
super("Gather Namespaces");
|
||||||
|
this.program = program;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(TaskMonitor monitor) throws CancelledException {
|
||||||
|
List<Namespace> list = new ArrayList<>();
|
||||||
|
list.add(program.getGlobalNamespace());
|
||||||
|
for (Symbol symbol : program.getSymbolTable().getDefinedSymbols()) {
|
||||||
|
monitor.checkCancelled();
|
||||||
|
if (!symbol.getSymbolType().isNamespace()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Object object = symbol.getObject();
|
||||||
|
if (object instanceof Function f && f.isThunk()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
list.add((Namespace) object);
|
||||||
|
}
|
||||||
|
namespaces = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Namespace> getNamespaces() {
|
||||||
|
return namespaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
/* ###
|
||||||
|
* 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.util;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.help.UnsupportedOperationException;
|
||||||
|
import javax.swing.ListCellRenderer;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import docking.widgets.DropDownTextFieldDataModel;
|
||||||
|
import docking.widgets.list.GListCellRenderer;
|
||||||
|
import ghidra.program.model.symbol.Namespace;
|
||||||
|
import ghidra.util.datastruct.CaseInsensitiveDuplicateStringComparator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the drop down text field model for namespaces.
|
||||||
|
*/
|
||||||
|
public class NamespaceDropDownModel implements DropDownTextFieldDataModel<Namespace> {
|
||||||
|
private static final char END_CHAR = '\uffff';
|
||||||
|
private List<Namespace> namespaces;
|
||||||
|
private ListCellRenderer<Namespace> renderer;
|
||||||
|
private Comparator<Namespace> comparator =
|
||||||
|
(n1, n2) -> n1.getName().compareToIgnoreCase(n2.getName());
|
||||||
|
private Comparator<String> stringComparator = new CaseInsensitiveDuplicateStringComparator();
|
||||||
|
|
||||||
|
NamespaceDropDownModel() {
|
||||||
|
renderer = GListCellRenderer.createDefaultTextRenderer(this::getListDisplay);
|
||||||
|
setNamespaces(Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Namespace> getMatchingData(String searchText) {
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"Method no longer supported. Instead, call getMatchingData(String, SearchMode)");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNamespaces(List<Namespace> namespaces) {
|
||||||
|
this.namespaces = namespaces;
|
||||||
|
Collections.sort(namespaces, comparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getListDisplay(Namespace namespace) {
|
||||||
|
StringBuilder buf = new StringBuilder(namespace.getName());
|
||||||
|
Namespace parentNamespace = namespace.getParentNamespace();
|
||||||
|
if (parentNamespace != null) {
|
||||||
|
buf.append(" (");
|
||||||
|
buf.append(parentNamespace.getName(true));
|
||||||
|
buf.append(")");
|
||||||
|
}
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Namespace> getMatchingData(String searchText, SearchMode searchMode) {
|
||||||
|
if (StringUtils.isBlank(searchText)) {
|
||||||
|
return new ArrayList<>(namespaces);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!getSupportedSearchModes().contains(searchMode)) {
|
||||||
|
throw new IllegalArgumentException("Unsupported SearchMode: " + searchMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchMode == SearchMode.STARTS_WITH) {
|
||||||
|
return getMatchingDataStartsWith(searchText);
|
||||||
|
}
|
||||||
|
|
||||||
|
Pattern p = searchMode.createPattern(searchText);
|
||||||
|
return getMatchingDataRegex(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Namespace> getMatchingDataRegex(Pattern p) {
|
||||||
|
List<Namespace> results = new ArrayList<>();
|
||||||
|
for (Namespace namespace : namespaces) {
|
||||||
|
String namespacePath = namespace.getName(true);
|
||||||
|
Matcher m = p.matcher(namespacePath);
|
||||||
|
if (m.matches()) {
|
||||||
|
results.add(namespace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Namespace> getMatchingDataStartsWith(String searchText) {
|
||||||
|
MappedList<Namespace, String> list = new MappedList<>(namespaces, n -> n.getName());
|
||||||
|
|
||||||
|
int startIndex = Collections.binarySearch(list, searchText, stringComparator);
|
||||||
|
int endIndex = Collections.binarySearch(list, searchText + END_CHAR, stringComparator);
|
||||||
|
|
||||||
|
// the binary search returns a negative, incremented position if there is no match in the
|
||||||
|
// list for the given search
|
||||||
|
if (startIndex < 0) {
|
||||||
|
startIndex = -startIndex - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endIndex < 0) {
|
||||||
|
endIndex = -endIndex - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return namespaces.subList(startIndex, endIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SearchMode> getSupportedSearchModes() {
|
||||||
|
return List.of(SearchMode.STARTS_WITH, SearchMode.CONTAINS, SearchMode.WILDCARD);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIndexOfFirstMatchingEntry(List<Namespace> data, String text) {
|
||||||
|
// The data are sorted such that lower-case is before upper-case and smaller length
|
||||||
|
// matches come before longer matches. If we ever find a case-sensitive exact match,
|
||||||
|
// use that. Otherwise, keep looking for a case-insensitive exact match. The
|
||||||
|
// case-insensitive match is preferred over a non-matching item. Once we get to a
|
||||||
|
// non-matching item, we can quit.
|
||||||
|
int lastPreferredMatchIndex = -1;
|
||||||
|
for (int i = 0; i < data.size(); i++) {
|
||||||
|
Namespace namespace = data.get(i);
|
||||||
|
String name = namespace.getName();
|
||||||
|
if (name.equals(text)) {
|
||||||
|
// an exact match is the best possible match!
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.equalsIgnoreCase(text)) {
|
||||||
|
// keep going, but remember this location, in case we don't find any more matches
|
||||||
|
lastPreferredMatchIndex = i;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// we've encountered a non-matching entry--nothing left to search
|
||||||
|
return lastPreferredMatchIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1; // we only get here when the list is empty
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ListCellRenderer<Namespace> getListRenderer() {
|
||||||
|
return renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription(Namespace value) {
|
||||||
|
return value.getName(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayText(Namespace value) {
|
||||||
|
return value.getName(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an read-only mapped view List of type T from a List of type S.
|
||||||
|
* @param <S> The type of elements in the source list
|
||||||
|
* @param <T> The type of elements in the mapped list
|
||||||
|
*/
|
||||||
|
private static class MappedList<S, T> extends AbstractList<T> {
|
||||||
|
private final List<S> sourceList;
|
||||||
|
private final Function<S, T> transformer;
|
||||||
|
|
||||||
|
public MappedList(List<S> sourceList, Function<S, T> transformer) {
|
||||||
|
this.sourceList = sourceList;
|
||||||
|
this.transformer = transformer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T get(int index) {
|
||||||
|
return transformer.apply(sourceList.get(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return sourceList.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
/* ###
|
||||||
|
* 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.label;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
import org.junit.*;
|
||||||
|
|
||||||
|
import ghidra.app.cmd.function.CreateFunctionCmd;
|
||||||
|
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||||
|
import ghidra.app.plugin.core.navigation.GoToAddressLabelPlugin;
|
||||||
|
import ghidra.app.util.AddEditDialog;
|
||||||
|
import ghidra.app.util.NamespaceChooserDialog;
|
||||||
|
import ghidra.app.util.viewer.field.MnemonicFieldFactory;
|
||||||
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
|
import ghidra.program.model.address.Address;
|
||||||
|
import ghidra.program.model.address.AddressSet;
|
||||||
|
import ghidra.program.model.listing.Function;
|
||||||
|
import ghidra.program.model.listing.Program;
|
||||||
|
import ghidra.program.model.symbol.*;
|
||||||
|
import ghidra.test.*;
|
||||||
|
|
||||||
|
public class AddEditDialogWithNamespaceChooserTest extends AbstractGhidraHeadedIntegrationTest {
|
||||||
|
private TestEnv env;
|
||||||
|
private Program program;
|
||||||
|
private AddEditDialog dialog;
|
||||||
|
private PluginTool tool;
|
||||||
|
private LabelMgrPlugin plugin;
|
||||||
|
private JComboBox<?> namespacesComboBox;
|
||||||
|
private Symbol symbol1;
|
||||||
|
private Symbol symbol2;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
env = new TestEnv();
|
||||||
|
|
||||||
|
ClassicSampleX86ProgramBuilder builder = new ClassicSampleX86ProgramBuilder();
|
||||||
|
builder.createNamespace("Foo");
|
||||||
|
builder.createNamespace("Bar");
|
||||||
|
program = builder.getProgram();
|
||||||
|
|
||||||
|
tool = env.showTool(program);
|
||||||
|
tool.addPlugin(CodeBrowserPlugin.class.getName());
|
||||||
|
tool.addPlugin(LabelMgrPlugin.class.getName());
|
||||||
|
tool.addPlugin(GoToAddressLabelPlugin.class.getName());
|
||||||
|
|
||||||
|
plugin = getPlugin(tool, LabelMgrPlugin.class);
|
||||||
|
|
||||||
|
symbol1 = builder.createLabel("0x10065a6", "symbol1");
|
||||||
|
symbol2 = builder.createLabel("0x10065a9", "symbol2");
|
||||||
|
createEntryFunction();
|
||||||
|
builder.dispose();
|
||||||
|
|
||||||
|
dialog = getAddEditDialog();
|
||||||
|
|
||||||
|
namespacesComboBox = dialog.getNamespaceComboBox();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private AddEditDialog getAddEditDialog() {
|
||||||
|
return runSwing(() -> plugin.getAddEditDialog());
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
close(dialog);
|
||||||
|
env.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUsingNamespaceChooser() throws Exception {
|
||||||
|
|
||||||
|
editLabel(symbol1);
|
||||||
|
assertNamespaces("Global", "entry");
|
||||||
|
assertSelected("Global");
|
||||||
|
chooseNamespaceFromChooser("Foo");
|
||||||
|
assertSelected("Foo");
|
||||||
|
pressOk();
|
||||||
|
|
||||||
|
editLabel(symbol2);
|
||||||
|
assertNamespaces("Global", "entry", "Foo");
|
||||||
|
assertSelected("Global");
|
||||||
|
chooseNamespaceFromChooser("Bar");
|
||||||
|
assertSelected("Bar");
|
||||||
|
pressOk();
|
||||||
|
|
||||||
|
editLabel(symbol1);
|
||||||
|
assertNamespaces("Global", "Foo", "entry", "Bar");
|
||||||
|
assertSelected("Foo");
|
||||||
|
pressOk();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertSelected(String name) {
|
||||||
|
assertEquals(name, getSelectedNamespace().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertNamespaces(String... names) {
|
||||||
|
List<Namespace> comboNamespaces = getComboNamespaces();
|
||||||
|
assertEquals(names.length, comboNamespaces.size());
|
||||||
|
for (int i = 0; i < names.length; i++) {
|
||||||
|
assertEquals(names[i], comboNamespaces.get(i).getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void chooseNamespaceFromChooser(String text) {
|
||||||
|
AbstractButton button = findButtonByName(dialog.getComponent(), "BrowseButton");
|
||||||
|
pressButton(button, false);
|
||||||
|
NamespaceChooserDialog nd = waitForDialogComponent(NamespaceChooserDialog.class);
|
||||||
|
JTextField field = findComponent(nd.getComponent(), JTextField.class);
|
||||||
|
typeText(field, text, true);
|
||||||
|
enter(field);
|
||||||
|
pressButtonByText(nd.getComponent(), "OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void enter(JTextField field) {
|
||||||
|
triggerActionKey(field, 0, KeyEvent.VK_ENTER);
|
||||||
|
waitForSwing();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void typeText(JTextField textField, String text, boolean expectWindow) {
|
||||||
|
waitForSwing();
|
||||||
|
triggerText(textField, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
// Private Methods
|
||||||
|
//==================================================================================================
|
||||||
|
|
||||||
|
private void pressOk() {
|
||||||
|
pressButtonByText(dialog, "OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Namespace getSelectedNamespace() {
|
||||||
|
return runSwing(() -> {
|
||||||
|
Object selectedItem = namespacesComboBox.getSelectedItem();
|
||||||
|
return ((AddEditDialog.NamespaceWrapper) selectedItem).getNamespace();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Namespace> getComboNamespaces() {
|
||||||
|
return runSwing(() -> {
|
||||||
|
List<Namespace> namespaces = new ArrayList<>();
|
||||||
|
int count = namespacesComboBox.getItemCount();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
Object itemAt = namespacesComboBox.getItemAt(i);
|
||||||
|
namespaces.add(((AddEditDialog.NamespaceWrapper) itemAt).getNamespace());
|
||||||
|
}
|
||||||
|
return namespaces;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void editLabel(final Symbol symbol) {
|
||||||
|
// this makes debugging easier
|
||||||
|
CodeBrowserPlugin cb = getPlugin(tool, CodeBrowserPlugin.class);
|
||||||
|
cb.goToField(symbol.getAddress(), MnemonicFieldFactory.FIELD_NAME, 0, 0);
|
||||||
|
|
||||||
|
runSwing(() -> dialog.editLabel(symbol, program), false);
|
||||||
|
waitForSwing();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createEntryFunction() throws Exception {
|
||||||
|
Symbol s = getUniqueSymbol(program, "entry", null);
|
||||||
|
Function f = program.getListing().getFunctionAt(s.getAddress());
|
||||||
|
if (f == null) {
|
||||||
|
Address addr = s.getAddress();
|
||||||
|
AddressSet body = new AddressSet(addr, addr.getNewAddress(0x010065cc));
|
||||||
|
body.addRange(addr.getNewAddress(0x10065a4), addr.getNewAddress(0x010065cc));
|
||||||
|
CreateFunctionCmd cmd =
|
||||||
|
new CreateFunctionCmd(null, addr, body, SourceType.USER_DEFINED);
|
||||||
|
assertTrue(tool.execute(cmd, program));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -19,8 +19,8 @@ import static org.junit.Assert.*;
|
|||||||
|
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
import java.awt.Window;
|
import java.awt.Window;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
@@ -86,11 +86,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private AddEditDialog getAddEditDialog() {
|
private AddEditDialog getAddEditDialog() {
|
||||||
AtomicReference<AddEditDialog> ref = new AtomicReference<>();
|
return runSwing(() -> plugin.getAddEditDialog());
|
||||||
runSwing(() -> {
|
|
||||||
ref.set(plugin.getAddEditDialog());
|
|
||||||
});
|
|
||||||
return ref.get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -106,7 +102,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
assertEquals("", getText());
|
assertEquals("", getText());
|
||||||
assertTrue(!entryCheckBox.isSelected());
|
assertTrue(!entryCheckBox.isSelected());
|
||||||
|
|
||||||
Namespace scope = getScope();
|
Namespace scope = getSelectedNamespace();
|
||||||
assertEquals(globalScope, scope);
|
assertEquals(globalScope, scope);
|
||||||
assertTrue(primaryCheckBox.isSelected());
|
assertTrue(primaryCheckBox.isSelected());
|
||||||
assertTrue(!primaryCheckBox.isEnabled());
|
assertTrue(!primaryCheckBox.isEnabled());
|
||||||
@@ -195,42 +191,19 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
|
|
||||||
Address address = addr(0x10065a6);
|
Address address = addr(0x10065a6);
|
||||||
Symbol symbol = st.createLabel(address, "sybmol1", scope4, SourceType.USER_DEFINED);
|
Symbol symbol = st.createLabel(address, "sybmol1", scope4, SourceType.USER_DEFINED);
|
||||||
editLabel(symbol);
|
|
||||||
|
|
||||||
JComboBox<?> namespaceChoices = (JComboBox<?>) getInstanceField("namespaceChoices", dialog);
|
|
||||||
|
|
||||||
// get a list of all namespaces in order to compare with the values
|
|
||||||
// from the combo box
|
|
||||||
// the first item should always be the global namespace...
|
|
||||||
assertEquals("The first item in the list of namespaces is not the global namespace",
|
|
||||||
program.getGlobalNamespace(), getScope(0));
|
|
||||||
|
|
||||||
assertEquals("The symbol's namespace was not put into the proper place in the list.",
|
|
||||||
getScope(2), symbol.getParentNamespace());
|
|
||||||
|
|
||||||
// ...then, each namespace should be a child of the following
|
|
||||||
// namespace
|
|
||||||
int itemCount = namespaceChoices.getItemCount();
|
|
||||||
for (int i = 1; i < itemCount - 1; i++) {
|
|
||||||
Namespace currentNamespace = getScope(i);
|
|
||||||
Namespace nextNamespace = getScope(i + 1);
|
|
||||||
Namespace actualParent = currentNamespace.getParentNamespace();
|
|
||||||
|
|
||||||
assertTrue("The namespaces are not in order in the " +
|
|
||||||
"namespaceChoices combo box. Each item should be a child of " + "following item.",
|
|
||||||
nextNamespace.equals(actualParent));
|
|
||||||
}
|
|
||||||
|
|
||||||
// finally, the last item should be parented on the global namespace
|
|
||||||
Namespace currentNamespace = getScope(itemCount - 1);
|
|
||||||
Namespace actualParent = currentNamespace.getParentNamespace();
|
|
||||||
|
|
||||||
assertTrue(
|
|
||||||
"The namespaces are not in order in the " +
|
|
||||||
"namespaceChoices combo box. Each item should be a child of " + "following item.",
|
|
||||||
actualParent.equals(program.getGlobalNamespace()));
|
|
||||||
|
|
||||||
program.endTransaction(transactionID, true);
|
program.endTransaction(transactionID, true);
|
||||||
|
|
||||||
|
editLabel(symbol);
|
||||||
|
List<Namespace> comboNamespaces = getComboNamespaces();
|
||||||
|
assertEquals(3, comboNamespaces.size());
|
||||||
|
|
||||||
|
// currently, combo is populated with: global, current symbol namespace, containing function
|
||||||
|
// namespace if in a function body, and then recently used (there are no recently used in
|
||||||
|
// this test)
|
||||||
|
assertEquals(program.getGlobalNamespace(), comboNamespaces.get(0));
|
||||||
|
assertEquals(scope4, comboNamespaces.get(1));
|
||||||
|
assertEquals(f, comboNamespaces.get(2));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -892,33 +865,23 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
|
|||||||
runSwing(() -> checkbox.setSelected(value));
|
runSwing(() -> checkbox.setSelected(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Namespace getScope() {
|
private Namespace getSelectedNamespace() {
|
||||||
final AtomicReference<Namespace> ref = new AtomicReference<>();
|
return runSwing(() -> {
|
||||||
runSwing(() -> {
|
|
||||||
Object selectedItem = namespacesComboBox.getSelectedItem();
|
Object selectedItem = namespacesComboBox.getSelectedItem();
|
||||||
Namespace ns = ((AddEditDialog.NamespaceWrapper) selectedItem).getNamespace();
|
return ((AddEditDialog.NamespaceWrapper) selectedItem).getNamespace();
|
||||||
ref.set(ns);
|
|
||||||
});
|
});
|
||||||
return ref.get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Namespace getScope(final int index) {
|
private List<Namespace> getComboNamespaces() {
|
||||||
final AtomicReference<Namespace> ref = new AtomicReference<>();
|
return runSwing(() -> {
|
||||||
runSwing(() -> {
|
List<Namespace> namespaces = new ArrayList<>();
|
||||||
int count = namespacesComboBox.getItemCount();
|
int count = namespacesComboBox.getItemCount();
|
||||||
if (count <= index) {
|
for (int i = 0; i < count; i++) {
|
||||||
System.err.println("Available namespaces: ");
|
Object itemAt = namespacesComboBox.getItemAt(i);
|
||||||
for (int i = 0; i < count; i++) {
|
namespaces.add(((AddEditDialog.NamespaceWrapper) itemAt).getNamespace());
|
||||||
System.err.println("\t" + namespacesComboBox.getItemAt(i));
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("No namespace at index: " + index);
|
|
||||||
}
|
}
|
||||||
|
return namespaces;
|
||||||
Object selectedItem = namespacesComboBox.getItemAt(index);
|
|
||||||
Namespace ns = ((AddEditDialog.NamespaceWrapper) selectedItem).getNamespace();
|
|
||||||
ref.set(ns);
|
|
||||||
});
|
});
|
||||||
return ref.get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setScope(final Namespace scope) {
|
private void setScope(final Namespace scope) {
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
/* ###
|
||||||
|
* 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.util;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import generic.test.AbstractGuiTest;
|
||||||
|
import ghidra.program.database.ProgramBuilder;
|
||||||
|
import ghidra.program.database.ProgramDB;
|
||||||
|
import ghidra.program.model.symbol.Namespace;
|
||||||
|
import ghidra.program.model.symbol.SourceType;
|
||||||
|
import ghidra.test.ToyProgramBuilder;
|
||||||
|
|
||||||
|
public class NamespaceCacheTest extends AbstractGuiTest {
|
||||||
|
|
||||||
|
private Namespace a;
|
||||||
|
private Namespace b;
|
||||||
|
private Namespace c;
|
||||||
|
private Namespace d;
|
||||||
|
private Namespace e;
|
||||||
|
private Namespace f;
|
||||||
|
private Namespace g;
|
||||||
|
private Namespace h;
|
||||||
|
private Namespace i;
|
||||||
|
private Namespace j;
|
||||||
|
private Namespace k;
|
||||||
|
private ProgramDB program;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() throws Exception {
|
||||||
|
ProgramBuilder builder = new ToyProgramBuilder("Test", false, this);
|
||||||
|
a = builder.createNamespace("a");
|
||||||
|
b = builder.createNamespace("b", "a", SourceType.USER_DEFINED);
|
||||||
|
c = builder.createNamespace("c", "a::b", SourceType.USER_DEFINED);
|
||||||
|
d = builder.createNamespace("d");
|
||||||
|
e = builder.createNamespace("e");
|
||||||
|
f = builder.createNamespace("f");
|
||||||
|
g = builder.createNamespace("g");
|
||||||
|
h = builder.createNamespace("h");
|
||||||
|
i = builder.createNamespace("i");
|
||||||
|
j = builder.createNamespace("j");
|
||||||
|
k = builder.createNamespace("k");
|
||||||
|
program = builder.getProgram();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetRecent() {
|
||||||
|
NamespaceCache.add(program, a);
|
||||||
|
NamespaceCache.add(program, b);
|
||||||
|
NamespaceCache.add(program, c);
|
||||||
|
|
||||||
|
List<Namespace> recent = NamespaceCache.get(program);
|
||||||
|
assertEquals(c, recent.get(0));
|
||||||
|
assertEquals(b, recent.get(1));
|
||||||
|
assertEquals(a, recent.get(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMostRecentAtTop() {
|
||||||
|
NamespaceCache.add(program, a);
|
||||||
|
NamespaceCache.add(program, b);
|
||||||
|
NamespaceCache.add(program, c);
|
||||||
|
NamespaceCache.add(program, a);
|
||||||
|
|
||||||
|
List<Namespace> recents = NamespaceCache.get(program);
|
||||||
|
assertEquals(3, recents.size());
|
||||||
|
assertEquals(a, recents.get(0));
|
||||||
|
assertEquals(c, recents.get(1));
|
||||||
|
assertEquals(b, recents.get(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMaxRecents() {
|
||||||
|
NamespaceCache.add(program, a);
|
||||||
|
NamespaceCache.add(program, b);
|
||||||
|
NamespaceCache.add(program, c);
|
||||||
|
NamespaceCache.add(program, d);
|
||||||
|
NamespaceCache.add(program, e);
|
||||||
|
NamespaceCache.add(program, f);
|
||||||
|
NamespaceCache.add(program, g);
|
||||||
|
NamespaceCache.add(program, h);
|
||||||
|
NamespaceCache.add(program, i);
|
||||||
|
NamespaceCache.add(program, j);
|
||||||
|
NamespaceCache.add(program, k);
|
||||||
|
|
||||||
|
List<Namespace> recents = NamespaceCache.get(program);
|
||||||
|
assertEquals(NamespaceCache.MAX_RECENTS, recents.size());
|
||||||
|
assertEquals(k, recents.get(0));
|
||||||
|
assertEquals(b, recents.get(9));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClosingProgramClearsRecents() {
|
||||||
|
NamespaceCache.add(program, a);
|
||||||
|
NamespaceCache.add(program, b);
|
||||||
|
NamespaceCache.add(program, c);
|
||||||
|
|
||||||
|
List<Namespace> recents = NamespaceCache.get(program);
|
||||||
|
assertEquals(3, recents.size());
|
||||||
|
|
||||||
|
program.release(this);
|
||||||
|
|
||||||
|
recents = NamespaceCache.get(program);
|
||||||
|
assertTrue(recents.isEmpty());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package ghidra.util.datastruct;
|
package ghidra.util.datastruct;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An ordered set-like data structure.
|
* An ordered set-like data structure.
|
||||||
@@ -51,6 +51,13 @@ public class LRUSet<T> extends LRUMap<T, T> implements Iterable<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return map.keySet().toString();
|
return keySet().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@return a List of elements in this set}
|
||||||
|
*/
|
||||||
|
public List<T> toList() {
|
||||||
|
return new ArrayList<>(keySet());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,18 +68,6 @@ public class DataTypeEditorsScreenShots extends GhidraScreenShotGenerator {
|
|||||||
positionListingTop(0x40D3B8);
|
positionListingTop(0x40D3B8);
|
||||||
DropDownSelectionTextField<?> textField = showTypeChooserDialog();
|
DropDownSelectionTextField<?> textField = showTypeChooserDialog();
|
||||||
|
|
||||||
triggerText(textField, "undefined");
|
|
||||||
|
|
||||||
DialogComponentProvider dialog = getDialog();
|
|
||||||
JComponent component = dialog.getComponent();
|
|
||||||
Window dataTypeDialog = windowForComponent(component);
|
|
||||||
Window[] popUpWindows = dataTypeDialog.getOwnedWindows();
|
|
||||||
|
|
||||||
List<Component> dataTypeWindows = new ArrayList<>(Arrays.asList(popUpWindows));
|
|
||||||
dataTypeWindows.add(dataTypeDialog);
|
|
||||||
|
|
||||||
captureComponents(dataTypeWindows);
|
|
||||||
closeAllWindows();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private DropDownSelectionTextField<?> showTypeChooserDialog() throws Exception {
|
private DropDownSelectionTextField<?> showTypeChooserDialog() throws Exception {
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -23,8 +23,7 @@ import org.junit.Test;
|
|||||||
|
|
||||||
import docking.widgets.combobox.GhidraComboBox;
|
import docking.widgets.combobox.GhidraComboBox;
|
||||||
import ghidra.app.plugin.core.label.*;
|
import ghidra.app.plugin.core.label.*;
|
||||||
import ghidra.app.util.AddEditDialog;
|
import ghidra.app.util.*;
|
||||||
import ghidra.app.util.EditFieldNameDialog;
|
|
||||||
import ghidra.program.model.address.*;
|
import ghidra.program.model.address.*;
|
||||||
import ghidra.program.model.symbol.LabelHistory;
|
import ghidra.program.model.symbol.LabelHistory;
|
||||||
|
|
||||||
@@ -76,11 +75,21 @@ public class LabelMgrPluginScreenShots extends GhidraScreenShotGenerator {
|
|||||||
captureDialog();
|
captureDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChooseNamespace() {
|
||||||
|
runSwingLater(() -> {
|
||||||
|
NamespaceChooserDialog dialog = new NamespaceChooserDialog();
|
||||||
|
dialog.getNameSpace(program);
|
||||||
|
});
|
||||||
|
waitForDialogComponent(NamespaceChooserDialog.class);
|
||||||
|
captureDialog();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSetLabel() {
|
public void testSetLabel() {
|
||||||
LabelMgrPlugin plugin = getPlugin(tool, LabelMgrPlugin.class);
|
LabelMgrPlugin plugin = getPlugin(tool, LabelMgrPlugin.class);
|
||||||
final OperandLabelDialog dialog = new OperandLabelDialog(plugin);
|
final OperandLabelDialog dialog = new OperandLabelDialog(plugin);
|
||||||
final GhidraComboBox combo = (GhidraComboBox) getInstanceField("myChoice", dialog);
|
final GhidraComboBox<?> combo = (GhidraComboBox<?>) getInstanceField("myChoice", dialog);
|
||||||
runSwing(new Runnable() {
|
runSwing(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|||||||