GP-2968: per review comment (cleanup)

GP-2968: prompting ReviewTool
GP-2968: my bad, NPE check was hanging the launch
GP-2968: fix for passing Enums; fix for toMap'ing nulls
GP-2968:  refactoring args logic into loop w/ handling for exception
GP-2968: fix for NPE & stored parameter errors
This commit is contained in:
d-millar 2023-01-13 11:48:41 -05:00
parent f930701ed6
commit c43351d742
13 changed files with 159 additions and 92 deletions

View file

@ -1382,9 +1382,8 @@ public class DbgManagerImpl implements DbgManager {
BitmaskSet<DebugVerifierFlags> vf = BitmaskSet<DebugVerifierFlags> vf =
new BitmaskSet<DebugVerifierFlags>(DebugVerifierFlags.class, new BitmaskSet<DebugVerifierFlags>(DebugVerifierFlags.class,
vfVal == null ? 0 : vfVal); vfVal == null ? 0 : vfVal);
execute(new DbgLaunchProcessCommand(this, args, return execute(new DbgLaunchProcessCommand(this, args,
initDir, env, cf, ef, vf)); initDir, env, cf, ef, vf)).thenApply(__ -> null);
return AsyncUtils.NIL;
} }
public CompletableFuture<?> openFile(Map<String, ?> args) { public CompletableFuture<?> openFile(Map<String, ?> args) {

View file

@ -82,7 +82,7 @@ public class DbgModelTargetContinuationOptionImpl extends DbgModelTargetObjectIm
public void setAttributes() { public void setAttributes() {
changeAttributes(List.of(), List.of(), Map.of( // changeAttributes(List.of(), List.of(), Map.of( //
DISPLAY_ATTRIBUTE_NAME, getName() + " : " + optionCont.description, // DISPLAY_ATTRIBUTE_NAME, getName() + " : " + optionCont.description, //
VALUE_ATTRIBUTE_NAME, optionCont, // VALUE_ATTRIBUTE_NAME, optionCont.val, //
ENABLED_ATTRIBUTE_NAME, ENABLED_ATTRIBUTE_NAME,
optionCont.equals(DebugFilterContinuationOption.DEBUG_FILTER_GO_HANDLED)), optionCont.equals(DebugFilterContinuationOption.DEBUG_FILTER_GO_HANDLED)),
"Refreshed"); "Refreshed");

View file

@ -83,7 +83,7 @@ public class DbgModelTargetExecutionOptionImpl extends DbgModelTargetObjectImpl
public void setAttributes() { public void setAttributes() {
changeAttributes(List.of(), List.of(), Map.of( // changeAttributes(List.of(), List.of(), Map.of( //
DISPLAY_ATTRIBUTE_NAME, getName() + " : " + optionExc.description, // DISPLAY_ATTRIBUTE_NAME, getName() + " : " + optionExc.description, //
VALUE_ATTRIBUTE_NAME, optionExc, // VALUE_ATTRIBUTE_NAME, optionExc.val, //
ENABLED_ATTRIBUTE_NAME, ENABLED_ATTRIBUTE_NAME,
optionExc.equals(DebugFilterExecutionOption.DEBUG_FILTER_BREAK)), "Refreshed"); optionExc.equals(DebugFilterExecutionOption.DEBUG_FILTER_BREAK)), "Refreshed");
} }

View file

@ -78,6 +78,7 @@ public class DbgModelTargetProcessLaunchConnectorImpl extends DbgModelTargetObje
map.put("cf", cf); map.put("cf", cf);
map.put("ef", ef); map.put("ef", ef);
map.put("vf", vf); map.put("vf", vf);
// Innocuous comment: up-up-down-down-left-right-left-right-B-A
return map; return map;
} }
@ -91,11 +92,4 @@ public class DbgModelTargetProcessLaunchConnectorImpl extends DbgModelTargetObje
return getManager().launch(args); return getManager().launch(args);
} }
// public CompletableFuture<Void> launch(List<String> args) {
// return AsyncUtils.sequence(TypeSpec.VOID).then(seq -> {
// getManager().launch(args).handle(seq::nextIgnore);
// }).finish().exceptionally((exc) -> {
// throw new DebuggerUserException("Launch failed for " + args);
// });
// }
} }

