mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-03 09:49:23 +02:00
GP-5474 - Symbol Tree - Event handling improvements to maintain user view position; added an option for org node group threshold; Fixed missing nodes under classes
This commit is contained in:
parent
397a814f5f
commit
5f17963eba
21 changed files with 1053 additions and 157 deletions
|
@ -114,7 +114,7 @@ public class DisconnectedSymbolTreeProvider extends SymbolTreeProvider {
|
|||
|
||||
@Override
|
||||
protected SymbolTreeRootNode createRootNode() {
|
||||
return new ConfigurableSymbolTreeRootNode(program);
|
||||
return new ConfigurableSymbolTreeRootNode(program, getNodeGroupThreshold());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -22,12 +22,14 @@ import ghidra.app.CorePluginPackage;
|
|||
import ghidra.app.events.*;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.services.GoToService;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.options.*;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.symbol.*;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.bean.opteditor.OptionsVetoException;
|
||||
|
||||
//@formatter:off
|
||||
@PluginInfo(
|
||||
|
@ -36,8 +38,8 @@ import ghidra.program.util.ProgramLocation;
|
|||
category = PluginCategoryNames.COMMON,
|
||||
shortDescription = "Symbol Tree",
|
||||
description = "This plugin shows the symbols from the program " +
|
||||
"in a tree hierarchy. All symbols (except for the global namespace symbol)" +
|
||||
" have a parent symbol. From the tree, symbols can be renamed, deleted, or " +
|
||||
"in a tree hierarchy. All symbols (except for the global namespace symbol) " +
|
||||
"have a parent symbol. From the tree, symbols can be renamed, deleted, or " +
|
||||
"reorganized.",
|
||||
eventsConsumed = { ProgramActivatedPluginEvent.class, ProgramLocationPluginEvent.class, ProgramClosedPluginEvent.class },
|
||||
servicesProvided = { SymbolTreeService.class }
|
||||
|
@ -45,7 +47,8 @@ import ghidra.program.util.ProgramLocation;
|
|||
//@formatter:on
|
||||
public class SymbolTreePlugin extends Plugin implements SymbolTreeService {
|
||||
|
||||
public static final String PLUGIN_NAME = "SymbolTreePlugin";
|
||||
private static final String OPTIONS_CATEGORY = "Symbol Tree";
|
||||
private static final String OPTION_NAME_GROUP_THRESHOLD = "Group Threshold";
|
||||
|
||||
private SymbolTreeProvider connectedProvider;
|
||||
private List<SymbolTreeProvider> disconnectedProviders = new ArrayList<>();
|
||||
|
@ -53,8 +56,12 @@ public class SymbolTreePlugin extends Plugin implements SymbolTreeService {
|
|||
private GoToService goToService;
|
||||
private boolean processingGoTo;
|
||||
|
||||
private OptionsChangeListener optionsListener = new SymbolTreeOptionsListener();
|
||||
private int nodeGroupThreshold = 200;
|
||||
|
||||
public SymbolTreePlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
|
||||
connectedProvider = new SymbolTreeProvider(tool, this);
|
||||
}
|
||||
|
||||
|
@ -108,6 +115,20 @@ public class SymbolTreePlugin extends Plugin implements SymbolTreeService {
|
|||
@Override
|
||||
protected void init() {
|
||||
goToService = tool.getService(GoToService.class);
|
||||
|
||||
initializeOptions();
|
||||
}
|
||||
|
||||
private void initializeOptions() {
|
||||
|
||||
ToolOptions options = tool.getOptions(OPTIONS_CATEGORY);
|
||||
options.addOptionsChangeListener(optionsListener);
|
||||
|
||||
HelpLocation help = new HelpLocation("SymbolTreePlugin", "GroupNode");
|
||||
options.registerOption(OPTION_NAME_GROUP_THRESHOLD, nodeGroupThreshold, help,
|
||||
"The max number of children before nodes are organized by name");
|
||||
|
||||
nodeGroupThreshold = options.getInt(OPTION_NAME_GROUP_THRESHOLD, nodeGroupThreshold);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -192,4 +213,25 @@ public class SymbolTreePlugin extends Plugin implements SymbolTreeService {
|
|||
connectedProvider.selectSymbol(symbol);
|
||||
|
||||
}
|
||||
|
||||
int getNodeGroupThreshold() {
|
||||
return nodeGroupThreshold;
|
||||
}
|
||||
|
||||
private class SymbolTreeOptionsListener implements OptionsChangeListener {
|
||||
|
||||
@Override
|
||||
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
|
||||
Object newValue) throws OptionsVetoException {
|
||||
|
||||
if (OPTION_NAME_GROUP_THRESHOLD.equals(optionName)) {
|
||||
nodeGroupThreshold = (int) newValue;
|
||||
connectedProvider.rebuildTree();
|
||||
for (SymbolTreeProvider provider : disconnectedProviders) {
|
||||
provider.rebuildTree();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,31 +75,34 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
|||
* prevent to much work from happening too fast. Also, we perform the work in a bulk task
|
||||
* so that the tree can benefit from optimizations made by the bulk task.
|
||||
*/
|
||||
private List<GTreeTask> bufferedTasks = new ArrayList<>();
|
||||
private List<AbstractSymbolUpdateTask> bufferedTasks = new ArrayList<>();
|
||||
private Map<Program, GTreeState> treeStateMap = new HashMap<>();
|
||||
private SwingUpdateManager domainChangeUpdateManager = new SwingUpdateManager(1000,
|
||||
AbstractSwingUpdateManager.DEFAULT_MAX_DELAY, "Symbol Tree Provider", () -> {
|
||||
AbstractSwingUpdateManager.DEFAULT_MAX_DELAY, "Symbol Tree Provider - Bulk Update", () -> {
|
||||
|
||||
if (bufferedTasks.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bufferedTasks.size() == 1) {
|
||||
//
|
||||
// Single events happen from user operations, like creating namespaces and
|
||||
// rename operations.
|
||||
//
|
||||
// Perform a simple update in the normal fashion (a single, targeted filter
|
||||
// performed when adding changing one symbol is faster than the complete
|
||||
// refilter done by the bulk task below).
|
||||
//
|
||||
tree.runTask(bufferedTasks.remove(0));
|
||||
List<AbstractSymbolUpdateTask> copiedTasks = new ArrayList<>(bufferedTasks);
|
||||
bufferedTasks.clear();
|
||||
tree.runTask(new BulkWorkTask(tree, copiedTasks));
|
||||
});
|
||||
|
||||
// Track the tree state from before undo/redo operations so we can put the user's view back. We
|
||||
// buffer this so the user can perform multiple rapid operations without responding to each one.
|
||||
private GTreeState preRestoreTreeState;
|
||||
private SwingUpdateManager restoredUpdateManager = new SwingUpdateManager(750,
|
||||
AbstractSwingUpdateManager.DEFAULT_MAX_DELAY, "Symbol Tree Provider - Restore", () -> {
|
||||
|
||||
// trigger a delayed refilter to happen after we restore the tree state
|
||||
tree.refilterLater();
|
||||
if (preRestoreTreeState == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayList<GTreeTask> copiedTasks = new ArrayList<>(bufferedTasks);
|
||||
bufferedTasks.clear();
|
||||
tree.runTask(new BulkWorkTask(tree, copiedTasks));
|
||||
tree.restoreTreeState(preRestoreTreeState);
|
||||
preRestoreTreeState = null;
|
||||
});
|
||||
|
||||
public SymbolTreeProvider(PluginTool tool, SymbolTreePlugin plugin) {
|
||||
|
@ -134,7 +137,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
|||
}
|
||||
|
||||
protected SymbolTreeRootNode createRootNode() {
|
||||
return new SymbolTreeRootNode(program);
|
||||
return new SymbolTreeRootNode(program, getNodeGroupThreshold());
|
||||
}
|
||||
|
||||
private JComponent buildProvider() {
|
||||
|
@ -307,6 +310,10 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
|||
// Class Methods
|
||||
//==================================================================================================
|
||||
|
||||
protected int getNodeGroupThreshold() {
|
||||
return plugin.getNodeGroupThreshold();
|
||||
}
|
||||
|
||||
GTree getTree() {
|
||||
return tree;
|
||||
}
|
||||
|
@ -467,9 +474,15 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
|||
// seems safer to cancel an edit rather than to commit it without asking.
|
||||
tree.cancelEditing();
|
||||
|
||||
// preserve the original state to handle multiple undo/redo requests
|
||||
if (preRestoreTreeState == null) {
|
||||
preRestoreTreeState = tree.getTreeState();
|
||||
}
|
||||
|
||||
restoredUpdateManager.updateLater();
|
||||
|
||||
SymbolTreeRootNode node = (SymbolTreeRootNode) tree.getModelRoot();
|
||||
node.setChildren(null);
|
||||
tree.refilterLater();
|
||||
}
|
||||
|
||||
private void symbolChanged(Symbol symbol) {
|
||||
|
@ -488,7 +501,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
|||
addTask(new SymbolRemovedTask(tree, symbol));
|
||||
}
|
||||
|
||||
private void addTask(GTreeTask task) {
|
||||
private void addTask(AbstractSymbolUpdateTask task) {
|
||||
// Note: if we want to call this method from off the Swing thread, then we have to
|
||||
// synchronize on the list that we are adding to here.
|
||||
Swing.assertSwingThread("Adding tasks must be done on the Swing thread," +
|
||||
|
@ -621,6 +634,11 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
|||
|
||||
private void processSymbolChanged(ProgramChangeRecord pcr) {
|
||||
Symbol symbol = (Symbol) pcr.getObject();
|
||||
Object oldValue = pcr.getOldValue();
|
||||
if (oldValue instanceof Namespace oldNs) {
|
||||
addTask(new SymbolScopeChangedTask(tree, symbol, oldNs));
|
||||
return;
|
||||
}
|
||||
symbolChanged(symbol);
|
||||
}
|
||||
|
||||
|
@ -712,12 +730,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
|||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
TreePath[] selectionPaths = tree.getSelectionPaths();
|
||||
doRun(monitor);
|
||||
|
||||
if (selectionPaths.length != 0) {
|
||||
tree.setSelectionPaths(selectionPaths);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -739,7 +752,7 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
|||
|
||||
// the symbol may have been deleted while we are processing bulk changes
|
||||
if (!symbol.isDeleted()) {
|
||||
GTreeNode newNode = rootNode.symbolAdded(symbol);
|
||||
GTreeNode newNode = rootNode.symbolAdded(symbol, monitor);
|
||||
tree.refilterLater(newNode);
|
||||
}
|
||||
}
|
||||
|
@ -762,9 +775,32 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
|||
|
||||
// the symbol may have been deleted while we are processing bulk changes
|
||||
if (!symbol.isDeleted()) {
|
||||
root.symbolAdded(symbol);
|
||||
SymbolNode newNode = root.symbolAdded(symbol, monitor);
|
||||
tree.refilterLater(newNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SymbolScopeChangedTask extends AbstractSymbolUpdateTask {
|
||||
|
||||
private Namespace oldNamespace;
|
||||
|
||||
SymbolScopeChangedTask(GTree tree, Symbol symbol, Namespace oldNamespace) {
|
||||
super(tree, symbol);
|
||||
this.oldNamespace = oldNamespace;
|
||||
}
|
||||
|
||||
@Override
|
||||
void doRun(TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
SymbolTreeRootNode root = (SymbolTreeRootNode) tree.getModelRoot();
|
||||
root.symbolRemoved(symbol, oldNamespace, monitor);
|
||||
|
||||
// the symbol may have been deleted while we are processing bulk changes
|
||||
if (!symbol.isDeleted()) {
|
||||
SymbolNode newNode = root.symbolAdded(symbol, monitor);
|
||||
tree.refilterLater(newNode);
|
||||
}
|
||||
tree.refilterLater();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -778,19 +814,21 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
|||
void doRun(TaskMonitor monitor) throws CancelledException {
|
||||
SymbolTreeRootNode root = (SymbolTreeRootNode) tree.getModelRoot();
|
||||
root.symbolRemoved(symbol, symbol.getName(), monitor);
|
||||
tree.refilterLater();
|
||||
|
||||
// Note: turned this off; less tree flashing seems worth having a parent node still in
|
||||
// the tree that doesn't belong
|
||||
// tree.refilterLater();
|
||||
}
|
||||
}
|
||||
|
||||
private class BulkWorkTask extends GTreeBulkTask {
|
||||
|
||||
// somewhat arbitrary max amount of work to perform...at some point it is faster to
|
||||
// just reload the tree
|
||||
// somewhat arbitrary limit to individual tasks... too many, then reload the tree
|
||||
private static final int MAX_TASK_COUNT = 1000;
|
||||
|
||||
private List<GTreeTask> tasks;
|
||||
private List<AbstractSymbolUpdateTask> tasks;
|
||||
|
||||
BulkWorkTask(GTree gTree, List<GTreeTask> tasks) {
|
||||
BulkWorkTask(GTree gTree, List<AbstractSymbolUpdateTask> tasks) {
|
||||
super(gTree);
|
||||
this.tasks = tasks;
|
||||
}
|
||||
|
@ -803,10 +841,14 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
|||
return;
|
||||
}
|
||||
|
||||
for (GTreeTask task : tasks) {
|
||||
GTreeState state = tree.getTreeState();
|
||||
for (AbstractSymbolUpdateTask task : tasks) {
|
||||
monitor.checkCancelled();
|
||||
task.run(monitor);
|
||||
}
|
||||
|
||||
GTreeRestoreTreeStateTask task = new GTreeRestoreTreeStateTask(tree, state);
|
||||
task.run(monitor);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,12 +17,14 @@ package ghidra.app.plugin.core.symboltree.nodes;
|
|||
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.app.plugin.core.symboltree.SymbolCategory;
|
||||
import ghidra.app.util.NamespaceUtils;
|
||||
import ghidra.program.model.listing.GhidraClass;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.symbol.*;
|
||||
|
@ -56,7 +58,134 @@ public class ClassCategoryNode extends SymbolCategoryNode {
|
|||
}
|
||||
|
||||
@Override
|
||||
public SymbolNode symbolAdded(Symbol symbol) {
|
||||
protected boolean supportsSymbol(Symbol symbol) {
|
||||
SymbolType symbolType = symbol.getSymbolType();
|
||||
if (symbolType == symbolCategory.getSymbolType()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// must be in a class at some level
|
||||
Namespace parentNamespace = symbol.getParentNamespace();
|
||||
while (parentNamespace != null && parentNamespace != globalNamespace) {
|
||||
if (parentNamespace instanceof GhidraClass) {
|
||||
return true;
|
||||
}
|
||||
parentNamespace = parentNamespace.getParentNamespace();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren, TaskMonitor monitor) {
|
||||
|
||||
if ((!isLoaded() && !loadChildren) || monitor.isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Symbol symbol = key.getSymbol();
|
||||
Namespace parentNs = symbol.getParentNamespace();
|
||||
if (parentNs == globalNamespace) {
|
||||
// no need to search for the class in the tree; the class only lives at the top
|
||||
return findNode(this, key, loadChildren, monitor);
|
||||
}
|
||||
|
||||
// set getAllClassNodes() for a description of the map
|
||||
Map<GTreeNode, List<Namespace>> classNodes =
|
||||
getAllClassNodes(symbol, parentNs, loadChildren, monitor);
|
||||
if (classNodes.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// since the symbol lives in all of these paths, just pick one in a consistent way
|
||||
List<GTreeNode> keys = new ArrayList<>(classNodes.keySet());
|
||||
Collections.sort(keys);
|
||||
GTreeNode classNode = keys.get(0);
|
||||
List<Namespace> parentPath = classNodes.get(classNode);
|
||||
GTreeNode symbolParent =
|
||||
getNamespaceNode(classNode, parentPath, loadChildren, monitor);
|
||||
return findNode(symbolParent, key, loadChildren, monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void symbolRemoved(Symbol symbol, Namespace oldNamespace, TaskMonitor monitor) {
|
||||
|
||||
if (!isLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!supportsSymbol(symbol)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SymbolNode key = SymbolNode.createKeyNode(symbol, symbol.getName(), program);
|
||||
Namespace parentNs = symbol.getParentNamespace();
|
||||
if (parentNs == globalNamespace) {
|
||||
// no need to search for the class in the tree; the class only lives at the top
|
||||
GTreeNode symbolNode = findNode(this, key, false, monitor);
|
||||
if (symbolNode != null) {
|
||||
removeNode(symbolNode);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// set getAllClassNodes() for a description of the map
|
||||
Map<GTreeNode, List<Namespace>> classNodes =
|
||||
getAllClassNodes(symbol, oldNamespace, monitor);
|
||||
removeSymbol(key, classNodes, monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void symbolRemoved(Symbol symbol, String oldName, TaskMonitor monitor) {
|
||||
if (!isLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!supportsSymbol(symbol)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SymbolNode key = SymbolNode.createKeyNode(symbol, oldName, program);
|
||||
Namespace parentNs = symbol.getParentNamespace();
|
||||
if (parentNs == globalNamespace) {
|
||||
// no need to search for the class in the tree; the class only lives at the top
|
||||
GTreeNode symbolNode = findNode(this, key, false, monitor);
|
||||
if (symbolNode != null) {
|
||||
removeNode(symbolNode);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// set getAllClassNodes() for a description of the map
|
||||
Map<GTreeNode, List<Namespace>> classNodes = getAllClassNodes(symbol, parentNs, monitor);
|
||||
removeSymbol(key, classNodes, monitor);
|
||||
}
|
||||
|
||||
private void removeSymbol(SymbolNode key, Map<GTreeNode, List<Namespace>> classNodes,
|
||||
TaskMonitor monitor) {
|
||||
|
||||
Set<Entry<GTreeNode, List<Namespace>>> entries = classNodes.entrySet();
|
||||
for (Entry<GTreeNode, List<Namespace>> entry : entries) {
|
||||
|
||||
if (monitor.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// start with the the top-level class node and walk the namespace path to find the
|
||||
// parent for the given symbol
|
||||
GTreeNode classNode = entry.getKey();
|
||||
List<Namespace> parentPath = entry.getValue();
|
||||
GTreeNode symbolParent =
|
||||
getNamespaceNode(classNode, parentPath, false, monitor);
|
||||
GTreeNode symbolNode = findNode(symbolParent, key, false, monitor);
|
||||
if (symbolParent != null) {
|
||||
symbolParent.removeNode(symbolNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SymbolNode symbolAdded(Symbol symbol, TaskMonitor monitor) {
|
||||
if (!isLoaded()) {
|
||||
return null;
|
||||
}
|
||||
|
@ -66,18 +195,110 @@ public class ClassCategoryNode extends SymbolCategoryNode {
|
|||
}
|
||||
|
||||
if (symbol.getSymbolType() == symbolCategory.getSymbolType()) {
|
||||
return doAddSymbol(symbol, this); // add new Class symbol
|
||||
doAddSymbol(symbol, this); // add new flat Class symbol
|
||||
}
|
||||
|
||||
// see if the symbol is in a class namespace
|
||||
Namespace parentNamespace = symbol.getParentNamespace();
|
||||
Symbol namespaceSymbol = parentNamespace.getSymbol();
|
||||
SymbolNode key = SymbolNode.createNode(namespaceSymbol, program);
|
||||
GTreeNode parentNode = findSymbolTreeNode(key, false, TaskMonitor.DUMMY);
|
||||
if (parentNode == null) {
|
||||
return null;
|
||||
// set getAllClassNodes() for a description of the map
|
||||
SymbolNode lastNode = null;
|
||||
Namespace parentNs = symbol.getParentNamespace();
|
||||
Map<GTreeNode, List<Namespace>> classNodes = getAllClassNodes(symbol, parentNs, monitor);
|
||||
Set<Entry<GTreeNode, List<Namespace>>> entries = classNodes.entrySet();
|
||||
for (Entry<GTreeNode, List<Namespace>> entry : entries) {
|
||||
|
||||
// start with the the top-level class node and walk the namespace path to find the
|
||||
// parent for the given symbol
|
||||
GTreeNode classNode = entry.getKey();
|
||||
List<Namespace> parentPath = entry.getValue();
|
||||
GTreeNode symbolParent =
|
||||
getNamespaceNode(classNode, parentPath, false, monitor);
|
||||
if (symbolParent != null) {
|
||||
lastNode = doAddSymbol(symbol, symbolParent);
|
||||
}
|
||||
return doAddSymbol(symbol, parentNode);
|
||||
|
||||
}
|
||||
|
||||
return lastNode;
|
||||
}
|
||||
|
||||
/*
|
||||
Uses the namespace path of the given symbol to create a mapping from this Classes category
|
||||
node's top-level child classes to the path from that child node to the given symbol node.
|
||||
|
||||
This mapping allows us to find the symbol in multiple tree paths, such as in this example:
|
||||
|
||||
Classes
|
||||
Class1
|
||||
Label1
|
||||
BarNs
|
||||
Class2
|
||||
Label2
|
||||
Class2
|
||||
Label2
|
||||
|
||||
|
||||
In this tree, the Label2 symbol is in the tree twice. The mapping created by this method
|
||||
will have have as keys both Class1 and Class2. Class1 will be mapped to Class1/BarNs/Class2
|
||||
and Class2 will be mapped to Class2 (since it only has one namespace element).
|
||||
|
||||
This code is needed because this Classes category node will duplicate class nodes. It puts
|
||||
each class at the top-level (as a flattened view) and then also includes each class under
|
||||
any other parent class nodes.
|
||||
|
||||
*/
|
||||
private Map<GTreeNode, List<Namespace>> getAllClassNodes(Symbol symbol, Namespace parentNs,
|
||||
TaskMonitor monitor) {
|
||||
return getAllClassNodes(symbol, parentNs, false, monitor);
|
||||
}
|
||||
|
||||
private Map<GTreeNode, List<Namespace>> getAllClassNodes(Symbol symbol, Namespace parentNs,
|
||||
boolean loadChildren, TaskMonitor monitor) {
|
||||
List<Namespace> parents = NamespaceUtils.getNamespaceParts(parentNs);
|
||||
Map<GTreeNode, List<Namespace>> classByPath = new HashMap<>();
|
||||
findAllClassNodes(this, parents, classByPath, loadChildren, monitor);
|
||||
return classByPath;
|
||||
}
|
||||
|
||||
private void findAllClassNodes(GTreeNode searchNode, List<Namespace> namespaces,
|
||||
Map<GTreeNode, List<Namespace>> results, boolean loadChildren, TaskMonitor monitor) {
|
||||
|
||||
if ((!searchNode.isLoaded() && !loadChildren) || monitor.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (namespaces.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Namespace namespace = getNextClass(namespaces);
|
||||
if (namespace == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Symbol nsSymbol = namespace.getSymbol();
|
||||
SymbolNode key = SymbolNode.createKeyNode(nsSymbol, nsSymbol.getName(), program);
|
||||
GTreeNode namespaceNode = findNode(searchNode, key, loadChildren, monitor);
|
||||
if (namespaceNode == null) {
|
||||
return; // we hit the last namespace
|
||||
}
|
||||
|
||||
if (namespaceNode instanceof ClassSymbolNode) {
|
||||
List<Namespace> currentPath = new ArrayList<>(namespaces);
|
||||
currentPath.add(0, namespace);
|
||||
results.put(namespaceNode, currentPath);
|
||||
}
|
||||
|
||||
// move to the next namespace
|
||||
findAllClassNodes(searchNode, namespaces, results, loadChildren, monitor);
|
||||
}
|
||||
|
||||
private GhidraClass getNextClass(List<Namespace> namespaces) {
|
||||
while (namespaces.size() > 0) {
|
||||
Namespace ns = namespaces.remove(0);
|
||||
if (ns instanceof GhidraClass) {
|
||||
return (GhidraClass) ns;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -99,23 +320,4 @@ public class ClassCategoryNode extends SymbolCategoryNode {
|
|||
Collections.sort(list, getChildrenComparator());
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportsSymbol(Symbol symbol) {
|
||||
SymbolType symbolType = symbol.getSymbolType();
|
||||
if (symbolType == symbolCategory.getSymbolType()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// must be in a class at some level
|
||||
Namespace parentNamespace = symbol.getParentNamespace();
|
||||
while (parentNamespace != null && parentNamespace != globalNamespace) {
|
||||
if (parentNamespace instanceof GhidraClass) {
|
||||
return true;
|
||||
}
|
||||
parentNamespace = parentNamespace.getParentNamespace();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,8 +29,8 @@ import ghidra.program.model.listing.Program;
|
|||
*/
|
||||
public class ConfigurableSymbolTreeRootNode extends SymbolTreeRootNode {
|
||||
|
||||
public ConfigurableSymbolTreeRootNode(Program program) {
|
||||
super(program);
|
||||
public ConfigurableSymbolTreeRootNode(Program program, int groupThreshold) {
|
||||
super(program, groupThreshold);
|
||||
}
|
||||
|
||||
public void transferSettings(ConfigurableSymbolTreeRootNode otherRoot) {
|
||||
|
|
|
@ -76,6 +76,12 @@ class ExportsCategoryNode extends SymbolCategoryNode {
|
|||
if (!symbol.isPrimary()) {
|
||||
return false;
|
||||
}
|
||||
return symbol.isExternalEntryPoint() || symbol.getParentSymbol().isExternalEntryPoint();
|
||||
|
||||
if (symbol.isExternalEntryPoint()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Symbol parent = symbol.getParentSymbol();
|
||||
return parent != null && parent.isExternalEntryPoint();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@ class FunctionCategoryNode extends SymbolCategoryNode {
|
|||
}
|
||||
|
||||
@Override
|
||||
public SymbolNode symbolAdded(Symbol symbol) {
|
||||
public SymbolNode symbolAdded(Symbol symbol, TaskMonitor monitor) {
|
||||
if (!isLoaded()) {
|
||||
return null;
|
||||
}
|
||||
|
@ -111,15 +111,20 @@ class FunctionCategoryNode extends SymbolCategoryNode {
|
|||
return null;
|
||||
}
|
||||
|
||||
// only allow functions in the global namespace; others live in the Namespaces category
|
||||
if (!isGlobalFunction(symbol)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// variables and parameters will be beneath function nodes, and our parent method
|
||||
// will find them
|
||||
if (isVariableParameterOrCodeSymbol(symbol)) {
|
||||
return super.symbolAdded(symbol);
|
||||
return super.symbolAdded(symbol, monitor);
|
||||
}
|
||||
|
||||
// this namespace will be beneath function nodes, and our parent method will find them
|
||||
if (isChildNamespaceOfFunction(symbol)) {
|
||||
return super.symbolAdded(symbol);
|
||||
return super.symbolAdded(symbol, monitor);
|
||||
}
|
||||
|
||||
// ...otherwise, we have a function and we need to add it as a child of our parent node
|
||||
|
@ -128,6 +133,15 @@ class FunctionCategoryNode extends SymbolCategoryNode {
|
|||
return newNode;
|
||||
}
|
||||
|
||||
private boolean isGlobalFunction(Symbol symbol) {
|
||||
SymbolType type = symbol.getSymbolType();
|
||||
if (type != SymbolType.FUNCTION) {
|
||||
return false;
|
||||
}
|
||||
Namespace namespace = symbol.getParentNamespace();
|
||||
return namespace == globalNamespace;
|
||||
}
|
||||
|
||||
private boolean isChildNamespaceOfFunction(Symbol symbol) {
|
||||
if (symbol instanceof Function) {
|
||||
return false;
|
||||
|
@ -151,7 +165,15 @@ class FunctionCategoryNode extends SymbolCategoryNode {
|
|||
|
||||
@Override
|
||||
protected boolean supportsSymbol(Symbol symbol) {
|
||||
if (super.supportsSymbol(symbol)) {
|
||||
if (symbol.isExternal()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Note: we say that we support function symbol types, even though we do not include
|
||||
// functions that live in non-global namespaces. This allows functions that are moved to
|
||||
// be removed from this category.
|
||||
SymbolType type = symbol.getSymbolType();
|
||||
if (type == SymbolType.FUNCTION) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -61,6 +61,15 @@ public class LabelCategoryNode extends SymbolCategoryNode {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supportsSymbol(Symbol symbol) {
|
||||
if (!symbol.isGlobal() || symbol.isExternal()) {
|
||||
return false;
|
||||
}
|
||||
SymbolType symbolType = symbol.getSymbolType();
|
||||
return symbolType == symbolCategory.getSymbolType();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<GTreeNode> getSymbols(SymbolType type, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
|
@ -85,7 +94,7 @@ public class LabelCategoryNode extends SymbolCategoryNode {
|
|||
}
|
||||
|
||||
@Override
|
||||
public SymbolNode symbolAdded(Symbol symbol) {
|
||||
public SymbolNode symbolAdded(Symbol symbol, TaskMonitor monitor) {
|
||||
if (!isLoaded()) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -16,14 +16,17 @@
|
|||
package ghidra.app.plugin.core.symboltree.nodes;
|
||||
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.app.plugin.core.symboltree.SymbolCategory;
|
||||
import ghidra.app.util.NamespaceUtils;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.symbol.Namespace;
|
||||
import ghidra.program.model.symbol.Symbol;
|
||||
import ghidra.program.model.symbol.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class NamespaceCategoryNode extends SymbolCategoryNode {
|
||||
|
||||
|
@ -43,7 +46,13 @@ public class NamespaceCategoryNode extends SymbolCategoryNode {
|
|||
|
||||
@Override
|
||||
protected boolean supportsSymbol(Symbol symbol) {
|
||||
if (super.supportsSymbol(symbol)) {
|
||||
|
||||
if (symbol.isExternal()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SymbolType symbolType = symbol.getSymbolType();
|
||||
if (symbolType == SymbolType.NAMESPACE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -52,6 +61,78 @@ public class NamespaceCategoryNode extends SymbolCategoryNode {
|
|||
return parentNamespace != null && parentNamespace != globalNamespace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void symbolRemoved(Symbol symbol, Namespace oldNamespace, TaskMonitor monitor) {
|
||||
|
||||
if (!isLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!supportsSymbol(symbol)) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<Namespace> parents = NamespaceUtils.getNamespaceParts(oldNamespace);
|
||||
GTreeNode namespaceNode = getNamespaceNode(this, parents, false, monitor);
|
||||
if (namespaceNode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SymbolNode key = SymbolNode.createKeyNode(symbol, symbol.getName(), program);
|
||||
GTreeNode foundNode = findNode(namespaceNode, key, false, monitor);
|
||||
if (foundNode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
GTreeNode foundParent = foundNode.getParent();
|
||||
foundParent.removeNode(foundNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SymbolNode symbolAdded(Symbol symbol, TaskMonitor monitor) {
|
||||
|
||||
if (!isLoaded()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!supportsSymbol(symbol)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
GTreeNode parentNode = this;
|
||||
if (symbol.isGlobal()) {
|
||||
return doAddSymbol(symbol, parentNode);
|
||||
}
|
||||
|
||||
Namespace parentNamespace = symbol.getParentNamespace();
|
||||
List<Namespace> parents = NamespaceUtils.getNamespaceParts(parentNamespace);
|
||||
GTreeNode namespaceNode = getNamespaceNode(this, parents, false, monitor);
|
||||
if (namespaceNode == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return doAddSymbol(symbol, namespaceNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren, TaskMonitor monitor) {
|
||||
|
||||
if ((!isLoaded() && !loadChildren) || monitor.isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Symbol symbol = key.getSymbol();
|
||||
Namespace parent = symbol.getParentNamespace();
|
||||
List<Namespace> parents = NamespaceUtils.getNamespaceParts(parent);
|
||||
GTreeNode namespaceNode = getNamespaceNode(this, parents, loadChildren, monitor);
|
||||
if (namespaceNode != null) {
|
||||
return findNode(namespaceNode, key, loadChildren, monitor);
|
||||
}
|
||||
|
||||
// look in the namespace node for the given symbol
|
||||
return findNode(this, key, loadChildren, monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsDataFlavors(DataFlavor[] dataFlavors) {
|
||||
for (DataFlavor flavor : dataFlavors) {
|
||||
|
|
|
@ -243,7 +243,13 @@ public class OrganizationNode extends SymbolTreeNode {
|
|||
}
|
||||
|
||||
private void checkForTooManyNodes() {
|
||||
if (getChildCount() > SymbolCategoryNode.MAX_NODES_BEFORE_CLOSING) {
|
||||
|
||||
SymbolTreeRootNode root = (SymbolTreeRootNode) getRoot();
|
||||
int reOrgLimit = root.getReorganizeLimit();
|
||||
if (getChildCount() < reOrgLimit) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have too many nodes, find the root category node and close it
|
||||
GTreeNode parent = getParent();
|
||||
while (parent != null) {
|
||||
|
@ -257,7 +263,6 @@ public class OrganizationNode extends SymbolTreeNode {
|
|||
parent = parent.getParent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren,
|
||||
|
|
|
@ -29,8 +29,6 @@ import ghidra.util.exception.CancelledException;
|
|||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public abstract class SymbolCategoryNode extends SymbolTreeNode {
|
||||
public static final int MAX_NODES_BEFORE_ORGANIZING = 100;
|
||||
public static final int MAX_NODES_BEFORE_CLOSING = 200;
|
||||
|
||||
protected SymbolCategory symbolCategory;
|
||||
protected SymbolTable symbolTable;
|
||||
|
@ -76,7 +74,9 @@ public abstract class SymbolCategoryNode extends SymbolTreeNode {
|
|||
SymbolType symbolType = symbolCategory.getSymbolType();
|
||||
List<GTreeNode> list = getSymbols(symbolType, monitor);
|
||||
monitor.checkCancelled();
|
||||
return OrganizationNode.organize(list, MAX_NODES_BEFORE_ORGANIZING, monitor);
|
||||
SymbolTreeRootNode root = (SymbolTreeRootNode) getRoot();
|
||||
int groupThreshold = root.getNodeGroupThreshold();
|
||||
return OrganizationNode.organize(list, groupThreshold, monitor);
|
||||
}
|
||||
|
||||
public Program getProgram() {
|
||||
|
@ -173,7 +173,9 @@ public abstract class SymbolCategoryNode extends SymbolTreeNode {
|
|||
dataFlavor == ClassSymbolNode.LOCAL_DATA_FLAVOR;
|
||||
}
|
||||
|
||||
public SymbolNode symbolAdded(Symbol symbol) {
|
||||
protected abstract boolean supportsSymbol(Symbol symbol);
|
||||
|
||||
public SymbolNode symbolAdded(Symbol symbol, TaskMonitor monitor) {
|
||||
|
||||
if (!isLoaded()) {
|
||||
return null;
|
||||
|
@ -191,7 +193,7 @@ public abstract class SymbolCategoryNode extends SymbolTreeNode {
|
|||
Namespace parentNamespace = symbol.getParentNamespace();
|
||||
Symbol namespaceSymbol = parentNamespace.getSymbol();
|
||||
SymbolNode key = SymbolNode.createNode(namespaceSymbol, program);
|
||||
parentNode = findSymbolTreeNode(key, false, TaskMonitor.DUMMY);
|
||||
parentNode = findSymbolTreeNode(key, false, monitor);
|
||||
if (parentNode == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -230,11 +232,17 @@ public abstract class SymbolCategoryNode extends SymbolTreeNode {
|
|||
}
|
||||
|
||||
parentNode.addNode(index, newNode);
|
||||
if (parentNode.isLoaded() && parentNode.getChildCount() > MAX_NODES_BEFORE_CLOSING) {
|
||||
if (!parentNode.isLoaded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SymbolTreeRootNode root = (SymbolTreeRootNode) getRoot();
|
||||
int reOrgLimit = root.getReorganizeLimit();
|
||||
if (parentNode.getChildCount() > reOrgLimit) {
|
||||
GTree tree = parentNode.getTree();
|
||||
// tree needs to be reorganized, close this category node to clear its children
|
||||
// and force a reorganization next time it is opened
|
||||
// also need to clear the selection so that it doesn't re-open the category
|
||||
// The tree needs to be reorganized, close this category node to clear its children
|
||||
// and force a reorganization next time it is opened. Also need to clear the selection
|
||||
// so that it doesn't re-open the category.
|
||||
tree.clearSelectionPaths();
|
||||
tree.runTask(new GTreeCollapseAllTask(tree, parentNode));
|
||||
}
|
||||
|
@ -249,6 +257,10 @@ public abstract class SymbolCategoryNode extends SymbolTreeNode {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!supportsSymbol(symbol)) {
|
||||
return;
|
||||
}
|
||||
|
||||
SymbolNode key = SymbolNode.createKeyNode(symbol, oldName, program);
|
||||
GTreeNode foundNode = findSymbolTreeNode(key, false, monitor);
|
||||
if (foundNode == null) {
|
||||
|
@ -259,12 +271,45 @@ public abstract class SymbolCategoryNode extends SymbolTreeNode {
|
|||
foundParent.removeNode(foundNode);
|
||||
}
|
||||
|
||||
protected boolean supportsSymbol(Symbol symbol) {
|
||||
if (!symbol.isGlobal() || symbol.isExternal()) {
|
||||
return false;
|
||||
public void symbolRemoved(Symbol symbol, Namespace oldNamespace, TaskMonitor monitor) {
|
||||
// Most categories will treat a symbol moved as a remove; symbolAdded() will get called
|
||||
// after this to restore the symbol. Subclasses that depend on scope will override this
|
||||
// method.
|
||||
symbolRemoved(symbol, monitor);
|
||||
}
|
||||
SymbolType symbolType = symbol.getSymbolType();
|
||||
return symbolType == symbolCategory.getSymbolType();
|
||||
|
||||
/**
|
||||
* Returns the last Namespace tree node in the given path of namespaces. Each Namespace in the
|
||||
* list from 0 to n will be used to find the last tree node, starting at the given parent
|
||||
* node.
|
||||
*
|
||||
* @param parentNode the node at which to start the search
|
||||
* @param namespaces the list of namespaces to traverse.
|
||||
* @param loadChildren true to load children if they have not been loaded
|
||||
* @param monitor the task monitor
|
||||
* @return the namespace node or null if it is not open in the tree
|
||||
*/
|
||||
protected GTreeNode getNamespaceNode(GTreeNode parentNode, List<Namespace> namespaces,
|
||||
boolean loadChildren, TaskMonitor monitor) {
|
||||
|
||||
if (!loadChildren && !parentNode.isLoaded() || monitor.isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (namespaces.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Namespace namespace = namespaces.remove(0);
|
||||
Symbol nsSymbol = namespace.getSymbol();
|
||||
SymbolNode key = SymbolNode.createKeyNode(nsSymbol, nsSymbol.getName(), program);
|
||||
GTreeNode namespaceNode = findNode(parentNode, key, loadChildren, monitor);
|
||||
if (namespaceNode == null || namespaces.isEmpty()) {
|
||||
return namespaceNode; // we hit the last namespace
|
||||
}
|
||||
|
||||
// move to the next namespace
|
||||
return getNamespaceNode(namespaceNode, namespaces, loadChildren, monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -38,6 +38,8 @@ public class SymbolNode extends SymbolTreeNode {
|
|||
protected final Program program;
|
||||
protected final Symbol symbol;
|
||||
|
||||
private String cachedName;
|
||||
|
||||
private boolean isCut;
|
||||
|
||||
SymbolNode(Program program, Symbol symbol) {
|
||||
|
@ -113,8 +115,11 @@ public class SymbolNode extends SymbolTreeNode {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
if (cachedName == null) {
|
||||
String baseName = symbol.getName();
|
||||
return getNameFromBaseName(baseName);
|
||||
cachedName = getNameFromBaseName(baseName);
|
||||
}
|
||||
return cachedName;
|
||||
}
|
||||
|
||||
protected String getNameFromBaseName(String baseName) {
|
||||
|
|
|
@ -176,18 +176,23 @@ public abstract class SymbolTreeNode extends GTreeSlowLoadingNode {
|
|||
public GTreeNode findSymbolTreeNode(SymbolNode key, boolean loadChildren, TaskMonitor monitor) {
|
||||
|
||||
// if we don't have to loadChildren and we are not loaded get out.
|
||||
if (!loadChildren && !isLoaded()) {
|
||||
if ((!isLoaded() && !loadChildren) || monitor.isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// see if the given node is the node we want
|
||||
Symbol searchSymbol = key.getSymbol();
|
||||
if (getSymbol() == searchSymbol) {
|
||||
return this;
|
||||
}
|
||||
|
||||
List<GTreeNode> children = getChildren();
|
||||
int index = Collections.binarySearch(children, key, getChildrenComparator());
|
||||
if (index >= 0) {
|
||||
GTreeNode node = children.get(index);
|
||||
SymbolTreeNode symbolNode = (SymbolTreeNode) node;
|
||||
Symbol searchSymbol = key.getSymbol();
|
||||
if (symbolNode.getSymbol() == searchSymbol) {
|
||||
return node;
|
||||
return symbolNode;
|
||||
}
|
||||
|
||||
// At this point we know that the given child is not itself a symbol node, but it
|
||||
|
@ -199,7 +204,9 @@ public abstract class SymbolTreeNode extends GTreeSlowLoadingNode {
|
|||
return node;
|
||||
}
|
||||
|
||||
// Brute-force lookup in each child. This will not typically be called.
|
||||
// Brute-force lookup in each child. This will not typically be called. Category nodes
|
||||
// that support large numbers of children have overridden this method to perform smarter
|
||||
// searching.
|
||||
for (GTreeNode childNode : children) {
|
||||
if (monitor.isCancelled()) {
|
||||
return null;
|
||||
|
@ -217,4 +224,48 @@ public abstract class SymbolTreeNode extends GTreeSlowLoadingNode {
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for the given node 'key' inside of the given parent. This method performs an
|
||||
* efficient search and does not recurse below the given node.
|
||||
*
|
||||
* @param parent the node whose children will be searched
|
||||
* @param key the token node to search for
|
||||
* @param loadChildren true to load children; false signals to search only if already loaded
|
||||
* @param monitor the monitor
|
||||
* @return the node or null
|
||||
*/
|
||||
protected GTreeNode findNode(GTreeNode parent, SymbolNode key, boolean loadChildren,
|
||||
TaskMonitor monitor) {
|
||||
|
||||
if ((!isLoaded() && !loadChildren) || monitor.isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// see if the given node is the node we want
|
||||
Symbol searchSymbol = key.getSymbol();
|
||||
if (parent instanceof SymbolTreeNode symbolNode) {
|
||||
if (symbolNode.getSymbol() == searchSymbol) {
|
||||
return symbolNode;
|
||||
}
|
||||
}
|
||||
|
||||
Comparator<GTreeNode> comparator = ((SymbolTreeNode) parent).getChildrenComparator();
|
||||
List<GTreeNode> children = parent.getChildren();
|
||||
int index = Collections.binarySearch(children, key, comparator);
|
||||
if (index >= 0) {
|
||||
GTreeNode node = children.get(index);
|
||||
SymbolTreeNode symbolNode = (SymbolTreeNode) node;
|
||||
|
||||
// Some parent nodes may contain OrganizationNodes, which will return as a match when
|
||||
// the symbol does not match. We expect the symbol to always match for clients of this
|
||||
// method.
|
||||
if (symbolNode.getSymbol() == searchSymbol) {
|
||||
return symbolNode;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,8 +25,7 @@ import docking.widgets.tree.GTreeNode;
|
|||
import generic.theme.GIcon;
|
||||
import ghidra.app.plugin.core.symboltree.SymbolCategory;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.symbol.Symbol;
|
||||
import ghidra.program.model.symbol.SymbolType;
|
||||
import ghidra.program.model.symbol.*;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class SymbolTreeRootNode extends GTreeNode {
|
||||
|
@ -35,8 +34,10 @@ public class SymbolTreeRootNode extends GTreeNode {
|
|||
|
||||
protected SymbolCategory symbolCategory;
|
||||
protected Program program;
|
||||
private int groupThreshold;
|
||||
|
||||
public SymbolTreeRootNode(Program program) {
|
||||
public SymbolTreeRootNode(Program program, int groupThreshold) {
|
||||
this.groupThreshold = groupThreshold;
|
||||
this.symbolCategory = SymbolCategory.ROOT_CATEGORY;
|
||||
this.program = program;
|
||||
|
||||
|
@ -48,6 +49,17 @@ public class SymbolTreeRootNode extends GTreeNode {
|
|||
}
|
||||
}
|
||||
|
||||
public int getNodeGroupThreshold() {
|
||||
return groupThreshold;
|
||||
}
|
||||
|
||||
public int getReorganizeLimit() {
|
||||
// Arbitrary number to prevent bulk updates from triggering repeated node organization.
|
||||
// The higher the value, the longer the delay between the tree collapsing nodes to signal
|
||||
// that a re-organzation is needed.
|
||||
return groupThreshold * 2;
|
||||
}
|
||||
|
||||
public Program getProgram() {
|
||||
return program;
|
||||
}
|
||||
|
@ -126,7 +138,6 @@ public class SymbolTreeRootNode extends GTreeNode {
|
|||
}
|
||||
|
||||
private GTreeNode findClassSymbol(SymbolNode key, boolean loadChildren, TaskMonitor monitor) {
|
||||
|
||||
SymbolCategoryNode category = getClassesNode();
|
||||
return category.findSymbolTreeNode(key, loadChildren, monitor);
|
||||
}
|
||||
|
@ -238,12 +249,12 @@ public class SymbolTreeRootNode extends GTreeNode {
|
|||
return null; // must be filtered out
|
||||
}
|
||||
|
||||
public SymbolNode symbolAdded(Symbol symbol) {
|
||||
public SymbolNode symbolAdded(Symbol symbol, TaskMonitor monitor) {
|
||||
SymbolNode returnNode = null;
|
||||
List<GTreeNode> allChildren = getChildren();
|
||||
for (GTreeNode gNode : allChildren) {
|
||||
SymbolCategoryNode symbolNode = (SymbolCategoryNode) gNode;
|
||||
SymbolNode newNode = symbolNode.symbolAdded(symbol);
|
||||
SymbolNode newNode = symbolNode.symbolAdded(symbol, monitor);
|
||||
if (newNode != null) {
|
||||
returnNode = newNode; // doesn't matter which one we return
|
||||
}
|
||||
|
@ -261,6 +272,16 @@ public class SymbolTreeRootNode extends GTreeNode {
|
|||
}
|
||||
}
|
||||
|
||||
public void symbolRemoved(Symbol symbol, Namespace oldNamespace, TaskMonitor monitor) {
|
||||
|
||||
// we have to loop--the symbol may exist in more than one category
|
||||
List<GTreeNode> allChildren = getChildren();
|
||||
for (GTreeNode gNode : allChildren) {
|
||||
SymbolCategoryNode symbolNode = (SymbolCategoryNode) gNode;
|
||||
symbolNode.symbolRemoved(symbol, oldNamespace, monitor);
|
||||
}
|
||||
}
|
||||
|
||||
public void rebuild() {
|
||||
setChildren(null);
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ import ghidra.program.model.listing.Program;
|
|||
import ghidra.program.model.symbol.*;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.test.TestEnv;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Tests for the symbol tree plugin.
|
||||
|
@ -118,7 +119,8 @@ public class SymbolTreePlugin1Test extends AbstractGhidraHeadedIntegrationTest {
|
|||
assertTrue(functionsNode.isLoaded());
|
||||
|
||||
// add lots of nodes to cause functionsNode to close
|
||||
addFunctions(SymbolCategoryNode.MAX_NODES_BEFORE_CLOSING);
|
||||
int reorganizeLimit = ((SymbolTreeRootNode) rootNode).getReorganizeLimit();
|
||||
addFunctions(reorganizeLimit);
|
||||
waitForTree(tree);
|
||||
|
||||
assertFalse(functionsNode.isLoaded());
|
||||
|
@ -325,6 +327,7 @@ public class SymbolTreePlugin1Test extends AbstractGhidraHeadedIntegrationTest {
|
|||
|
||||
flushAndWaitForTree();
|
||||
|
||||
// Functions node
|
||||
GTreeNode fNode = rootNode.getChild(2);
|
||||
util.expandNode(fNode);
|
||||
|
||||
|
@ -341,10 +344,13 @@ public class SymbolTreePlugin1Test extends AbstractGhidraHeadedIntegrationTest {
|
|||
assertTrue(cutAction.isEnabledForContext(util.getSymbolTreeContext()));
|
||||
performAction(cutAction, util.getSymbolTreeContext(), true);
|
||||
|
||||
// NewNamespace node
|
||||
GTreeNode gNode = namespaceNode.getChild(0);
|
||||
util.selectNode(gNode);
|
||||
assertTrue(pasteAction.isEnabledForContext(util.getSymbolTreeContext()));
|
||||
|
||||
// doStuff function node
|
||||
waitForSwing();
|
||||
GTreeNode dNode = fNode.getChild(0);
|
||||
util.selectNode(dNode);
|
||||
assertFalse(pasteAction.isEnabledForContext(util.getSymbolTreeContext()));
|
||||
|
@ -775,7 +781,7 @@ public class SymbolTreePlugin1Test extends AbstractGhidraHeadedIntegrationTest {
|
|||
Symbol symbol = fNode.getSymbol();
|
||||
|
||||
// symbolAdded() was throwing an exception before the fix
|
||||
symbolRootNode.symbolAdded(symbol);
|
||||
symbolRootNode.symbolAdded(symbol, TaskMonitor.DUMMY);
|
||||
}
|
||||
|
||||
private void addFunctions(int count) throws Exception {
|
||||
|
|
|
@ -30,6 +30,8 @@ import docking.action.DockingActionIf;
|
|||
import docking.action.ToggleDockingAction;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import generic.test.AbstractGenericTest;
|
||||
import ghidra.app.cmd.label.CreateNamespacesCmd;
|
||||
import ghidra.app.cmd.label.RenameLabelCmd;
|
||||
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||
import ghidra.app.plugin.core.marker.MarkerManagerPlugin;
|
||||
import ghidra.app.plugin.core.programtree.ProgramTreePlugin;
|
||||
|
@ -38,8 +40,8 @@ import ghidra.app.plugin.core.symboltree.nodes.SymbolNode;
|
|||
import ghidra.app.util.viewer.field.*;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.address.AddressFactory;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.symbol.*;
|
||||
import ghidra.test.AbstractGhidraHeadedIntegrationTest;
|
||||
import ghidra.test.TestEnv;
|
||||
|
@ -313,6 +315,217 @@ public class SymbolTreePlugin2Test extends AbstractGhidraHeadedIntegrationTest {
|
|||
waitForCondition(tree::isEditing);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassCategoryDuplicates_NestedClass_RenameLabel() throws Exception {
|
||||
|
||||
/*
|
||||
|
||||
The Classes folder flattens classes so every class appears at the top level. Because
|
||||
users can expand classes, top level classes may also appear nested under other classes.
|
||||
|
||||
Classes
|
||||
Class1
|
||||
Label1
|
||||
BarNs
|
||||
Class2
|
||||
Label2
|
||||
Class2
|
||||
Label2
|
||||
|
||||
Namespaces
|
||||
FooNs
|
||||
Class1
|
||||
Label1
|
||||
BarNs
|
||||
Class2
|
||||
Label2
|
||||
*/
|
||||
|
||||
Namespace fooNs = createNamespace("FooNs");
|
||||
GhidraClass class1 = createClass(fooNs, "Class1");
|
||||
Namespace barNs = createNamespace(class1, "BarNs");
|
||||
createLabel(class1, "Label1", "0x1001100");
|
||||
GhidraClass class2 = createClass(barNs, "Class2");
|
||||
Symbol lable2 = createLabel(class2, "Label2", "0x1001104");
|
||||
|
||||
expandClasses();
|
||||
expandNamesapces();
|
||||
|
||||
// verify all leaf nodes
|
||||
//@formatter:off
|
||||
assertNamespaceNodes(
|
||||
"FooNs::Class1::Label1",
|
||||
"FooNs::Class1::BarNs::Class2::Label2"
|
||||
);
|
||||
//@formatter:on
|
||||
|
||||
//@formatter:off
|
||||
assertClassNodes(
|
||||
"Class1::Label1",
|
||||
"Class1::BarNs::Class2::Label2",
|
||||
"Class2::Label2"
|
||||
);
|
||||
//@formatter:on
|
||||
|
||||
renameSymbol(lable2, "Label2.renamed");
|
||||
|
||||
//@formatter:off
|
||||
assertNamespaceNodes(
|
||||
"FooNs::Class1::Label1",
|
||||
"FooNs::Class1::BarNs::Class2::Label2.renamed"
|
||||
);
|
||||
//@formatter:on
|
||||
|
||||
//@formatter:off
|
||||
assertClassNodes(
|
||||
"Class1::Label1",
|
||||
"Class1::BarNs::Class2::Label2.renamed",
|
||||
"Class2::Label2.renamed"
|
||||
);
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassCategoryDuplicates_NestedClass_ChangeNamespace() throws Exception {
|
||||
|
||||
/*
|
||||
|
||||
The Classes folder flattens classes so every class appears at the top level. Because
|
||||
users can expand classes, top level classes may also appear nested under other classes.
|
||||
|
||||
Classes
|
||||
Class1
|
||||
Label1
|
||||
BarNs
|
||||
Class2
|
||||
Label2
|
||||
Class2
|
||||
Label2
|
||||
|
||||
Namespaces
|
||||
FooNs
|
||||
Class1
|
||||
Label1
|
||||
BarNs
|
||||
Class2
|
||||
Label2
|
||||
*/
|
||||
|
||||
Namespace fooNs = createNamespace("FooNs");
|
||||
GhidraClass class1 = createClass(fooNs, "Class1");
|
||||
Namespace barNs = createNamespace(class1, "BarNs");
|
||||
Symbol label1 = createLabel(class1, "Label1", "0x1001100");
|
||||
GhidraClass class2 = createClass(barNs, "Class2");
|
||||
createLabel(class2, "Label2", "0x1001104");
|
||||
|
||||
expandClasses();
|
||||
expandNamesapces();
|
||||
|
||||
// verify all leaf nodes
|
||||
//@formatter:off
|
||||
assertNamespaceNodes(
|
||||
"FooNs::Class1::Label1",
|
||||
"FooNs::Class1::BarNs::Class2::Label2"
|
||||
);
|
||||
//@formatter:on
|
||||
|
||||
//@formatter:off
|
||||
assertClassNodes(
|
||||
"Class1::Label1",
|
||||
"Class1::BarNs::Class2::Label2",
|
||||
"Class2::Label2"
|
||||
);
|
||||
//@formatter:on
|
||||
|
||||
moveLabel(label1, barNs);
|
||||
|
||||
//@formatter:off
|
||||
assertNamespaceNodes(
|
||||
"FooNs::Class1::BarNs::Label1",
|
||||
"FooNs::Class1::BarNs::Class2::Label2"
|
||||
);
|
||||
//@formatter:on
|
||||
|
||||
//@formatter:off
|
||||
assertClassNodes(
|
||||
"Class1",
|
||||
"Class1::BarNs::Label1",
|
||||
"Class1::BarNs::Class2::Label2",
|
||||
"Class2::Label2"
|
||||
);
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassCategoryDuplicates_NestedClass_RenameNamespace() throws Exception {
|
||||
|
||||
/*
|
||||
|
||||
The Classes folder flattens classes so every class appears at the top level. Because
|
||||
users can expand classes, top level classes may also appear nested under other classes.
|
||||
|
||||
Classes
|
||||
Class1
|
||||
Label1
|
||||
BarNs
|
||||
Class2
|
||||
Label2
|
||||
Class2
|
||||
Label2
|
||||
|
||||
Namespaces
|
||||
FooNs
|
||||
Class1
|
||||
Label1
|
||||
BarNs
|
||||
Class2
|
||||
Label2
|
||||
*/
|
||||
|
||||
Namespace fooNs = createNamespace("FooNs");
|
||||
GhidraClass class1 = createClass(fooNs, "Class1");
|
||||
Namespace barNs = createNamespace(class1, "BarNs");
|
||||
createLabel(class1, "Label1", "0x1001100");
|
||||
GhidraClass class2 = createClass(barNs, "Class2");
|
||||
createLabel(class2, "Label2", "0x1001104");
|
||||
|
||||
expandClasses();
|
||||
expandNamesapces();
|
||||
|
||||
// verify all leaf nodes
|
||||
//@formatter:off
|
||||
assertNamespaceNodes(
|
||||
"FooNs::Class1::Label1",
|
||||
"FooNs::Class1::BarNs::Class2::Label2"
|
||||
);
|
||||
//@formatter:on
|
||||
|
||||
//@formatter:off
|
||||
assertClassNodes(
|
||||
"Class1::Label1",
|
||||
"Class1::BarNs::Class2::Label2",
|
||||
"Class2::Label2"
|
||||
);
|
||||
//@formatter:on
|
||||
|
||||
renameNamespace(barNs, "BarNs.renamed");
|
||||
|
||||
//@formatter:off
|
||||
assertNamespaceNodes(
|
||||
"FooNs::Class1::Label1",
|
||||
"FooNs::Class1::BarNs.renamed::Class2::Label2"
|
||||
);
|
||||
//@formatter:on
|
||||
|
||||
//@formatter:off
|
||||
assertClassNodes(
|
||||
"Class1::Label1",
|
||||
"Class1::BarNs.renamed::Class2::Label2",
|
||||
"Class2::Label2"
|
||||
);
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionsOnGroup() throws Exception {
|
||||
// select a group node; only cut, delete, make selection should be
|
||||
|
@ -403,6 +616,100 @@ public class SymbolTreePlugin2Test extends AbstractGhidraHeadedIntegrationTest {
|
|||
assertEquals("MyAnotherLocal", (((SymbolNode) node).getSymbol()).getName());
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
// Private Methods
|
||||
//=================================================================================================
|
||||
|
||||
private void expandClasses() {
|
||||
GTreeNode node = rootNode.getChild("Classes");
|
||||
tree.expandTree(node);
|
||||
waitForTree(tree);
|
||||
}
|
||||
|
||||
private void expandNamesapces() {
|
||||
GTreeNode node = rootNode.getChild("Namespaces");
|
||||
tree.expandTree(node);
|
||||
waitForTree(tree);
|
||||
}
|
||||
|
||||
private void renameSymbol(Symbol s, String newName) {
|
||||
RenameLabelCmd cmd = new RenameLabelCmd(s, newName, SourceType.USER_DEFINED);
|
||||
if (!applyCmd(program, cmd)) {
|
||||
fail("Rename failed: " + cmd.getStatusMsg());
|
||||
}
|
||||
waitForTree(tree);
|
||||
}
|
||||
|
||||
private void moveLabel(Symbol symbol, Namespace ns) {
|
||||
tx(program, () -> {
|
||||
symbol.setNamespace(ns);
|
||||
});
|
||||
waitForTree(tree);
|
||||
}
|
||||
|
||||
private void renameNamespace(Namespace barNs, String newName) {
|
||||
Symbol symbol = barNs.getSymbol();
|
||||
renameSymbol(symbol, newName);
|
||||
}
|
||||
|
||||
private void assertNamespaceNodes(String... paths) {
|
||||
GTreeNode root = tree.getViewRoot();
|
||||
GTreeNode parent = root.getChild("Namespaces");
|
||||
assertNodes(parent, paths);
|
||||
}
|
||||
|
||||
private void assertClassNodes(String... paths) {
|
||||
GTreeNode root = tree.getViewRoot();
|
||||
GTreeNode parent = root.getChild("Classes");
|
||||
assertNodes(parent, paths);
|
||||
}
|
||||
|
||||
private void assertNodes(GTreeNode category, String... paths) {
|
||||
|
||||
for (String path : paths) {
|
||||
GTreeNode parent = category;
|
||||
String[] parts = path.split("::");
|
||||
for (String name : parts) {
|
||||
GTreeNode child = parent.getChild(name);
|
||||
String message =
|
||||
"Child '%s' not found in parent '%s' \n\tfor path '%s'\n\tCategory '%s'"
|
||||
.formatted(name, parent, path, category);
|
||||
assertNotNull(message, child);
|
||||
parent = child;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Namespace createNamespace(String name) throws Exception {
|
||||
return createNamespace(program.getGlobalNamespace(), name);
|
||||
}
|
||||
|
||||
private Namespace createNamespace(Namespace parent, String name) throws Exception {
|
||||
CreateNamespacesCmd cmd = new CreateNamespacesCmd(name, parent, SourceType.USER_DEFINED);
|
||||
applyCmd(program, cmd);
|
||||
return cmd.getNamespace();
|
||||
}
|
||||
|
||||
private GhidraClass createClass(Namespace parent, String name) throws Exception {
|
||||
GhidraClass c = tx(program, () -> {
|
||||
SymbolTable symbolTable = program.getSymbolTable();
|
||||
return symbolTable.createClass(parent, name, SourceType.USER_DEFINED);
|
||||
});
|
||||
assertNotNull(c);
|
||||
return c;
|
||||
}
|
||||
|
||||
private Symbol createLabel(Namespace parent, String name, String addr) {
|
||||
Symbol s = tx(program, () -> {
|
||||
SymbolTable symbolTable = program.getSymbolTable();
|
||||
AddressFactory af = program.getAddressFactory();
|
||||
Address address = af.getAddress(addr);
|
||||
return symbolTable.createLabel(address, name, parent, SourceType.USER_DEFINED);
|
||||
});
|
||||
assertNotNull(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
private GTreeNode getFunctionsNode() {
|
||||
return runSwing(() -> rootNode.getChild(2));
|
||||
}
|
||||
|
|
|
@ -232,26 +232,26 @@ class SymbolTreeTestUtils {
|
|||
|
||||
selectNode(parenGTreeNode);
|
||||
int childCount = parenGTreeNode.getChildCount();
|
||||
int index = parenGTreeNode.getIndexInParent();
|
||||
GTreeNode pNode = parenGTreeNode.getParent();
|
||||
int parentIndex = parenGTreeNode.getIndexInParent();
|
||||
GTreeNode grandParentNode = parenGTreeNode.getParent();
|
||||
|
||||
AbstractDockingTest.performAction(action, getSymbolTreeContext(), false);
|
||||
waitForSwing();
|
||||
waitForTree();
|
||||
program.flushEvents();
|
||||
|
||||
if (pNode != null) {
|
||||
// re-acquire parent
|
||||
parenGTreeNode = pNode.getChild(index);
|
||||
if (grandParentNode != null) {
|
||||
parenGTreeNode = grandParentNode.getChild(parentIndex);
|
||||
}
|
||||
GTreeNode node = parenGTreeNode.getChild(childCount > 0 ? childCount - 1 : 0);
|
||||
|
||||
GTreeNode newNode = parenGTreeNode.getChild(childCount > 0 ? childCount - 1 : 0);
|
||||
|
||||
waitForTree();
|
||||
|
||||
runSwing(() -> tree.stopEditing());
|
||||
waitForCondition(() -> !tree.isEditing());
|
||||
|
||||
rename(node, newName);
|
||||
rename(newNode, newName);
|
||||
return parenGTreeNode.getChild(newName);
|
||||
}
|
||||
|
||||
|
@ -274,6 +274,11 @@ class SymbolTreeTestUtils {
|
|||
waitForTree();
|
||||
}
|
||||
|
||||
void expandTree() {
|
||||
tree.expandAll();
|
||||
waitForTree();
|
||||
}
|
||||
|
||||
void expandNode(GTreeNode parenGTreeNode) throws Exception {
|
||||
tree.expandPath(parenGTreeNode);
|
||||
waitForTree();
|
||||
|
|
|
@ -2144,6 +2144,11 @@ public abstract class AbstractDockingTest extends AbstractGuiTest {
|
|||
TimeUnit.NANOSECONDS));
|
||||
*/
|
||||
doWaitForTree(gTree);
|
||||
|
||||
// some client tree operations will launch tasks that wait for the tree and then call a
|
||||
// Swing task to run at some point after that. waitForSwing() is not good enough for these,
|
||||
// since the tree may be using a timer that has not yet expired.
|
||||
waitForExpiringSwingTimers();
|
||||
}
|
||||
|
||||
private static void doWaitForTree(GTree gTree) {
|
||||
|
|
|
@ -597,7 +597,6 @@ public class GTree extends JPanel implements BusyListener {
|
|||
* @param origin the event type; use {@link EventOrigin#API_GENERATED} if unsure
|
||||
*/
|
||||
public void setSelectionPaths(List<TreePath> paths, boolean expandPaths, EventOrigin origin) {
|
||||
|
||||
if (expandPaths) {
|
||||
expandPaths(paths);
|
||||
}
|
||||
|
@ -1432,7 +1431,7 @@ public class GTree extends JPanel implements BusyListener {
|
|||
*/
|
||||
public void refilterLater() {
|
||||
if (isFilteringEnabled && filter != null) {
|
||||
filterUpdateManager.update();
|
||||
filterUpdateManager.updateLater();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
package generic.timer;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.*;
|
||||
|
||||
|
@ -32,6 +32,8 @@ import utility.function.Dummy;
|
|||
*/
|
||||
public class ExpiringSwingTimer extends GhidraSwingTimer {
|
||||
|
||||
private static Set<ExpiringSwingTimer> instances = new HashSet<>();
|
||||
|
||||
private long startMs = System.currentTimeMillis();
|
||||
private int expireMs;
|
||||
private BooleanSupplier isReady;
|
||||
|
@ -130,9 +132,16 @@ public class ExpiringSwingTimer extends GhidraSwingTimer {
|
|||
return;
|
||||
}
|
||||
|
||||
instances.add(this);
|
||||
super.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
super.stop();
|
||||
instances.remove(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true the initial expiration period has passed
|
||||
* @return true if expired
|
||||
|
|
|
@ -34,6 +34,7 @@ import javax.swing.tree.*;
|
|||
|
||||
import org.junit.Assert;
|
||||
|
||||
import generic.timer.ExpiringSwingTimer;
|
||||
import ghidra.framework.ApplicationConfiguration;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.datastruct.WeakSet;
|
||||
|
@ -1149,6 +1150,39 @@ public class AbstractGuiTest extends AbstractGenericTest {
|
|||
// Swing Methods
|
||||
//==================================================================================================
|
||||
|
||||
public static boolean waitForExpiringSwingTimers() {
|
||||
if (SwingUtilities.isEventDispatchThread()) {
|
||||
throw new AssertException("Can't wait for swing from within the swing thread!");
|
||||
}
|
||||
|
||||
// Note: this is based on the waitForSwing() timeout; this can be adjusted
|
||||
boolean waited = false;
|
||||
int MAX_SWING_TIMEOUT = 15000;
|
||||
int totalTime = 0;
|
||||
while (totalTime < MAX_SWING_TIMEOUT) {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<ExpiringSwingTimer> timers = runSwing(() -> {
|
||||
return (Set<ExpiringSwingTimer>) getInstanceField("instances",
|
||||
ExpiringSwingTimer.class);
|
||||
});
|
||||
if (timers.isEmpty()) {
|
||||
return waited;
|
||||
}
|
||||
|
||||
waited = true;
|
||||
totalTime += sleep(DEFAULT_WAIT_DELAY);
|
||||
}
|
||||
|
||||
if (totalTime >= MAX_SWING_TIMEOUT) {
|
||||
Msg.debug(AbstractGenericTest.class,
|
||||
"Timed-out waitinig for ExpiringSwingTimerc after " + totalTime + " ms. ");
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the Swing thread to process any pending events. This method
|
||||
* also waits for any {@link SwingUpdateManager}s that have pending events
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue