mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-27 06:30:51 +00:00
GP-4292: Detect input-driven Model tree expansion and refresh.
This commit is contained in:
parent
d8dae6d092
commit
ed9297dd25
@ -277,6 +277,9 @@ def start_trace(name):
|
||||
with STATE.trace.open_tx("Create Root Object"):
|
||||
root = STATE.trace.create_root_object(schema_xml, 'Session')
|
||||
root.set_value('_display', 'GNU gdb ' + util.GDB_VERSION.full)
|
||||
STATE.trace.create_object(AVAILABLES_PATH).insert()
|
||||
STATE.trace.create_object(BREAKPOINTS_PATH).insert()
|
||||
STATE.trace.create_object(INFERIORS_PATH).insert()
|
||||
gdb.set_convenience_variable('_ghidra_tracing', True)
|
||||
|
||||
|
||||
@ -1057,7 +1060,7 @@ def put_available():
|
||||
procobj = STATE.trace.create_object(ppath)
|
||||
keys.append(AVAILABLE_KEY_PATTERN.format(pid=proc.pid))
|
||||
procobj.set_value('_pid', proc.pid)
|
||||
procobj.set_value('_display', '{} {}'.format(proc.pid, proc.name))
|
||||
procobj.set_value('_display', '{} {}'.format(proc.pid, proc.name()))
|
||||
procobj.insert()
|
||||
STATE.trace.proxy_object_path(AVAILABLES_PATH).retain_values(keys)
|
||||
|
||||
|
@ -428,7 +428,8 @@ public class TraceRmiTarget extends AbstractTarget {
|
||||
|
||||
@Override
|
||||
protected Map<String, ActionEntry> collectRefreshActions(ActionContext context) {
|
||||
return collectByName(ActionName.REFRESH, context);
|
||||
return collectFromMethods(connection.getMethods().getByAction(ActionName.REFRESH), context,
|
||||
true, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -141,6 +141,7 @@ public class DebuggerConsolePlugin extends Plugin implements DebuggerConsoleServ
|
||||
* For testing: get the number of rows having a given class of action context
|
||||
*
|
||||
* @param ctxCls the context class
|
||||
* @return the number of rows
|
||||
*/
|
||||
public long getRowCount(Class<? extends ActionContext> ctxCls) {
|
||||
return provider.getRowCount(ctxCls);
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package ghidra.app.plugin.core.debug.gui.control;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.services.DebuggerConsoleService;
|
||||
import ghidra.app.services.ProgressService;
|
||||
@ -25,25 +27,78 @@ import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.Task;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* A task for executing a target {@link ActionEntry}.
|
||||
*
|
||||
* <p>
|
||||
* This also has some static convenience methods for scheduling this and other types of tasks in the
|
||||
* Debugger tool.
|
||||
*/
|
||||
public class TargetActionTask extends Task {
|
||||
|
||||
public static void executeTask(PluginTool tool, Task task) {
|
||||
/**
|
||||
* Execute a task
|
||||
*
|
||||
* <p>
|
||||
* If available, this simply delegates to {@link ProgressService#execute(Task)}. If not, then
|
||||
* this falls back to {@link PluginTool#execute(Task)}.
|
||||
*
|
||||
* @param tool the tool in which to execute
|
||||
* @param task the task to execute
|
||||
* @return a future that completes (perhaps exceptionally) when the task is finished or
|
||||
* cancelled
|
||||
*/
|
||||
public static CompletableFuture<Void> executeTask(PluginTool tool, Task task) {
|
||||
ProgressService progressService = tool.getService(ProgressService.class);
|
||||
if (progressService != null) {
|
||||
progressService.execute(task);
|
||||
}
|
||||
else {
|
||||
tool.execute(task);
|
||||
return progressService.execute(task);
|
||||
}
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
tool.execute(
|
||||
new Task(task.getTaskTitle(), task.canCancel(), task.hasProgress(), task.isModal(),
|
||||
task.getWaitForTaskCompleted()) {
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
try {
|
||||
task.run(monitor);
|
||||
future.complete(null);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
future.cancel(false);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
tool.execute(task);
|
||||
return future;
|
||||
}
|
||||
|
||||
public static void runAction(PluginTool tool, String title, ActionEntry entry) {
|
||||
executeTask(tool, new TargetActionTask(tool, title, entry));
|
||||
/**
|
||||
* Execute an {@link ActionEntry}
|
||||
*
|
||||
* @param tool the tool in which to execute
|
||||
* @param title the title, often {@link ActionEntry#display()}
|
||||
* @param entry the action to execute
|
||||
* @return a future that completes (perhaps exceptionally) when the task is finished or
|
||||
* cancelled
|
||||
*/
|
||||
public static CompletableFuture<Void> runAction(PluginTool tool, String title,
|
||||
ActionEntry entry) {
|
||||
return executeTask(tool, new TargetActionTask(tool, title, entry));
|
||||
}
|
||||
|
||||
private final PluginTool tool;
|
||||
private final ActionEntry entry;
|
||||
|
||||
/**
|
||||
* Construct a task fore the given action
|
||||
*
|
||||
* @param tool the plugin tool
|
||||
* @param title the title, often {@link ActionEntry#display()}
|
||||
* @param entry the action to execute
|
||||
*/
|
||||
public TargetActionTask(PluginTool tool, String title, ActionEntry entry) {
|
||||
super(title, false, false, false);
|
||||
this.tool = tool;
|
||||
|
@ -18,13 +18,16 @@ package ghidra.app.plugin.core.debug.gui.model;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.TreeExpansionEvent;
|
||||
import javax.swing.event.TreeExpansionListener;
|
||||
import javax.swing.tree.TreePath;
|
||||
|
||||
import docking.*;
|
||||
import docking.action.DockingAction;
|
||||
@ -39,12 +42,17 @@ import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources.CloneWindowAction;
|
||||
import ghidra.app.plugin.core.debug.gui.MultiProviderSaveBehavior.SaveableProvider;
|
||||
import ghidra.app.plugin.core.debug.gui.control.TargetActionTask;
|
||||
import ghidra.app.plugin.core.debug.gui.model.AbstractQueryTablePanel.CellActivationListener;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ObjectRow;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTableModel.ValueRow;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.AbstractNode;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.*;
|
||||
import ghidra.app.plugin.core.debug.gui.model.ObjectTreeModel.RootNode;
|
||||
import ghidra.app.plugin.core.debug.gui.model.PathTableModel.PathRow;
|
||||
import ghidra.app.services.DebuggerTraceManagerService;
|
||||
import ghidra.debug.api.target.ActionName;
|
||||
import ghidra.debug.api.target.Target;
|
||||
import ghidra.debug.api.target.Target.ActionEntry;
|
||||
import ghidra.debug.api.tracemgr.DebuggerCoordinates;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.plugintool.*;
|
||||
@ -501,11 +509,26 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||
setPath(value == null ? TraceObjectKeyPath.of() : value.getCanonicalPath(),
|
||||
objectsTreePanel, EventOrigin.INTERNAL_GENERATED);
|
||||
});
|
||||
objectsTreePanel.tree.addTreeExpansionListener(new TreeExpansionListener() {
|
||||
@Override
|
||||
public void treeExpanded(TreeExpansionEvent expEvent) {
|
||||
if (EventQueue.getCurrentEvent() instanceof InputEvent event &&
|
||||
!event.isShiftDown()) {
|
||||
refreshTargetChildren(expEvent.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void treeCollapsed(TreeExpansionEvent event) {
|
||||
// I don't care
|
||||
}
|
||||
});
|
||||
objectsTreePanel.tree.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
|
||||
activateObjectSelectedInTree();
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -572,6 +595,54 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||
rebuildPanels();
|
||||
}
|
||||
|
||||
private TraceObjectValue getObject(TreePath path) {
|
||||
if (path.getLastPathComponent() instanceof CanonicalNode node) {
|
||||
return node.getValue();
|
||||
}
|
||||
if (path.getLastPathComponent() instanceof RootNode node) {
|
||||
return node.getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void refreshTargetChildren(TreePath path) {
|
||||
TraceObjectValue value = getObject(path);
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
DebuggerCoordinates current = traceManager.getCurrentFor(value.getTrace());
|
||||
if (!current.isAliveAndPresent() && limitToSnap) {
|
||||
return;
|
||||
}
|
||||
Target target = current.getTarget();
|
||||
if (target == null) {
|
||||
return;
|
||||
}
|
||||
Map<String, ActionEntry> actions = target.collectActions(ActionName.REFRESH,
|
||||
new DebuggerObjectActionContext(List.of(value), this, objectsTreePanel));
|
||||
for (ActionEntry ent : actions.values()) {
|
||||
if (ent.requiresPrompt()) {
|
||||
continue;
|
||||
}
|
||||
if (path.getLastPathComponent() instanceof AbstractNode node) {
|
||||
/**
|
||||
* This pending node does not duplicate what the lazy node already does. For all
|
||||
* it's concerned, once it has loaded the entries from the database, it is done.
|
||||
* This task asks the target to update that database, so it needs its own indicator.
|
||||
*/
|
||||
PendingNode pending = new PendingNode();
|
||||
node.addNode(0, pending);
|
||||
CompletableFuture<Void> future =
|
||||
TargetActionTask.runAction(plugin.getTool(), ent.display(), ent);
|
||||
future.handle((__, ex) -> {
|
||||
node.removeNode(pending);
|
||||
return null;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void activateObjectSelectedInTree() {
|
||||
List<AbstractNode> sel = objectsTreePanel.getSelectedItems();
|
||||
if (sel.size() != 1) {
|
||||
@ -580,7 +651,7 @@ public class DebuggerModelProvider extends ComponentProvider implements Saveable
|
||||
return;
|
||||
}
|
||||
TraceObjectValue value = sel.get(0).getValue();
|
||||
if (value.getValue() instanceof TraceObject child) {
|
||||
if (value != null && value.getValue() instanceof TraceObject child) {
|
||||
activatePath(child.getCanonicalPath());
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import javax.swing.Icon;
|
||||
|
||||
import docking.widgets.tree.GTreeLazyNode;
|
||||
import docking.widgets.tree.GTreeNode;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
|
||||
import ghidra.dbg.target.*;
|
||||
import ghidra.dbg.util.PathUtils.TargetObjectKeyComparator;
|
||||
@ -35,6 +36,7 @@ import ghidra.util.datastruct.WeakValueHashMap;
|
||||
import utilities.util.IDKeyed;
|
||||
|
||||
public class ObjectTreeModel implements DisplaysModified {
|
||||
public static final GIcon ICON_PENDING = new GIcon("icon.pending");
|
||||
|
||||
class ListenerForChanges extends TraceDomainObjectListener
|
||||
implements DomainObjectClosedListener {
|
||||
@ -193,6 +195,38 @@ public class ObjectTreeModel implements DisplaysModified {
|
||||
}
|
||||
}
|
||||
|
||||
public static class PendingNode extends GTreeLazyNode {
|
||||
@Override
|
||||
public String getName() {
|
||||
return ""; // Want it sorted to the front
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayText() {
|
||||
return "Refreshing...";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon(boolean expanded) {
|
||||
return ICON_PENDING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLeaf() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<GTreeNode> generateChildren() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTip() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class AbstractNode extends GTreeLazyNode {
|
||||
public abstract TraceObjectValue getValue();
|
||||
|
||||
@ -332,7 +366,7 @@ public class ObjectTreeModel implements DisplaysModified {
|
||||
}
|
||||
}
|
||||
|
||||
class RootNode extends AbstractNode {
|
||||
public class RootNode extends AbstractNode {
|
||||
@Override
|
||||
public TraceObjectValue getValue() {
|
||||
if (trace == null) {
|
||||
|
@ -398,7 +398,11 @@ public class ObjectsTreePanel extends JPanel {
|
||||
protected <R, A> R getItemsFromPaths(TreePath[] paths,
|
||||
Collector<? super AbstractNode, A, R> collector) {
|
||||
return Stream.of(paths)
|
||||
.map(p -> (AbstractNode) p.getLastPathComponent())
|
||||
.<AbstractNode> mapMulti((path, consumer) -> {
|
||||
if (path.getLastPathComponent() instanceof AbstractNode node) {
|
||||
consumer.accept(node);
|
||||
}
|
||||
})
|
||||
.collect(collector);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user