View file

@ -59,7 +59,6 @@ import ghidra.dbg.error.DebuggerMemoryAccessException;
import ghidra.dbg.target.*; import ghidra.dbg.target.*;
import ghidra.dbg.target.TargetConsole.Channel; import ghidra.dbg.target.TargetConsole.Channel;
import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState;
import ghidra.dbg.target.TargetLauncher.TargetCmdLineLauncher;
import ghidra.dbg.target.TargetMethod.ParameterDescription; import ghidra.dbg.target.TargetMethod.ParameterDescription;
import ghidra.dbg.target.TargetSteppable.TargetStepKind; import ghidra.dbg.target.TargetSteppable.TargetStepKind;
import ghidra.dbg.util.DebuggerCallbackReorderer; import ghidra.dbg.util.DebuggerCallbackReorderer;
@ -1335,38 +1334,32 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter
if (currentProgram == null) { if (currentProgram == null) {
return; return;
} }
performAction(context, true, TargetLauncher.class, launcher -> { performLaunchAction(context, false);
// TODO: A generic or pluggable way of deriving the launch arguments
return launcher.launch(Map.of(
TargetCmdLineLauncher.CMDLINE_ARGS_NAME, currentProgram.getExecutablePath()));
}, "Couldn't launch");
} }
public void performLaunch(ActionContext context) { public void performLaunch(ActionContext context) {
performAction(context, true, TargetLauncher.class, launcher -> { performLaunchAction(context, true);
}
Map<String, ?> args = launchOffer.getLauncherArgs(launcher, true); private void performLaunchAction(ActionContext context, boolean p) {
if (args == null) { performAction(context, true, TargetLauncher.class, launcher -> {
// Cancelled var locals = new Object() {
return AsyncUtils.NIL; boolean prompt = p;
} };
return launcher.launch(args); return AsyncUtils.loop(TypeSpec.VOID, (loop) -> {
/* Map<String, ?> args = launchOffer.getLauncherArgs(launcher, locals.prompt);
String argsKey = TargetCmdLineLauncher.CMDLINE_ARGS_NAME; if (args == null) {
String path = (currentProgram != null) ? currentProgram.getExecutablePath() : null; // Cancelled
launchDialog.setCurrentContext(path); loop.exit();
String cmdlineArgs = launchDialog.getMemorizedArgument(argsKey, String.class); }
if (cmdlineArgs == null) { else {
cmdlineArgs = path; launcher.launch(args).thenAccept(loop::exit).exceptionally(ex -> {
launchDialog.setMemorizedArgument(argsKey, String.class, loop.repeat();
cmdlineArgs); return null;
} });
Map<String, ?> args = launchDialog.promptArguments(launcher.getParameters()); }
if (args == null) { locals.prompt = true;
return AsyncUtils.NIL; });
}
return launcher.launch(args);
*/
}, "Couldn't launch"); }, "Couldn't launch");
} }

View file

@ -19,9 +19,7 @@ import java.awt.BorderLayout;
import java.awt.FlowLayout; import java.awt.FlowLayout;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.beans.*; import java.beans.*;
import java.util.HashMap; import java.util.*;
import java.util.Map;
import java.util.stream.Collectors;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.EmptyBorder; import javax.swing.border.EmptyBorder;
@ -32,6 +30,7 @@ import org.apache.commons.lang3.tuple.MutablePair;
import org.jdom.Element; import org.jdom.Element;
import docking.DialogComponentProvider; import docking.DialogComponentProvider;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils; import ghidra.app.plugin.core.debug.utils.MiscellaneousUtils;
import ghidra.dbg.target.TargetMethod; import ghidra.dbg.target.TargetMethod;
import ghidra.dbg.target.TargetMethod.ParameterDescription; import ghidra.dbg.target.TargetMethod.ParameterDescription;
@ -92,6 +91,8 @@ public class DebuggerMethodInvocationDialog extends DialogComponentProvider
private PairLayout layout; private PairLayout layout;
protected JButton invokeButton; protected JButton invokeButton;
protected JButton resetButton;
protected boolean resetRequested;
private final PluginTool tool; private final PluginTool tool;
private Map<String, ParameterDescription<?>> parameters; private Map<String, ParameterDescription<?>> parameters;
@ -105,7 +106,7 @@ public class DebuggerMethodInvocationDialog extends DialogComponentProvider
super(title, true, false, true, false); super(title, true, false, true, false);
this.tool = tool; this.tool = tool;
populateComponents(buttonText, buttonIcon); populateComponents(buttonText, buttonIcon, "Reset", DebuggerResources.ICON_REFRESH);
setRememberSize(false); setRememberSize(false);
} }
@ -126,7 +127,8 @@ public class DebuggerMethodInvocationDialog extends DialogComponentProvider
populateOptions(); populateOptions();
} }
private void populateComponents(String buttonText, Icon buttonIcon) { private void populateComponents(String buttonText, Icon buttonIcon,
String resetText, Icon resetIcon) {
panel = new JPanel(new BorderLayout()); panel = new JPanel(new BorderLayout());
panel.setBorder(new EmptyBorder(10, 10, 10, 10)); panel.setBorder(new EmptyBorder(10, 10, 10, 10));
@ -144,19 +146,31 @@ public class DebuggerMethodInvocationDialog extends DialogComponentProvider
invokeButton = new JButton(buttonText, buttonIcon); invokeButton = new JButton(buttonText, buttonIcon);
addButton(invokeButton); addButton(invokeButton);
resetButton = new JButton(resetText, resetIcon);
addButton(resetButton);
addCancelButton(); addCancelButton();
invokeButton.addActionListener(this::invoke); invokeButton.addActionListener(this::invoke);
resetButton.addActionListener(this::reset);
resetRequested = false;
} }
@Override @Override
protected void cancelCallback() { protected void cancelCallback() {
this.arguments = null; this.arguments = null;
this.resetRequested = false;
close(); close();
} }
private void invoke(ActionEvent evt) { private void invoke(ActionEvent evt) {
this.arguments = TargetMethod.validateArguments(parameters, collectArguments(), false); this.arguments = TargetMethod.validateArguments(parameters, collectArguments(), false);
this.resetRequested = false;
close();
}
private void reset(ActionEvent evt) {
this.arguments = new LinkedHashMap<>();
this.resetRequested = true;
close(); close();
} }
@ -175,7 +189,8 @@ public class DebuggerMethodInvocationDialog extends DialogComponentProvider
Msg.warn(this, "No editor for " + type + "? Trying String instead"); Msg.warn(this, "No editor for " + type + "? Trying String instead");
editor = PropertyEditorManager.findEditor(String.class); editor = PropertyEditorManager.findEditor(String.class);
} }
editor.setValue(computeMemorizedValue(param)); Object val = computeMemorizedValue(param);
editor.setValue(val);
editor.addPropertyChangeListener(this); editor.addPropertyChangeListener(this);
pairPanel.add(MiscellaneousUtils.getEditorComponent(editor)); pairPanel.add(MiscellaneousUtils.getEditorComponent(editor));
// TODO: How to handle parameter with choices? // TODO: How to handle parameter with choices?
@ -184,10 +199,14 @@ public class DebuggerMethodInvocationDialog extends DialogComponentProvider
} }
protected Map<String, ?> collectArguments() { protected Map<String, ?> collectArguments() {
return paramEditors.keySet() Map<String, Object> map = new LinkedHashMap<>();
.stream() for (ParameterDescription<?> param : paramEditors.keySet()) {
.collect(Collectors.toMap(param -> param.name, Object val = memorized.get(NameTypePair.fromParameter(param));
param -> memorized.get(NameTypePair.fromParameter(param)))); if (val != null) {
map.put(param.name, val);
}
}
return map;
} }
public Map<String, ?> getArguments() { public Map<String, ?> getArguments() {
@ -196,6 +215,9 @@ public class DebuggerMethodInvocationDialog extends DialogComponentProvider
public <T> void setMemorizedArgument(String name, Class<T> type, T value) { public <T> void setMemorizedArgument(String name, Class<T> type, T value) {
//name = addContext(name, currentContext); //name = addContext(name, currentContext);
if (value == null) {
return;
}
memorized.put(new NameTypePair(name, type), value); memorized.put(new NameTypePair(name, type), value);
} }
@ -239,4 +261,8 @@ public class DebuggerMethodInvocationDialog extends DialogComponentProvider
} }
} }
public boolean isResetRequested() {
return resetRequested;
}
} }

