Merge remote-tracking branch
'origin/GP-5806_ghidragon_add_namspace_chooser_to_rename_dialog--SQUASHED' (Closes #8263)
@@ -517,6 +517,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/Labels.htm||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/LabelHistoryInputDialog.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>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<UL>
|
||||
<LI>
|
||||
Text field for entering the name of the label. A combo box is included which allows
|
||||
selecting recently used label names.
|
||||
|
||||
@@ -150,35 +148,36 @@
|
||||
</PRE>
|
||||
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>
|
||||
|
||||
<PRE>
|
||||
Global::foo::bar::myLabel
|
||||
|
||||
</PRE>
|
||||
<PRE>
|
||||
Global::foo::bar::myLabel
|
||||
|
||||
</PRE>
|
||||
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
|
||||
path that starts with <B>Global</B>, then the value of the <I>Namespace</I> combo box
|
||||
will be ignored.
|
||||
</LI>
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P><I><B>Namespace</B></I></P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<UL>
|
||||
<LI>The defining scope of the label. The available namespaces are based upon the current
|
||||
address. When editing a label, the available namespaces are not necessarily those in the
|
||||
namespace hierarchy in which the label is located, but rather are those based upon the
|
||||
address of that label. The <B>Global</B> namespace is always included by default, as well
|
||||
as the parent namespace of the current label, if one is being edited.</LI>
|
||||
</UL>
|
||||
<P>The containing namespace for the label or function. </P>
|
||||
<P>The applicable namespaces are based upon the current address and symbol type.
|
||||
The namespace combobox
|
||||
will initially be populated with the most obvious choices for that location, as well
|
||||
any recently chosen namespaces.</P>
|
||||
<P>Next to the combobox, there is a browse button which can be pressed to bring up the
|
||||
<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>
|
||||
<P><IMG src="help/shared/note.png" alt="Note:">This field is disabled, if there is a
|
||||
function with a default name at this address. The namespace will stay set to the parent
|
||||
namespace of the function and the label name you enter will become the new function
|
||||
name.<BR>
|
||||
<P><IMG src="help/shared/note.png" alt="Note:">This field is disabled when the namespace
|
||||
at that location can't be changed. For example, parameters and local variables can only
|
||||
have the enclosing function as its namespace. <BR>
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
@@ -186,38 +185,66 @@
|
||||
<P><I><B>Entry Point</B></I></P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<UL>
|
||||
<LI>Sets the entry point property for address associated with this label. Setting this
|
||||
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>
|
||||
</UL>
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P><I><B>Primary</B></I></P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<UL>
|
||||
<LI>Sets the primary property for this symbol. If there is only one symbol at this
|
||||
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
|
||||
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.
|
||||
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
|
||||
select the checkbox, the function is renamed to the new label that you entered. The
|
||||
function symbol must always be the primary symbol.</LI>
|
||||
</UL>
|
||||
function symbol must always be the primary symbol.
|
||||
|
||||
</BLOCKQUOTE>
|
||||
<P><I><B>Pinned</B></I></P>
|
||||
<BLOCKQUOTE>
|
||||
<UL>
|
||||
<LI>Sets the label to pinned. A pinned label will not move if the image base is changed
|
||||
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 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,
|
||||
data, or function labels may be pinned.</LI>
|
||||
</UL>
|
||||
data, or function labels may be pinned.
|
||||
</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>
|
||||
|
||||
|
||||
|
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.ReusableDialogComponentProvider;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.button.BrowseButton;
|
||||
import docking.widgets.checkbox.GCheckBox;
|
||||
import docking.widgets.combobox.GhidraComboBox;
|
||||
import ghidra.app.cmd.label.*;
|
||||
@@ -46,6 +47,7 @@ import ghidra.util.layout.VerticalLayout;
|
||||
*/
|
||||
public class AddEditDialog extends ReusableDialogComponentProvider {
|
||||
private static final int MAX_RETENTION = 10;
|
||||
|
||||
private PluginTool tool;
|
||||
private TitledBorder nameBorder;
|
||||
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() {
|
||||
namespaceChoices.removeAllItems();
|
||||
|
||||
for (Namespace namespace : getSelectableNamespaces()) {
|
||||
namespaceChoices.addItem(new NamespaceWrapper(namespace));
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<Namespace> getSelectableNamespaces() {
|
||||
if (!namespaceChoices.isEnabled()) {
|
||||
namespaceChoices.addItem(new NamespaceWrapper(symbol.getParentNamespace()));
|
||||
selectNamespace();
|
||||
return List.of(symbol.getParentNamespace());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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();
|
||||
|
||||
NamespaceWrapper composite = new NamespaceWrapper(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);
|
||||
}
|
||||
}
|
||||
if (!namespaces.contains(globalNamespace)) {
|
||||
namespaces.add(globalNamespace);
|
||||
}
|
||||
}
|
||||
|
||||
private void addCurrentNamespace(SequencedSet<Namespace> namespaces) {
|
||||
if (symbol != null) {
|
||||
// we are adding the current namespace of the symbol if it is not in
|
||||
// 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);
|
||||
}
|
||||
namespaces.add(symbol.getParentNamespace());
|
||||
}
|
||||
|
||||
selectNamespace();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -478,6 +490,7 @@ public class AddEditDialog extends ReusableDialogComponentProvider {
|
||||
|
||||
namespaceChoices.setEnabled(true);
|
||||
initNamespaces();
|
||||
selectNamespace();
|
||||
clearStatusText();
|
||||
|
||||
}
|
||||
@@ -538,6 +551,7 @@ public class AddEditDialog extends ReusableDialogComponentProvider {
|
||||
namespaceChoices.setEnabled(true);
|
||||
}
|
||||
initNamespaces();
|
||||
selectNamespace();
|
||||
clearStatusText();
|
||||
|
||||
}
|
||||
@@ -550,12 +564,11 @@ public class AddEditDialog extends ReusableDialogComponentProvider {
|
||||
@Override
|
||||
public Dimension getPreferredSize() {
|
||||
Dimension size = super.getPreferredSize();
|
||||
// change the preferred size to use the width determined by the # of columns in
|
||||
// combo box editor instead of the largest item in the combo box data model to
|
||||
// prevent the dialog from growing huge when a large label gets added to its recent
|
||||
// items
|
||||
Dimension editorSize = getEditor().getEditorComponent().getPreferredSize();
|
||||
size.width = editorSize.width;
|
||||
// Change the preferred size to use a standard starting width. Previously, it
|
||||
// was sized on the first label edited, but then it just remembered that size.
|
||||
// A width of 500 is a good starting width that will fit most reasonable
|
||||
// namespace names.
|
||||
size.width = 500;
|
||||
return size;
|
||||
}
|
||||
};
|
||||
@@ -598,7 +611,8 @@ public class AddEditDialog extends ReusableDialogComponentProvider {
|
||||
mainPanel.add(bottomPanel);
|
||||
|
||||
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(primaryCheckBox);
|
||||
bottomPanel.add(pinnedCheckBox);
|
||||
@@ -610,6 +624,26 @@ public class AddEditDialog extends ReusableDialogComponentProvider {
|
||||
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() {
|
||||
labelNameChoices.addActionListener(e -> {
|
||||
if (program != null) {
|
||||
@@ -636,6 +670,11 @@ public class AddEditDialog extends ReusableDialogComponentProvider {
|
||||
return text;
|
||||
}
|
||||
|
||||
// for testing
|
||||
public JComboBox<?> getNamespaceComboBox() {
|
||||
return namespaceChoices;
|
||||
}
|
||||
|
||||
public class NamespaceWrapper {
|
||||
private Namespace namespace;
|
||||
|
||||
@@ -672,4 +711,5 @@ public class AddEditDialog extends ReusableDialogComponentProvider {
|
||||
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");
|
||||
* 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.
|
||||
@@ -19,8 +19,8 @@ import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Window;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
@@ -86,11 +86,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
}
|
||||
|
||||
private AddEditDialog getAddEditDialog() {
|
||||
AtomicReference<AddEditDialog> ref = new AtomicReference<>();
|
||||
runSwing(() -> {
|
||||
ref.set(plugin.getAddEditDialog());
|
||||
});
|
||||
return ref.get();
|
||||
return runSwing(() -> plugin.getAddEditDialog());
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -106,7 +102,7 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
assertEquals("", getText());
|
||||
assertTrue(!entryCheckBox.isSelected());
|
||||
|
||||
Namespace scope = getScope();
|
||||
Namespace scope = getSelectedNamespace();
|
||||
assertEquals(globalScope, scope);
|
||||
assertTrue(primaryCheckBox.isSelected());
|
||||
assertTrue(!primaryCheckBox.isEnabled());
|
||||
@@ -195,42 +191,19 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
Address address = addr(0x10065a6);
|
||||
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);
|
||||
|
||||
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
|
||||
@@ -892,33 +865,23 @@ public class AddEditDialoglTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
runSwing(() -> checkbox.setSelected(value));
|
||||
}
|
||||
|
||||
private Namespace getScope() {
|
||||
final AtomicReference<Namespace> ref = new AtomicReference<>();
|
||||
runSwing(() -> {
|
||||
private Namespace getSelectedNamespace() {
|
||||
return runSwing(() -> {
|
||||
Object selectedItem = namespacesComboBox.getSelectedItem();
|
||||
Namespace ns = ((AddEditDialog.NamespaceWrapper) selectedItem).getNamespace();
|
||||
ref.set(ns);
|
||||
return ((AddEditDialog.NamespaceWrapper) selectedItem).getNamespace();
|
||||
});
|
||||
return ref.get();
|
||||
}
|
||||
|
||||
private Namespace getScope(final int index) {
|
||||
final AtomicReference<Namespace> ref = new AtomicReference<>();
|
||||
runSwing(() -> {
|
||||
private List<Namespace> getComboNamespaces() {
|
||||
return runSwing(() -> {
|
||||
List<Namespace> namespaces = new ArrayList<>();
|
||||
int count = namespacesComboBox.getItemCount();
|
||||
if (count <= index) {
|
||||
System.err.println("Available namespaces: ");
|
||||
for (int i = 0; i < count; i++) {
|
||||
System.err.println("\t" + namespacesComboBox.getItemAt(i));
|
||||
}
|
||||
throw new IllegalArgumentException("No namespace at index: " + index);
|
||||
for (int i = 0; i < count; i++) {
|
||||
Object itemAt = namespacesComboBox.getItemAt(i);
|
||||
namespaces.add(((AddEditDialog.NamespaceWrapper) itemAt).getNamespace());
|
||||
}
|
||||
|
||||
Object selectedItem = namespacesComboBox.getItemAt(index);
|
||||
Namespace ns = ((AddEditDialog.NamespaceWrapper) selectedItem).getNamespace();
|
||||
ref.set(ns);
|
||||
return namespaces;
|
||||
});
|
||||
return ref.get();
|
||||
}
|
||||
|
||||
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");
|
||||
* 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.
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package ghidra.util.datastruct;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* An ordered set-like data structure.
|
||||
@@ -51,6 +51,13 @@ public class LRUSet<T> extends LRUMap<T, T> implements Iterable<T> {
|
||||
|
||||
@Override
|
||||
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);
|
||||
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 {
|
||||
|
||||
@@ -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.
|
||||
@@ -23,8 +23,7 @@ import org.junit.Test;
|
||||
|
||||
import docking.widgets.combobox.GhidraComboBox;
|
||||
import ghidra.app.plugin.core.label.*;
|
||||
import ghidra.app.util.AddEditDialog;
|
||||
import ghidra.app.util.EditFieldNameDialog;
|
||||
import ghidra.app.util.*;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.symbol.LabelHistory;
|
||||
|
||||
@@ -76,11 +75,21 @@ public class LabelMgrPluginScreenShots extends GhidraScreenShotGenerator {
|
||||
captureDialog();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChooseNamespace() {
|
||||
runSwingLater(() -> {
|
||||
NamespaceChooserDialog dialog = new NamespaceChooserDialog();
|
||||
dialog.getNameSpace(program);
|
||||
});
|
||||
waitForDialogComponent(NamespaceChooserDialog.class);
|
||||
captureDialog();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetLabel() {
|
||||
LabelMgrPlugin plugin = getPlugin(tool, LabelMgrPlugin.class);
|
||||
final OperandLabelDialog dialog = new OperandLabelDialog(plugin);
|
||||
final GhidraComboBox combo = (GhidraComboBox) getInstanceField("myChoice", dialog);
|
||||
final GhidraComboBox<?> combo = (GhidraComboBox<?>) getInstanceField("myChoice", dialog);
|
||||
runSwing(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||