View file

@ -61,8 +61,15 @@ import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.datastruct.CollectionChangeListener; import ghidra.util.datastruct.CollectionChangeListener;
import ghidra.util.datastruct.ListenerSet; import ghidra.util.datastruct.ListenerSet;
@PluginInfo(shortDescription = "Debugger models manager service", description = "Manage debug sessions, connections, and trace recording", category = PluginCategoryNames.DEBUGGER, packageName = DebuggerPluginPackage.NAME, status = PluginStatus.HIDDEN, servicesRequired = {}, servicesProvided = { @PluginInfo(
DebuggerModelService.class, }) shortDescription = "Debugger models manager service",
description = "Manage debug sessions, connections, and trace recording",
category = PluginCategoryNames.DEBUGGER,
packageName = DebuggerPluginPackage.NAME,
status = PluginStatus.HIDDEN,
servicesRequired = {},
servicesProvided = {
DebuggerModelService.class, })
public class DebuggerModelServicePlugin extends Plugin public class DebuggerModelServicePlugin extends Plugin
implements DebuggerModelServiceInternal, ApplicationLevelOnlyPlugin { implements DebuggerModelServiceInternal, ApplicationLevelOnlyPlugin {
@ -239,7 +246,10 @@ public class DebuggerModelServicePlugin extends Plugin
} }
model.addModelListener(forRemovalAndFocusListener); model.addModelListener(forRemovalAndFocusListener);
TargetObject root = model.getModelRoot(); TargetObject root = model.getModelRoot();
if (!root.isValid()) { // root == null, probably means we're between model construction
// and root construction, but the model was not closed, so no need
// to invalidate
if (root != null && !root.isValid()) {
forRemovalAndFocusListener.invalidated(root, root, forRemovalAndFocusListener.invalidated(root, root,
"Invalidated before or during add to service"); "Invalidated before or during add to service");
} }

View file

@ -39,6 +39,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.DebugProgramAction;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.DisconnectAllAction; import ghidra.app.plugin.core.debug.gui.DebuggerResources.DisconnectAllAction;
import ghidra.app.plugin.core.debug.mapping.DebuggerTargetTraceMapper; import ghidra.app.plugin.core.debug.mapping.DebuggerTargetTraceMapper;
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer; import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer;
import ghidra.app.plugin.core.debug.service.model.launch.DebuggerProgramLaunchOffer.PromptMode;
import ghidra.app.plugin.core.debug.utils.BackgroundUtils; import ghidra.app.plugin.core.debug.utils.BackgroundUtils;
import ghidra.app.services.*; import ghidra.app.services.*;
import ghidra.async.AsyncUtils; import ghidra.async.AsyncUtils;
@ -88,7 +89,7 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
new DebuggerProgramLaunchOffer() { new DebuggerProgramLaunchOffer() {
@Override @Override
public CompletableFuture<LaunchResult> launchProgram(TaskMonitor monitor, public CompletableFuture<LaunchResult> launchProgram(TaskMonitor monitor,
boolean prompt, LaunchConfigurator configurator) { PromptMode prompt, LaunchConfigurator configurator) {
throw new AssertionError("Who clicked me?"); throw new AssertionError("Who clicked me?");
} }
@ -330,7 +331,8 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
return offers.sorted(Comparator.comparingInt(o -> -mrl.indexOf(o.getConfigName()))); return offers.sorted(Comparator.comparingInt(o -> -mrl.indexOf(o.getConfigName())));
} }
private void debugProgram(DebuggerProgramLaunchOffer offer, Program program, boolean prompt) { private void debugProgram(DebuggerProgramLaunchOffer offer, Program program,
PromptMode prompt) {
BackgroundUtils.asyncModal(tool, offer.getButtonTitle(), true, true, m -> { BackgroundUtils.asyncModal(tool, offer.getButtonTitle(), true, true, m -> {
List<String> recent = new ArrayList<>(readMostRecentLaunches(program)); List<String> recent = new ArrayList<>(readMostRecentLaunches(program));
recent.remove(offer.getConfigName()); recent.remove(offer.getConfigName());
@ -360,7 +362,7 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
if (offer == null || program == null) { if (offer == null || program == null) {
return; return;
} }
debugProgram(offer, program, false); debugProgram(offer, program, PromptMode.ON_ERROR);
} }
private void debugProgramStateActivated(ActionState<DebuggerProgramLaunchOffer> offer, private void debugProgramStateActivated(ActionState<DebuggerProgramLaunchOffer> offer,
@ -375,7 +377,7 @@ public class DebuggerModelServiceProxyPlugin extends Plugin
if (program == null) { if (program == null) {
return; return;
} }
debugProgram(offer, program, true); debugProgram(offer, program, PromptMode.ALWAYS);
} }
private void updateActionDebugProgram() { private void updateActionDebugProgram() {

View file

@ -15,8 +15,11 @@
*/ */
package ghidra.app.plugin.core.debug.service.model.launch; package ghidra.app.plugin.core.debug.service.model.launch;
import static ghidra.async.AsyncUtils.*;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -267,7 +270,12 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
if (program == null) { if (program == null) {
return Map.of(); return Map.of();
} }
return Map.of(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, program.getExecutablePath()); Map<String, Object> map = new LinkedHashMap<String, Object>();
for (Entry<String, ParameterDescription<?>> entry : params.entrySet()) {
map.put(entry.getKey(), entry.getValue().defaultValue);
}
map.put(TargetCmdLineLauncher.CMDLINE_ARGS_NAME, program.getExecutablePath());
return map;
} }
/** /**
@ -282,20 +290,30 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
DebuggerMethodInvocationDialog dialog = DebuggerMethodInvocationDialog dialog =
new DebuggerMethodInvocationDialog(tool, getButtonTitle(), "Launch", getIcon()); new DebuggerMethodInvocationDialog(tool, getButtonTitle(), "Launch", getIcon());
// NB. Do not invoke read/writeConfigState // NB. Do not invoke read/writeConfigState
Map<String, ?> args = configurator.configureLauncher(launcher, Map<String, ?> args;
loadLastLauncherArgs(launcher, true), RelPrompt.BEFORE); boolean reset = false;
for (ParameterDescription<?> param : params.values()) { do {
Object val = args.get(param.name); args = configurator.configureLauncher(launcher,
if (val != null) { loadLastLauncherArgs(launcher, true), RelPrompt.BEFORE);
dialog.setMemorizedArgument(param.name, param.type.asSubclass(Object.class), val); for (ParameterDescription<?> param : params.values()) {
Object val = args.get(param.name);
if (val != null) {
dialog.setMemorizedArgument(param.name, param.type.asSubclass(Object.class),
val);
}
} }
args = dialog.promptArguments(params);
if (args == null) {
// Cancelled
return null;
}
reset = dialog.isResetRequested();
if (reset) {
args = generateDefaultLauncherArgs(params);
}
saveLauncherArgs(args, params);
} }
args = dialog.promptArguments(params); while (reset);
if (args == null) {
// Cancelled
return null;
}
saveLauncherArgs(args, params);
return args; return args;
} }
@ -328,12 +346,15 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
try { try {
Element element = XmlUtilities.fromString(property); Element element = XmlUtilities.fromString(property);
SaveState state = new SaveState(element); SaveState state = new SaveState(element);
List<String> names = List.of(state.getNames());
Map<String, Object> args = new LinkedHashMap<>(); Map<String, Object> args = new LinkedHashMap<>();
for (ParameterDescription<?> param : params.values()) { for (ParameterDescription<?> param : params.values()) {
Object configState = if (names.contains(param.name)) {
ConfigStateField.getState(state, param.type, param.name); Object configState =
if (configState != null) { ConfigStateField.getState(state, param.type, param.name);
args.put(param.name, configState); if (configState != null) {
args.put(param.name, configState);
}
} }
} }
if (!args.isEmpty()) { if (!args.isEmpty()) {
@ -347,7 +368,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
e); e);
} }
Msg.error(this, Msg.error(this,
"Saved launcher args are corrup, or launcher parameters changed. Defaulting.", "Saved launcher args are corrupt, or launcher parameters changed. Defaulting.",
e); e);
} }
} }
@ -463,12 +484,14 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
// Eww. // Eww.
protected CompletableFuture<Void> launch(TargetLauncher launcher, protected CompletableFuture<Void> launch(TargetLauncher launcher,
boolean prompt, LaunchConfigurator configurator) { boolean prompt, LaunchConfigurator configurator, TaskMonitor monitor) {
Map<String, ?> args = getLauncherArgs(launcher, prompt, configurator); Map<String, ?> args = getLauncherArgs(launcher, prompt, configurator);
if (args == null) { if (args == null) {
throw new CancellationException(); throw new CancellationException();
} }
return launcher.launch(args); return AsyncTimer.DEFAULT_TIMER.mark()
.timeOut(
launcher.launch(args), getTimeoutMillis(), () -> onTimedOutLaunch(monitor));
} }
protected void checkCancelled(TaskMonitor monitor) { protected void checkCancelled(TaskMonitor monitor) {
@ -560,7 +583,7 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
} }
@Override @Override
public CompletableFuture<LaunchResult> launchProgram(TaskMonitor monitor, boolean prompt, public CompletableFuture<LaunchResult> launchProgram(TaskMonitor monitor, PromptMode mode,
LaunchConfigurator configurator) { LaunchConfigurator configurator) {
DebuggerModelService service = tool.getService(DebuggerModelService.class); DebuggerModelService service = tool.getService(DebuggerModelService.class);
DebuggerStaticMappingService mappingService = DebuggerStaticMappingService mappingService =
@ -573,12 +596,13 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
TargetObject target; TargetObject target;
TraceRecorder recorder; TraceRecorder recorder;
Throwable exception; Throwable exception;
boolean prompt = mode == PromptMode.ALWAYS;
LaunchResult getResult() { LaunchResult getResult() {
return new LaunchResult(model, target, recorder, exception); return new LaunchResult(model, target, recorder, exception);
} }
}; };
return connect(service, prompt, configurator).thenCompose(m -> { return connect(service, locals.prompt, configurator).thenCompose(m -> {
checkCancelled(monitor); checkCancelled(monitor);
locals.model = m; locals.model = m;
monitor.incrementProgress(1); monitor.incrementProgress(1);
@ -591,12 +615,14 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
monitor.incrementProgress(1); monitor.incrementProgress(1);
monitor.setMessage("Launching"); monitor.setMessage("Launching");
locals.futureTarget = listenForTarget(l.getModel()); locals.futureTarget = listenForTarget(l.getModel());
if (prompt) { return loop(TypeSpec.VOID, (loop) -> {
return launch(l, true, configurator); launch(l, locals.prompt, configurator, monitor).thenAccept(loop::exit)
} .exceptionally(ex -> {
return AsyncTimer.DEFAULT_TIMER.mark() loop.repeat();
.timeOut(launch(l, false, configurator), getTimeoutMillis(), return null;
() -> onTimedOutLaunch(monitor)); });
locals.prompt = mode != PromptMode.NEVER;
});
}).thenCompose(__ -> { }).thenCompose(__ -> {
checkCancelled(monitor); checkCancelled(monitor);
monitor.incrementProgress(1); monitor.incrementProgress(1);
@ -636,4 +662,5 @@ public abstract class AbstractDebuggerProgramLaunchOffer implements DebuggerProg
return locals.getResult(); return locals.getResult();
}); });
} }
} }

View file

@ -82,6 +82,21 @@ public interface DebuggerProgramLaunchOffer {
AFTER; AFTER;
} }
public enum PromptMode {
/**
* The user is always prompted for parameters.
*/
ALWAYS,
/**
* The user is never prompted for parameters.
*/
NEVER,
/**
* The user is prompted after an error.
*/
ON_ERROR;
}
/** /**
* Callbacks for custom configuration when launching a program * Callbacks for custom configuration when launching a program
*/ */
@ -118,7 +133,7 @@ public interface DebuggerProgramLaunchOffer {
* @param configurator the configuration callbacks * @param configurator the configuration callbacks
* @return a future which completes when the program is launched * @return a future which completes when the program is launched
*/ */
CompletableFuture<LaunchResult> launchProgram(TaskMonitor monitor, boolean prompt, CompletableFuture<LaunchResult> launchProgram(TaskMonitor monitor, PromptMode prompt,
LaunchConfigurator configurator); LaunchConfigurator configurator);
/** /**
@ -128,7 +143,7 @@ public interface DebuggerProgramLaunchOffer {
* @param prompt if the user should be prompted to confirm launch parameters * @param prompt if the user should be prompted to confirm launch parameters
* @return a future which completes when the program is launched * @return a future which completes when the program is launched
*/ */
default CompletableFuture<LaunchResult> launchProgram(TaskMonitor monitor, boolean prompt) { default CompletableFuture<LaunchResult> launchProgram(TaskMonitor monitor, PromptMode prompt) {
return launchProgram(monitor, prompt, LaunchConfigurator.NOP); return launchProgram(monitor, prompt, LaunchConfigurator.NOP);
} }

View file

@ -1491,7 +1491,7 @@ public interface FlatDebuggerAPI {
default LaunchResult launch(DebuggerProgramLaunchOffer offer, String commandLine, default LaunchResult launch(DebuggerProgramLaunchOffer offer, String commandLine,
TaskMonitor monitor) { TaskMonitor monitor) {
try { try {
return waitOn(offer.launchProgram(monitor, false, new LaunchConfigurator() { return waitOn(offer.launchProgram(monitor, PromptMode.NEVER, new LaunchConfigurator() {
@Override @Override
public Map<String, ?> configureLauncher(TargetLauncher launcher, public Map<String, ?> configureLauncher(TargetLauncher launcher,
Map<String, ?> arguments, RelPrompt relPrompt) { Map<String, ?> arguments, RelPrompt relPrompt) {
@ -1514,7 +1514,7 @@ public interface FlatDebuggerAPI {
*/ */
default LaunchResult launch(DebuggerProgramLaunchOffer offer, TaskMonitor monitor) { default LaunchResult launch(DebuggerProgramLaunchOffer offer, TaskMonitor monitor) {
try { try {
return waitOn(offer.launchProgram(monitor, false)); return waitOn(offer.launchProgram(monitor, PromptMode.NEVER));
} }
catch (InterruptedException | ExecutionException | TimeoutException e) { catch (InterruptedException | ExecutionException | TimeoutException e) {
// TODO: This is not ideal, since it's likely partially completed // TODO: This is not ideal, since it's likely partially completed

View file

@ -15,7 +15,7 @@
*/ */
package ghidra.app.plugin.core.debug.service.model; package ghidra.app.plugin.core.debug.service.model;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -35,7 +35,7 @@ public class TestDebuggerProgramLaunchOpinion implements DebuggerProgramLaunchOp
public static class TestDebuggerProgramLaunchOffer implements DebuggerProgramLaunchOffer { public static class TestDebuggerProgramLaunchOffer implements DebuggerProgramLaunchOffer {
@Override @Override
public CompletableFuture<LaunchResult> launchProgram(TaskMonitor monitor, boolean prompt, public CompletableFuture<LaunchResult> launchProgram(TaskMonitor monitor, PromptMode prompt,
LaunchConfigurator configurator) { LaunchConfigurator configurator) {
return CompletableFuture return CompletableFuture
.completedFuture(LaunchResult.totalFailure(new AssertionError())); .completedFuture(LaunchResult.totalFailure(new AssertionError()));

View file

@ -139,4 +139,5 @@ public interface TargetLauncher extends TargetObject {
* @return a future which completes when the command is completed * @return a future which completes when the command is completed
*/ */
public CompletableFuture<Void> launch(Map<String, ?> args); public CompletableFuture<Void> launch(Map<String, ?> args);
} }