
Most commands were adding argument completion handling by themselves, resulting in a lot of unnecessary boilerplate. In many cases, this could be done generically given the argument definition and the entries in the g_argument_table. I'm going to address this in a couple passes. In this first pass, I added handling of commands that have only one argument list, with one argument type, either single or repeated, and changed all the commands that are of this sort (and don't have other bits of business in their completers.) I also added some missing connections between arg types and completions to the table, and added a RemoteFilename and RemotePath to use in places where we were using the Remote completers. Those arguments used to say they were "files" but they were in fact remote files. I also added a module arg type to use where we were using the module completer. In that case, we should call the argument module.
2830 lines
101 KiB
C++
2830 lines
101 KiB
C++
//===-- CommandObjectCommands.cpp -----------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "CommandObjectCommands.h"
|
|
#include "CommandObjectHelp.h"
|
|
#include "CommandObjectRegexCommand.h"
|
|
#include "lldb/Core/Debugger.h"
|
|
#include "lldb/Core/IOHandler.h"
|
|
#include "lldb/Interpreter/CommandHistory.h"
|
|
#include "lldb/Interpreter/CommandInterpreter.h"
|
|
#include "lldb/Interpreter/CommandOptionArgumentTable.h"
|
|
#include "lldb/Interpreter/CommandReturnObject.h"
|
|
#include "lldb/Interpreter/OptionArgParser.h"
|
|
#include "lldb/Interpreter/OptionValueBoolean.h"
|
|
#include "lldb/Interpreter/OptionValueString.h"
|
|
#include "lldb/Interpreter/OptionValueUInt64.h"
|
|
#include "lldb/Interpreter/Options.h"
|
|
#include "lldb/Interpreter/ScriptInterpreter.h"
|
|
#include "lldb/Utility/Args.h"
|
|
#include "lldb/Utility/StringList.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include <optional>
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
|
|
// CommandObjectCommandsSource
|
|
|
|
#define LLDB_OPTIONS_source
|
|
#include "CommandOptions.inc"
|
|
|
|
class CommandObjectCommandsSource : public CommandObjectParsed {
|
|
public:
|
|
CommandObjectCommandsSource(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(
|
|
interpreter, "command source",
|
|
"Read and execute LLDB commands from the file <filename>.",
|
|
nullptr) {
|
|
CommandArgumentEntry arg;
|
|
CommandArgumentData file_arg;
|
|
|
|
// Define the first (and only) variant of this arg.
|
|
file_arg.arg_type = eArgTypeFilename;
|
|
file_arg.arg_repetition = eArgRepeatPlain;
|
|
|
|
// There is only one variant this argument could be; put it into the
|
|
// argument entry.
|
|
arg.push_back(file_arg);
|
|
|
|
// Push the data for the first argument into the m_arguments vector.
|
|
m_arguments.push_back(arg);
|
|
}
|
|
|
|
~CommandObjectCommandsSource() override = default;
|
|
|
|
std::optional<std::string> GetRepeatCommand(Args ¤t_command_args,
|
|
uint32_t index) override {
|
|
return std::string("");
|
|
}
|
|
|
|
Options *GetOptions() override { return &m_options; }
|
|
|
|
protected:
|
|
class CommandOptions : public Options {
|
|
public:
|
|
CommandOptions()
|
|
: m_stop_on_error(true), m_silent_run(false), m_stop_on_continue(true),
|
|
m_cmd_relative_to_command_file(false) {}
|
|
|
|
~CommandOptions() override = default;
|
|
|
|
Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
|
|
ExecutionContext *execution_context) override {
|
|
Status error;
|
|
const int short_option = m_getopt_table[option_idx].val;
|
|
|
|
switch (short_option) {
|
|
case 'e':
|
|
error = m_stop_on_error.SetValueFromString(option_arg);
|
|
break;
|
|
|
|
case 'c':
|
|
error = m_stop_on_continue.SetValueFromString(option_arg);
|
|
break;
|
|
|
|
case 'C':
|
|
m_cmd_relative_to_command_file = true;
|
|
break;
|
|
|
|
case 's':
|
|
error = m_silent_run.SetValueFromString(option_arg);
|
|
break;
|
|
|
|
default:
|
|
llvm_unreachable("Unimplemented option");
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
void OptionParsingStarting(ExecutionContext *execution_context) override {
|
|
m_stop_on_error.Clear();
|
|
m_silent_run.Clear();
|
|
m_stop_on_continue.Clear();
|
|
m_cmd_relative_to_command_file.Clear();
|
|
}
|
|
|
|
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
|
|
return llvm::ArrayRef(g_source_options);
|
|
}
|
|
|
|
// Instance variables to hold the values for command options.
|
|
|
|
OptionValueBoolean m_stop_on_error;
|
|
OptionValueBoolean m_silent_run;
|
|
OptionValueBoolean m_stop_on_continue;
|
|
OptionValueBoolean m_cmd_relative_to_command_file;
|
|
};
|
|
|
|
void DoExecute(Args &command, CommandReturnObject &result) override {
|
|
if (command.GetArgumentCount() != 1) {
|
|
result.AppendErrorWithFormat(
|
|
"'%s' takes exactly one executable filename argument.\n",
|
|
GetCommandName().str().c_str());
|
|
return;
|
|
}
|
|
|
|
FileSpec source_dir = {};
|
|
if (m_options.m_cmd_relative_to_command_file) {
|
|
source_dir = GetDebugger().GetCommandInterpreter().GetCurrentSourceDir();
|
|
if (!source_dir) {
|
|
result.AppendError("command source -C can only be specified "
|
|
"from a command file");
|
|
result.SetStatus(eReturnStatusFailed);
|
|
return;
|
|
}
|
|
}
|
|
|
|
FileSpec cmd_file(command[0].ref());
|
|
if (source_dir) {
|
|
// Prepend the source_dir to the cmd_file path:
|
|
if (!cmd_file.IsRelative()) {
|
|
result.AppendError("command source -C can only be used "
|
|
"with a relative path.");
|
|
result.SetStatus(eReturnStatusFailed);
|
|
return;
|
|
}
|
|
cmd_file.MakeAbsolute(source_dir);
|
|
}
|
|
|
|
FileSystem::Instance().Resolve(cmd_file);
|
|
|
|
CommandInterpreterRunOptions options;
|
|
// If any options were set, then use them
|
|
if (m_options.m_stop_on_error.OptionWasSet() ||
|
|
m_options.m_silent_run.OptionWasSet() ||
|
|
m_options.m_stop_on_continue.OptionWasSet()) {
|
|
if (m_options.m_stop_on_continue.OptionWasSet())
|
|
options.SetStopOnContinue(
|
|
m_options.m_stop_on_continue.GetCurrentValue());
|
|
|
|
if (m_options.m_stop_on_error.OptionWasSet())
|
|
options.SetStopOnError(m_options.m_stop_on_error.GetCurrentValue());
|
|
|
|
// Individual silent setting is override for global command echo settings.
|
|
if (m_options.m_silent_run.GetCurrentValue()) {
|
|
options.SetSilent(true);
|
|
} else {
|
|
options.SetPrintResults(true);
|
|
options.SetPrintErrors(true);
|
|
options.SetEchoCommands(m_interpreter.GetEchoCommands());
|
|
options.SetEchoCommentCommands(m_interpreter.GetEchoCommentCommands());
|
|
}
|
|
}
|
|
|
|
m_interpreter.HandleCommandsFromFile(cmd_file, options, result);
|
|
}
|
|
|
|
CommandOptions m_options;
|
|
};
|
|
|
|
#pragma mark CommandObjectCommandsAlias
|
|
// CommandObjectCommandsAlias
|
|
|
|
#define LLDB_OPTIONS_alias
|
|
#include "CommandOptions.inc"
|
|
|
|
static const char *g_python_command_instructions =
|
|
"Enter your Python command(s). Type 'DONE' to end.\n"
|
|
"You must define a Python function with this signature:\n"
|
|
"def my_command_impl(debugger, args, exe_ctx, result, internal_dict):\n";
|
|
|
|
class CommandObjectCommandsAlias : public CommandObjectRaw {
|
|
protected:
|
|
class CommandOptions : public OptionGroup {
|
|
public:
|
|
CommandOptions() = default;
|
|
|
|
~CommandOptions() override = default;
|
|
|
|
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
|
|
return llvm::ArrayRef(g_alias_options);
|
|
}
|
|
|
|
Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_value,
|
|
ExecutionContext *execution_context) override {
|
|
Status error;
|
|
|
|
const int short_option = GetDefinitions()[option_idx].short_option;
|
|
std::string option_str(option_value);
|
|
|
|
switch (short_option) {
|
|
case 'h':
|
|
m_help.SetCurrentValue(option_str);
|
|
m_help.SetOptionWasSet();
|
|
break;
|
|
|
|
case 'H':
|
|
m_long_help.SetCurrentValue(option_str);
|
|
m_long_help.SetOptionWasSet();
|
|
break;
|
|
|
|
default:
|
|
llvm_unreachable("Unimplemented option");
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
void OptionParsingStarting(ExecutionContext *execution_context) override {
|
|
m_help.Clear();
|
|
m_long_help.Clear();
|
|
}
|
|
|
|
OptionValueString m_help;
|
|
OptionValueString m_long_help;
|
|
};
|
|
|
|
OptionGroupOptions m_option_group;
|
|
CommandOptions m_command_options;
|
|
|
|
public:
|
|
Options *GetOptions() override { return &m_option_group; }
|
|
|
|
CommandObjectCommandsAlias(CommandInterpreter &interpreter)
|
|
: CommandObjectRaw(
|
|
interpreter, "command alias",
|
|
"Define a custom command in terms of an existing command.") {
|
|
m_option_group.Append(&m_command_options);
|
|
m_option_group.Finalize();
|
|
|
|
SetHelpLong(
|
|
"'alias' allows the user to create a short-cut or abbreviation for long \
|
|
commands, multi-word commands, and commands that take particular options. \
|
|
Below are some simple examples of how one might use the 'alias' command:"
|
|
R"(
|
|
|
|
(lldb) command alias sc script
|
|
|
|
Creates the abbreviation 'sc' for the 'script' command.
|
|
|
|
(lldb) command alias bp breakpoint
|
|
|
|
)"
|
|
" Creates the abbreviation 'bp' for the 'breakpoint' command. Since \
|
|
breakpoint commands are two-word commands, the user would still need to \
|
|
enter the second word after 'bp', e.g. 'bp enable' or 'bp delete'."
|
|
R"(
|
|
|
|
(lldb) command alias bpl breakpoint list
|
|
|
|
Creates the abbreviation 'bpl' for the two-word command 'breakpoint list'.
|
|
|
|
)"
|
|
"An alias can include some options for the command, with the values either \
|
|
filled in at the time the alias is created, or specified as positional \
|
|
arguments, to be filled in when the alias is invoked. The following example \
|
|
shows how to create aliases with options:"
|
|
R"(
|
|
|
|
(lldb) command alias bfl breakpoint set -f %1 -l %2
|
|
|
|
)"
|
|
" Creates the abbreviation 'bfl' (for break-file-line), with the -f and -l \
|
|
options already part of the alias. So if the user wants to set a breakpoint \
|
|
by file and line without explicitly having to use the -f and -l options, the \
|
|
user can now use 'bfl' instead. The '%1' and '%2' are positional placeholders \
|
|
for the actual arguments that will be passed when the alias command is used. \
|
|
The number in the placeholder refers to the position/order the actual value \
|
|
occupies when the alias is used. All the occurrences of '%1' in the alias \
|
|
will be replaced with the first argument, all the occurrences of '%2' in the \
|
|
alias will be replaced with the second argument, and so on. This also allows \
|
|
actual arguments to be used multiple times within an alias (see 'process \
|
|
launch' example below)."
|
|
R"(
|
|
|
|
)"
|
|
"Note: the positional arguments must substitute as whole words in the resultant \
|
|
command, so you can't at present do something like this to append the file extension \
|
|
\".cpp\":"
|
|
R"(
|
|
|
|
(lldb) command alias bcppfl breakpoint set -f %1.cpp -l %2
|
|
|
|
)"
|
|
"For more complex aliasing, use the \"command regex\" command instead. In the \
|
|
'bfl' case above, the actual file value will be filled in with the first argument \
|
|
following 'bfl' and the actual line number value will be filled in with the second \
|
|
argument. The user would use this alias as follows:"
|
|
R"(
|
|
|
|
(lldb) command alias bfl breakpoint set -f %1 -l %2
|
|
(lldb) bfl my-file.c 137
|
|
|
|
This would be the same as if the user had entered 'breakpoint set -f my-file.c -l 137'.
|
|
|
|
Another example:
|
|
|
|
(lldb) command alias pltty process launch -s -o %1 -e %1
|
|
(lldb) pltty /dev/tty0
|
|
|
|
Interpreted as 'process launch -s -o /dev/tty0 -e /dev/tty0'
|
|
|
|
)"
|
|
"If the user always wanted to pass the same value to a particular option, the \
|
|
alias could be defined with that value directly in the alias as a constant, \
|
|
rather than using a positional placeholder:"
|
|
R"(
|
|
|
|
(lldb) command alias bl3 breakpoint set -f %1 -l 3
|
|
|
|
Always sets a breakpoint on line 3 of whatever file is indicated.)");
|
|
|
|
CommandArgumentEntry arg1;
|
|
CommandArgumentEntry arg2;
|
|
CommandArgumentEntry arg3;
|
|
CommandArgumentData alias_arg;
|
|
CommandArgumentData cmd_arg;
|
|
CommandArgumentData options_arg;
|
|
|
|
// Define the first (and only) variant of this arg.
|
|
alias_arg.arg_type = eArgTypeAliasName;
|
|
alias_arg.arg_repetition = eArgRepeatPlain;
|
|
|
|
// There is only one variant this argument could be; put it into the
|
|
// argument entry.
|
|
arg1.push_back(alias_arg);
|
|
|
|
// Define the first (and only) variant of this arg.
|
|
cmd_arg.arg_type = eArgTypeCommandName;
|
|
cmd_arg.arg_repetition = eArgRepeatPlain;
|
|
|
|
// There is only one variant this argument could be; put it into the
|
|
// argument entry.
|
|
arg2.push_back(cmd_arg);
|
|
|
|
// Define the first (and only) variant of this arg.
|
|
options_arg.arg_type = eArgTypeAliasOptions;
|
|
options_arg.arg_repetition = eArgRepeatOptional;
|
|
|
|
// There is only one variant this argument could be; put it into the
|
|
// argument entry.
|
|
arg3.push_back(options_arg);
|
|
|
|
// Push the data for the first argument into the m_arguments vector.
|
|
m_arguments.push_back(arg1);
|
|
m_arguments.push_back(arg2);
|
|
m_arguments.push_back(arg3);
|
|
}
|
|
|
|
~CommandObjectCommandsAlias() override = default;
|
|
|
|
protected:
|
|
void DoExecute(llvm::StringRef raw_command_line,
|
|
CommandReturnObject &result) override {
|
|
if (raw_command_line.empty()) {
|
|
result.AppendError("'command alias' requires at least two arguments");
|
|
return;
|
|
}
|
|
|
|
ExecutionContext exe_ctx = GetCommandInterpreter().GetExecutionContext();
|
|
m_option_group.NotifyOptionParsingStarting(&exe_ctx);
|
|
|
|
OptionsWithRaw args_with_suffix(raw_command_line);
|
|
|
|
if (args_with_suffix.HasArgs())
|
|
if (!ParseOptionsAndNotify(args_with_suffix.GetArgs(), result,
|
|
m_option_group, exe_ctx))
|
|
return;
|
|
|
|
llvm::StringRef raw_command_string = args_with_suffix.GetRawPart();
|
|
Args args(raw_command_string);
|
|
|
|
if (args.GetArgumentCount() < 2) {
|
|
result.AppendError("'command alias' requires at least two arguments");
|
|
return;
|
|
}
|
|
|
|
// Get the alias command.
|
|
|
|
auto alias_command = args[0].ref();
|
|
if (alias_command.starts_with("-")) {
|
|
result.AppendError("aliases starting with a dash are not supported");
|
|
if (alias_command == "--help" || alias_command == "--long-help") {
|
|
result.AppendWarning("if trying to pass options to 'command alias' add "
|
|
"a -- at the end of the options");
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Strip the new alias name off 'raw_command_string' (leave it on args,
|
|
// which gets passed to 'Execute', which does the stripping itself.
|
|
size_t pos = raw_command_string.find(alias_command);
|
|
if (pos == 0) {
|
|
raw_command_string = raw_command_string.substr(alias_command.size());
|
|
pos = raw_command_string.find_first_not_of(' ');
|
|
if ((pos != std::string::npos) && (pos > 0))
|
|
raw_command_string = raw_command_string.substr(pos);
|
|
} else {
|
|
result.AppendError("Error parsing command string. No alias created.");
|
|
return;
|
|
}
|
|
|
|
// Verify that the command is alias-able.
|
|
if (m_interpreter.CommandExists(alias_command)) {
|
|
result.AppendErrorWithFormat(
|
|
"'%s' is a permanent debugger command and cannot be redefined.\n",
|
|
args[0].c_str());
|
|
return;
|
|
}
|
|
|
|
if (m_interpreter.UserMultiwordCommandExists(alias_command)) {
|
|
result.AppendErrorWithFormat(
|
|
"'%s' is a user container command and cannot be overwritten.\n"
|
|
"Delete it first with 'command container delete'\n",
|
|
args[0].c_str());
|
|
return;
|
|
}
|
|
|
|
// Get CommandObject that is being aliased. The command name is read from
|
|
// the front of raw_command_string. raw_command_string is returned with the
|
|
// name of the command object stripped off the front.
|
|
llvm::StringRef original_raw_command_string = raw_command_string;
|
|
CommandObject *cmd_obj =
|
|
m_interpreter.GetCommandObjectForCommand(raw_command_string);
|
|
|
|
if (!cmd_obj) {
|
|
result.AppendErrorWithFormat("invalid command given to 'command alias'. "
|
|
"'%s' does not begin with a valid command."
|
|
" No alias created.",
|
|
original_raw_command_string.str().c_str());
|
|
} else if (!cmd_obj->WantsRawCommandString()) {
|
|
// Note that args was initialized with the original command, and has not
|
|
// been updated to this point. Therefore can we pass it to the version of
|
|
// Execute that does not need/expect raw input in the alias.
|
|
HandleAliasingNormalCommand(args, result);
|
|
} else {
|
|
HandleAliasingRawCommand(alias_command, raw_command_string, *cmd_obj,
|
|
result);
|
|
}
|
|
}
|
|
|
|
bool HandleAliasingRawCommand(llvm::StringRef alias_command,
|
|
llvm::StringRef raw_command_string,
|
|
CommandObject &cmd_obj,
|
|
CommandReturnObject &result) {
|
|
// Verify & handle any options/arguments passed to the alias command
|
|
|
|
OptionArgVectorSP option_arg_vector_sp =
|
|
OptionArgVectorSP(new OptionArgVector);
|
|
|
|
const bool include_aliases = true;
|
|
// Look up the command using command's name first. This is to resolve
|
|
// aliases when you are making nested aliases. But if you don't find
|
|
// it that way, then it wasn't an alias and we can just use the object
|
|
// we were passed in.
|
|
CommandObjectSP cmd_obj_sp = m_interpreter.GetCommandSPExact(
|
|
cmd_obj.GetCommandName(), include_aliases);
|
|
if (!cmd_obj_sp)
|
|
cmd_obj_sp = cmd_obj.shared_from_this();
|
|
|
|
if (m_interpreter.AliasExists(alias_command) ||
|
|
m_interpreter.UserCommandExists(alias_command)) {
|
|
result.AppendWarningWithFormat(
|
|
"Overwriting existing definition for '%s'.\n",
|
|
alias_command.str().c_str());
|
|
}
|
|
if (CommandAlias *alias = m_interpreter.AddAlias(
|
|
alias_command, cmd_obj_sp, raw_command_string)) {
|
|
if (m_command_options.m_help.OptionWasSet())
|
|
alias->SetHelp(m_command_options.m_help.GetCurrentValue());
|
|
if (m_command_options.m_long_help.OptionWasSet())
|
|
alias->SetHelpLong(m_command_options.m_long_help.GetCurrentValue());
|
|
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
|
} else {
|
|
result.AppendError("Unable to create requested alias.\n");
|
|
}
|
|
return result.Succeeded();
|
|
}
|
|
|
|
bool HandleAliasingNormalCommand(Args &args, CommandReturnObject &result) {
|
|
size_t argc = args.GetArgumentCount();
|
|
|
|
if (argc < 2) {
|
|
result.AppendError("'command alias' requires at least two arguments");
|
|
return false;
|
|
}
|
|
|
|
// Save these in std::strings since we're going to shift them off.
|
|
const std::string alias_command(std::string(args[0].ref()));
|
|
const std::string actual_command(std::string(args[1].ref()));
|
|
|
|
args.Shift(); // Shift the alias command word off the argument vector.
|
|
args.Shift(); // Shift the old command word off the argument vector.
|
|
|
|
// Verify that the command is alias'able, and get the appropriate command
|
|
// object.
|
|
|
|
if (m_interpreter.CommandExists(alias_command)) {
|
|
result.AppendErrorWithFormat(
|
|
"'%s' is a permanent debugger command and cannot be redefined.\n",
|
|
alias_command.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (m_interpreter.UserMultiwordCommandExists(alias_command)) {
|
|
result.AppendErrorWithFormat(
|
|
"'%s' is user container command and cannot be overwritten.\n"
|
|
"Delete it first with 'command container delete'",
|
|
alias_command.c_str());
|
|
return false;
|
|
}
|
|
|
|
CommandObjectSP command_obj_sp(
|
|
m_interpreter.GetCommandSPExact(actual_command, true));
|
|
CommandObjectSP subcommand_obj_sp;
|
|
bool use_subcommand = false;
|
|
if (!command_obj_sp) {
|
|
result.AppendErrorWithFormat("'%s' is not an existing command.\n",
|
|
actual_command.c_str());
|
|
return false;
|
|
}
|
|
CommandObject *cmd_obj = command_obj_sp.get();
|
|
CommandObject *sub_cmd_obj = nullptr;
|
|
OptionArgVectorSP option_arg_vector_sp =
|
|
OptionArgVectorSP(new OptionArgVector);
|
|
|
|
while (cmd_obj->IsMultiwordObject() && !args.empty()) {
|
|
auto sub_command = args[0].ref();
|
|
assert(!sub_command.empty());
|
|
subcommand_obj_sp = cmd_obj->GetSubcommandSP(sub_command);
|
|
if (!subcommand_obj_sp) {
|
|
result.AppendErrorWithFormat(
|
|
"'%s' is not a valid sub-command of '%s'. "
|
|
"Unable to create alias.\n",
|
|
args[0].c_str(), actual_command.c_str());
|
|
return false;
|
|
}
|
|
|
|
sub_cmd_obj = subcommand_obj_sp.get();
|
|
use_subcommand = true;
|
|
args.Shift(); // Shift the sub_command word off the argument vector.
|
|
cmd_obj = sub_cmd_obj;
|
|
}
|
|
|
|
// Verify & handle any options/arguments passed to the alias command
|
|
|
|
std::string args_string;
|
|
|
|
if (!args.empty()) {
|
|
CommandObjectSP tmp_sp =
|
|
m_interpreter.GetCommandSPExact(cmd_obj->GetCommandName());
|
|
if (use_subcommand)
|
|
tmp_sp = m_interpreter.GetCommandSPExact(sub_cmd_obj->GetCommandName());
|
|
|
|
args.GetCommandString(args_string);
|
|
}
|
|
|
|
if (m_interpreter.AliasExists(alias_command) ||
|
|
m_interpreter.UserCommandExists(alias_command)) {
|
|
result.AppendWarningWithFormat(
|
|
"Overwriting existing definition for '%s'.\n", alias_command.c_str());
|
|
}
|
|
|
|
if (CommandAlias *alias = m_interpreter.AddAlias(
|
|
alias_command, use_subcommand ? subcommand_obj_sp : command_obj_sp,
|
|
args_string)) {
|
|
if (m_command_options.m_help.OptionWasSet())
|
|
alias->SetHelp(m_command_options.m_help.GetCurrentValue());
|
|
if (m_command_options.m_long_help.OptionWasSet())
|
|
alias->SetHelpLong(m_command_options.m_long_help.GetCurrentValue());
|
|
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
|
} else {
|
|
result.AppendError("Unable to create requested alias.\n");
|
|
return false;
|
|
}
|
|
|
|
return result.Succeeded();
|
|
}
|
|
};
|
|
|
|
#pragma mark CommandObjectCommandsUnalias
|
|
// CommandObjectCommandsUnalias
|
|
|
|
class CommandObjectCommandsUnalias : public CommandObjectParsed {
|
|
public:
|
|
CommandObjectCommandsUnalias(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(
|
|
interpreter, "command unalias",
|
|
"Delete one or more custom commands defined by 'command alias'.",
|
|
nullptr) {
|
|
CommandArgumentEntry arg;
|
|
CommandArgumentData alias_arg;
|
|
|
|
// Define the first (and only) variant of this arg.
|
|
alias_arg.arg_type = eArgTypeAliasName;
|
|
alias_arg.arg_repetition = eArgRepeatPlain;
|
|
|
|
// There is only one variant this argument could be; put it into the
|
|
// argument entry.
|
|
arg.push_back(alias_arg);
|
|
|
|
// Push the data for the first argument into the m_arguments vector.
|
|
m_arguments.push_back(arg);
|
|
}
|
|
|
|
~CommandObjectCommandsUnalias() override = default;
|
|
|
|
void
|
|
HandleArgumentCompletion(CompletionRequest &request,
|
|
OptionElementVector &opt_element_vector) override {
|
|
if (!m_interpreter.HasCommands() || request.GetCursorIndex() != 0)
|
|
return;
|
|
|
|
for (const auto &ent : m_interpreter.GetAliases()) {
|
|
request.TryCompleteCurrentArg(ent.first, ent.second->GetHelp());
|
|
}
|
|
}
|
|
|
|
protected:
|
|
void DoExecute(Args &args, CommandReturnObject &result) override {
|
|
CommandObject::CommandMap::iterator pos;
|
|
CommandObject *cmd_obj;
|
|
|
|
if (args.empty()) {
|
|
result.AppendError("must call 'unalias' with a valid alias");
|
|
return;
|
|
}
|
|
|
|
auto command_name = args[0].ref();
|
|
cmd_obj = m_interpreter.GetCommandObject(command_name);
|
|
if (!cmd_obj) {
|
|
result.AppendErrorWithFormat(
|
|
"'%s' is not a known command.\nTry 'help' to see a "
|
|
"current list of commands.\n",
|
|
args[0].c_str());
|
|
return;
|
|
}
|
|
|
|
if (m_interpreter.CommandExists(command_name)) {
|
|
if (cmd_obj->IsRemovable()) {
|
|
result.AppendErrorWithFormat(
|
|
"'%s' is not an alias, it is a debugger command which can be "
|
|
"removed using the 'command delete' command.\n",
|
|
args[0].c_str());
|
|
} else {
|
|
result.AppendErrorWithFormat(
|
|
"'%s' is a permanent debugger command and cannot be removed.\n",
|
|
args[0].c_str());
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!m_interpreter.RemoveAlias(command_name)) {
|
|
if (m_interpreter.AliasExists(command_name))
|
|
result.AppendErrorWithFormat(
|
|
"Error occurred while attempting to unalias '%s'.\n",
|
|
args[0].c_str());
|
|
else
|
|
result.AppendErrorWithFormat("'%s' is not an existing alias.\n",
|
|
args[0].c_str());
|
|
return;
|
|
}
|
|
|
|
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
|
}
|
|
};
|
|
|
|
#pragma mark CommandObjectCommandsDelete
|
|
// CommandObjectCommandsDelete
|
|
|
|
class CommandObjectCommandsDelete : public CommandObjectParsed {
|
|
public:
|
|
CommandObjectCommandsDelete(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(
|
|
interpreter, "command delete",
|
|
"Delete one or more custom commands defined by 'command regex'.",
|
|
nullptr) {
|
|
CommandArgumentEntry arg;
|
|
CommandArgumentData alias_arg;
|
|
|
|
// Define the first (and only) variant of this arg.
|
|
alias_arg.arg_type = eArgTypeCommandName;
|
|
alias_arg.arg_repetition = eArgRepeatPlain;
|
|
|
|
// There is only one variant this argument could be; put it into the
|
|
// argument entry.
|
|
arg.push_back(alias_arg);
|
|
|
|
// Push the data for the first argument into the m_arguments vector.
|
|
m_arguments.push_back(arg);
|
|
}
|
|
|
|
~CommandObjectCommandsDelete() override = default;
|
|
|
|
void
|
|
HandleArgumentCompletion(CompletionRequest &request,
|
|
OptionElementVector &opt_element_vector) override {
|
|
if (!m_interpreter.HasCommands() || request.GetCursorIndex() != 0)
|
|
return;
|
|
|
|
for (const auto &ent : m_interpreter.GetCommands()) {
|
|
if (ent.second->IsRemovable())
|
|
request.TryCompleteCurrentArg(ent.first, ent.second->GetHelp());
|
|
}
|
|
}
|
|
|
|
protected:
|
|
void DoExecute(Args &args, CommandReturnObject &result) override {
|
|
CommandObject::CommandMap::iterator pos;
|
|
|
|
if (args.empty()) {
|
|
result.AppendErrorWithFormat("must call '%s' with one or more valid user "
|
|
"defined regular expression command names",
|
|
GetCommandName().str().c_str());
|
|
return;
|
|
}
|
|
|
|
auto command_name = args[0].ref();
|
|
if (!m_interpreter.CommandExists(command_name)) {
|
|
StreamString error_msg_stream;
|
|
const bool generate_upropos = true;
|
|
const bool generate_type_lookup = false;
|
|
CommandObjectHelp::GenerateAdditionalHelpAvenuesMessage(
|
|
&error_msg_stream, command_name, llvm::StringRef(), llvm::StringRef(),
|
|
generate_upropos, generate_type_lookup);
|
|
result.AppendError(error_msg_stream.GetString());
|
|
return;
|
|
}
|
|
|
|
if (!m_interpreter.RemoveCommand(command_name)) {
|
|
result.AppendErrorWithFormat(
|
|
"'%s' is a permanent debugger command and cannot be removed.\n",
|
|
args[0].c_str());
|
|
return;
|
|
}
|
|
|
|
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
|
}
|
|
};
|
|
|
|
// CommandObjectCommandsAddRegex
|
|
|
|
#define LLDB_OPTIONS_regex
|
|
#include "CommandOptions.inc"
|
|
|
|
#pragma mark CommandObjectCommandsAddRegex
|
|
|
|
class CommandObjectCommandsAddRegex : public CommandObjectParsed,
|
|
public IOHandlerDelegateMultiline {
|
|
public:
|
|
CommandObjectCommandsAddRegex(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(
|
|
interpreter, "command regex",
|
|
"Define a custom command in terms of "
|
|
"existing commands by matching "
|
|
"regular expressions.",
|
|
"command regex <cmd-name> [s/<regex>/<subst>/ ...]"),
|
|
IOHandlerDelegateMultiline("",
|
|
IOHandlerDelegate::Completion::LLDBCommand) {
|
|
SetHelpLong(
|
|
R"(
|
|
)"
|
|
"This command allows the user to create powerful regular expression commands \
|
|
with substitutions. The regular expressions and substitutions are specified \
|
|
using the regular expression substitution format of:"
|
|
R"(
|
|
|
|
s/<regex>/<subst>/
|
|
|
|
)"
|
|
"<regex> is a regular expression that can use parenthesis to capture regular \
|
|
expression input and substitute the captured matches in the output using %1 \
|
|
for the first match, %2 for the second, and so on."
|
|
R"(
|
|
|
|
)"
|
|
"The regular expressions can all be specified on the command line if more than \
|
|
one argument is provided. If just the command name is provided on the command \
|
|
line, then the regular expressions and substitutions can be entered on separate \
|
|
lines, followed by an empty line to terminate the command definition."
|
|
R"(
|
|
|
|
EXAMPLES
|
|
|
|
)"
|
|
"The following example will define a regular expression command named 'f' that \
|
|
will call 'finish' if there are no arguments, or 'frame select <frame-idx>' if \
|
|
a number follows 'f':"
|
|
R"(
|
|
|
|
(lldb) command regex f s/^$/finish/ 's/([0-9]+)/frame select %1/')");
|
|
CommandArgumentData thread_arg{eArgTypeSEDStylePair, eArgRepeatOptional};
|
|
m_arguments.push_back({thread_arg});
|
|
}
|
|
|
|
~CommandObjectCommandsAddRegex() override = default;
|
|
|
|
protected:
|
|
void IOHandlerActivated(IOHandler &io_handler, bool interactive) override {
|
|
StreamFileSP output_sp(io_handler.GetOutputStreamFileSP());
|
|
if (output_sp && interactive) {
|
|
output_sp->PutCString("Enter one or more sed substitution commands in "
|
|
"the form: 's/<regex>/<subst>/'.\nTerminate the "
|
|
"substitution list with an empty line.\n");
|
|
output_sp->Flush();
|
|
}
|
|
}
|
|
|
|
void IOHandlerInputComplete(IOHandler &io_handler,
|
|
std::string &data) override {
|
|
io_handler.SetIsDone(true);
|
|
if (m_regex_cmd_up) {
|
|
StringList lines;
|
|
if (lines.SplitIntoLines(data)) {
|
|
bool check_only = false;
|
|
for (const std::string &line : lines) {
|
|
Status error = AppendRegexSubstitution(line, check_only);
|
|
if (error.Fail()) {
|
|
if (!GetDebugger().GetCommandInterpreter().GetBatchCommandMode()) {
|
|
StreamSP out_stream = GetDebugger().GetAsyncOutputStream();
|
|
out_stream->Printf("error: %s\n", error.AsCString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (m_regex_cmd_up->HasRegexEntries()) {
|
|
CommandObjectSP cmd_sp(m_regex_cmd_up.release());
|
|
m_interpreter.AddCommand(cmd_sp->GetCommandName(), cmd_sp, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DoExecute(Args &command, CommandReturnObject &result) override {
|
|
const size_t argc = command.GetArgumentCount();
|
|
if (argc == 0) {
|
|
result.AppendError("usage: 'command regex <command-name> "
|
|
"[s/<regex1>/<subst1>/ s/<regex2>/<subst2>/ ...]'\n");
|
|
return;
|
|
}
|
|
|
|
Status error;
|
|
auto name = command[0].ref();
|
|
m_regex_cmd_up = std::make_unique<CommandObjectRegexCommand>(
|
|
m_interpreter, name, m_options.GetHelp(), m_options.GetSyntax(), 0,
|
|
true);
|
|
|
|
if (argc == 1) {
|
|
Debugger &debugger = GetDebugger();
|
|
bool color_prompt = debugger.GetUseColor();
|
|
const bool multiple_lines = true; // Get multiple lines
|
|
IOHandlerSP io_handler_sp(new IOHandlerEditline(
|
|
debugger, IOHandler::Type::Other,
|
|
"lldb-regex", // Name of input reader for history
|
|
llvm::StringRef("> "), // Prompt
|
|
llvm::StringRef(), // Continuation prompt
|
|
multiple_lines, color_prompt,
|
|
0, // Don't show line numbers
|
|
*this));
|
|
|
|
if (io_handler_sp) {
|
|
debugger.RunIOHandlerAsync(io_handler_sp);
|
|
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
|
}
|
|
} else {
|
|
for (auto &entry : command.entries().drop_front()) {
|
|
bool check_only = false;
|
|
error = AppendRegexSubstitution(entry.ref(), check_only);
|
|
if (error.Fail())
|
|
break;
|
|
}
|
|
|
|
if (error.Success()) {
|
|
AddRegexCommandToInterpreter();
|
|
}
|
|
}
|
|
if (error.Fail()) {
|
|
result.AppendError(error.AsCString());
|
|
}
|
|
}
|
|
|
|
Status AppendRegexSubstitution(const llvm::StringRef ®ex_sed,
|
|
bool check_only) {
|
|
Status error;
|
|
|
|
if (!m_regex_cmd_up) {
|
|
error.SetErrorStringWithFormat(
|
|
"invalid regular expression command object for: '%.*s'",
|
|
(int)regex_sed.size(), regex_sed.data());
|
|
return error;
|
|
}
|
|
|
|
size_t regex_sed_size = regex_sed.size();
|
|
|
|
if (regex_sed_size <= 1) {
|
|
error.SetErrorStringWithFormat(
|
|
"regular expression substitution string is too short: '%.*s'",
|
|
(int)regex_sed.size(), regex_sed.data());
|
|
return error;
|
|
}
|
|
|
|
if (regex_sed[0] != 's') {
|
|
error.SetErrorStringWithFormat("regular expression substitution string "
|
|
"doesn't start with 's': '%.*s'",
|
|
(int)regex_sed.size(), regex_sed.data());
|
|
return error;
|
|
}
|
|
const size_t first_separator_char_pos = 1;
|
|
// use the char that follows 's' as the regex separator character so we can
|
|
// have "s/<regex>/<subst>/" or "s|<regex>|<subst>|"
|
|
const char separator_char = regex_sed[first_separator_char_pos];
|
|
const size_t second_separator_char_pos =
|
|
regex_sed.find(separator_char, first_separator_char_pos + 1);
|
|
|
|
if (second_separator_char_pos == std::string::npos) {
|
|
error.SetErrorStringWithFormat(
|
|
"missing second '%c' separator char after '%.*s' in '%.*s'",
|
|
separator_char,
|
|
(int)(regex_sed.size() - first_separator_char_pos - 1),
|
|
regex_sed.data() + (first_separator_char_pos + 1),
|
|
(int)regex_sed.size(), regex_sed.data());
|
|
return error;
|
|
}
|
|
|
|
const size_t third_separator_char_pos =
|
|
regex_sed.find(separator_char, second_separator_char_pos + 1);
|
|
|
|
if (third_separator_char_pos == std::string::npos) {
|
|
error.SetErrorStringWithFormat(
|
|
"missing third '%c' separator char after '%.*s' in '%.*s'",
|
|
separator_char,
|
|
(int)(regex_sed.size() - second_separator_char_pos - 1),
|
|
regex_sed.data() + (second_separator_char_pos + 1),
|
|
(int)regex_sed.size(), regex_sed.data());
|
|
return error;
|
|
}
|
|
|
|
if (third_separator_char_pos != regex_sed_size - 1) {
|
|
// Make sure that everything that follows the last regex separator char
|
|
if (regex_sed.find_first_not_of("\t\n\v\f\r ",
|
|
third_separator_char_pos + 1) !=
|
|
std::string::npos) {
|
|
error.SetErrorStringWithFormat(
|
|
"extra data found after the '%.*s' regular expression substitution "
|
|
"string: '%.*s'",
|
|
(int)third_separator_char_pos + 1, regex_sed.data(),
|
|
(int)(regex_sed.size() - third_separator_char_pos - 1),
|
|
regex_sed.data() + (third_separator_char_pos + 1));
|
|
return error;
|
|
}
|
|
} else if (first_separator_char_pos + 1 == second_separator_char_pos) {
|
|
error.SetErrorStringWithFormat(
|
|
"<regex> can't be empty in 's%c<regex>%c<subst>%c' string: '%.*s'",
|
|
separator_char, separator_char, separator_char, (int)regex_sed.size(),
|
|
regex_sed.data());
|
|
return error;
|
|
} else if (second_separator_char_pos + 1 == third_separator_char_pos) {
|
|
error.SetErrorStringWithFormat(
|
|
"<subst> can't be empty in 's%c<regex>%c<subst>%c' string: '%.*s'",
|
|
separator_char, separator_char, separator_char, (int)regex_sed.size(),
|
|
regex_sed.data());
|
|
return error;
|
|
}
|
|
|
|
if (!check_only) {
|
|
std::string regex(std::string(regex_sed.substr(
|
|
first_separator_char_pos + 1,
|
|
second_separator_char_pos - first_separator_char_pos - 1)));
|
|
std::string subst(std::string(regex_sed.substr(
|
|
second_separator_char_pos + 1,
|
|
third_separator_char_pos - second_separator_char_pos - 1)));
|
|
m_regex_cmd_up->AddRegexCommand(regex, subst);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
void AddRegexCommandToInterpreter() {
|
|
if (m_regex_cmd_up) {
|
|
if (m_regex_cmd_up->HasRegexEntries()) {
|
|
CommandObjectSP cmd_sp(m_regex_cmd_up.release());
|
|
m_interpreter.AddCommand(cmd_sp->GetCommandName(), cmd_sp, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
std::unique_ptr<CommandObjectRegexCommand> m_regex_cmd_up;
|
|
|
|
class CommandOptions : public Options {
|
|
public:
|
|
CommandOptions() = default;
|
|
|
|
~CommandOptions() override = default;
|
|
|
|
Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
|
|
ExecutionContext *execution_context) override {
|
|
Status error;
|
|
const int short_option = m_getopt_table[option_idx].val;
|
|
|
|
switch (short_option) {
|
|
case 'h':
|
|
m_help.assign(std::string(option_arg));
|
|
break;
|
|
case 's':
|
|
m_syntax.assign(std::string(option_arg));
|
|
break;
|
|
default:
|
|
llvm_unreachable("Unimplemented option");
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
void OptionParsingStarting(ExecutionContext *execution_context) override {
|
|
m_help.clear();
|
|
m_syntax.clear();
|
|
}
|
|
|
|
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
|
|
return llvm::ArrayRef(g_regex_options);
|
|
}
|
|
|
|
llvm::StringRef GetHelp() { return m_help; }
|
|
|
|
llvm::StringRef GetSyntax() { return m_syntax; }
|
|
|
|
protected:
|
|
// Instance variables to hold the values for command options.
|
|
|
|
std::string m_help;
|
|
std::string m_syntax;
|
|
};
|
|
|
|
Options *GetOptions() override { return &m_options; }
|
|
|
|
CommandOptions m_options;
|
|
};
|
|
|
|
class CommandObjectPythonFunction : public CommandObjectRaw {
|
|
public:
|
|
CommandObjectPythonFunction(CommandInterpreter &interpreter, std::string name,
|
|
std::string funct, std::string help,
|
|
ScriptedCommandSynchronicity synch,
|
|
CompletionType completion_type)
|
|
: CommandObjectRaw(interpreter, name), m_function_name(funct),
|
|
m_synchro(synch), m_completion_type(completion_type) {
|
|
if (!help.empty())
|
|
SetHelp(help);
|
|
else {
|
|
StreamString stream;
|
|
stream.Printf("For more information run 'help %s'", name.c_str());
|
|
SetHelp(stream.GetString());
|
|
}
|
|
}
|
|
|
|
~CommandObjectPythonFunction() override = default;
|
|
|
|
bool IsRemovable() const override { return true; }
|
|
|
|
const std::string &GetFunctionName() { return m_function_name; }
|
|
|
|
ScriptedCommandSynchronicity GetSynchronicity() { return m_synchro; }
|
|
|
|
llvm::StringRef GetHelpLong() override {
|
|
if (m_fetched_help_long)
|
|
return CommandObjectRaw::GetHelpLong();
|
|
|
|
ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
|
|
if (!scripter)
|
|
return CommandObjectRaw::GetHelpLong();
|
|
|
|
std::string docstring;
|
|
m_fetched_help_long =
|
|
scripter->GetDocumentationForItem(m_function_name.c_str(), docstring);
|
|
if (!docstring.empty())
|
|
SetHelpLong(docstring);
|
|
return CommandObjectRaw::GetHelpLong();
|
|
}
|
|
|
|
void
|
|
HandleArgumentCompletion(CompletionRequest &request,
|
|
OptionElementVector &opt_element_vector) override {
|
|
lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks(
|
|
GetCommandInterpreter(), m_completion_type, request, nullptr);
|
|
}
|
|
|
|
bool WantsCompletion() override { return true; }
|
|
|
|
protected:
|
|
void DoExecute(llvm::StringRef raw_command_line,
|
|
CommandReturnObject &result) override {
|
|
ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
|
|
|
|
m_interpreter.IncreaseCommandUsage(*this);
|
|
|
|
Status error;
|
|
|
|
result.SetStatus(eReturnStatusInvalid);
|
|
|
|
if (!scripter || !scripter->RunScriptBasedCommand(
|
|
m_function_name.c_str(), raw_command_line, m_synchro,
|
|
result, error, m_exe_ctx)) {
|
|
result.AppendError(error.AsCString());
|
|
} else {
|
|
// Don't change the status if the command already set it...
|
|
if (result.GetStatus() == eReturnStatusInvalid) {
|
|
if (result.GetOutputData().empty())
|
|
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
|
else
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
std::string m_function_name;
|
|
ScriptedCommandSynchronicity m_synchro;
|
|
bool m_fetched_help_long = false;
|
|
CompletionType m_completion_type = eNoCompletion;
|
|
};
|
|
|
|
/// This class implements a "raw" scripted command. lldb does no parsing of the
|
|
/// command line, instead passing the line unaltered (except for backtick
|
|
/// substitution).
|
|
class CommandObjectScriptingObjectRaw : public CommandObjectRaw {
|
|
public:
|
|
CommandObjectScriptingObjectRaw(CommandInterpreter &interpreter,
|
|
std::string name,
|
|
StructuredData::GenericSP cmd_obj_sp,
|
|
ScriptedCommandSynchronicity synch,
|
|
CompletionType completion_type)
|
|
: CommandObjectRaw(interpreter, name), m_cmd_obj_sp(cmd_obj_sp),
|
|
m_synchro(synch), m_fetched_help_short(false),
|
|
m_fetched_help_long(false), m_completion_type(completion_type) {
|
|
StreamString stream;
|
|
stream.Printf("For more information run 'help %s'", name.c_str());
|
|
SetHelp(stream.GetString());
|
|
if (ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter())
|
|
GetFlags().Set(scripter->GetFlagsForCommandObject(cmd_obj_sp));
|
|
}
|
|
|
|
~CommandObjectScriptingObjectRaw() override = default;
|
|
|
|
void
|
|
HandleArgumentCompletion(CompletionRequest &request,
|
|
OptionElementVector &opt_element_vector) override {
|
|
lldb_private::CommandCompletions::InvokeCommonCompletionCallbacks(
|
|
GetCommandInterpreter(), m_completion_type, request, nullptr);
|
|
}
|
|
|
|
bool WantsCompletion() override { return true; }
|
|
|
|
bool IsRemovable() const override { return true; }
|
|
|
|
ScriptedCommandSynchronicity GetSynchronicity() { return m_synchro; }
|
|
|
|
llvm::StringRef GetHelp() override {
|
|
if (m_fetched_help_short)
|
|
return CommandObjectRaw::GetHelp();
|
|
ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
|
|
if (!scripter)
|
|
return CommandObjectRaw::GetHelp();
|
|
std::string docstring;
|
|
m_fetched_help_short =
|
|
scripter->GetShortHelpForCommandObject(m_cmd_obj_sp, docstring);
|
|
if (!docstring.empty())
|
|
SetHelp(docstring);
|
|
|
|
return CommandObjectRaw::GetHelp();
|
|
}
|
|
|
|
llvm::StringRef GetHelpLong() override {
|
|
if (m_fetched_help_long)
|
|
return CommandObjectRaw::GetHelpLong();
|
|
|
|
ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
|
|
if (!scripter)
|
|
return CommandObjectRaw::GetHelpLong();
|
|
|
|
std::string docstring;
|
|
m_fetched_help_long =
|
|
scripter->GetLongHelpForCommandObject(m_cmd_obj_sp, docstring);
|
|
if (!docstring.empty())
|
|
SetHelpLong(docstring);
|
|
return CommandObjectRaw::GetHelpLong();
|
|
}
|
|
|
|
protected:
|
|
void DoExecute(llvm::StringRef raw_command_line,
|
|
CommandReturnObject &result) override {
|
|
ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
|
|
|
|
Status error;
|
|
|
|
result.SetStatus(eReturnStatusInvalid);
|
|
|
|
if (!scripter ||
|
|
!scripter->RunScriptBasedCommand(m_cmd_obj_sp, raw_command_line,
|
|
m_synchro, result, error, m_exe_ctx)) {
|
|
result.AppendError(error.AsCString());
|
|
} else {
|
|
// Don't change the status if the command already set it...
|
|
if (result.GetStatus() == eReturnStatusInvalid) {
|
|
if (result.GetOutputData().empty())
|
|
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
|
else
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
StructuredData::GenericSP m_cmd_obj_sp;
|
|
ScriptedCommandSynchronicity m_synchro;
|
|
bool m_fetched_help_short : 1;
|
|
bool m_fetched_help_long : 1;
|
|
CompletionType m_completion_type = eNoCompletion;
|
|
};
|
|
|
|
|
|
/// This command implements a lldb parsed scripted command. The command
|
|
/// provides a definition of the options and arguments, and a option value
|
|
/// setting callback, and then the command's execution function gets passed
|
|
/// just the parsed arguments.
|
|
/// Note, implementing a command in Python using these base interfaces is a bit
|
|
/// of a pain, but it is much easier to export this low level interface, and
|
|
/// then make it nicer on the Python side, than to try to do that in a
|
|
/// script language neutral way.
|
|
/// So I've also added a base class in Python that provides a table-driven
|
|
/// way of defining the options and arguments, which automatically fills the
|
|
/// option values, making them available as properties in Python.
|
|
///
|
|
class CommandObjectScriptingObjectParsed : public CommandObjectParsed {
|
|
private:
|
|
class CommandOptions : public Options {
|
|
public:
|
|
CommandOptions(CommandInterpreter &interpreter,
|
|
StructuredData::GenericSP cmd_obj_sp) : m_interpreter(interpreter),
|
|
m_cmd_obj_sp(cmd_obj_sp) {}
|
|
|
|
~CommandOptions() override = default;
|
|
|
|
Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
|
|
ExecutionContext *execution_context) override {
|
|
Status error;
|
|
ScriptInterpreter *scripter =
|
|
m_interpreter.GetDebugger().GetScriptInterpreter();
|
|
if (!scripter) {
|
|
error.SetErrorString("No script interpreter for SetOptionValue.");
|
|
return error;
|
|
}
|
|
if (!m_cmd_obj_sp) {
|
|
error.SetErrorString("SetOptionValue called with empty cmd_obj.");
|
|
return error;
|
|
}
|
|
if (!m_options_definition_up) {
|
|
error.SetErrorString("SetOptionValue called before options definitions "
|
|
"were created.");
|
|
return error;
|
|
}
|
|
// Pass the long option, since you aren't actually required to have a
|
|
// short_option, and for those options the index or short option character
|
|
// aren't meaningful on the python side.
|
|
const char * long_option =
|
|
m_options_definition_up.get()[option_idx].long_option;
|
|
bool success = scripter->SetOptionValueForCommandObject(m_cmd_obj_sp,
|
|
execution_context, long_option, option_arg);
|
|
if (!success)
|
|
error.SetErrorStringWithFormatv("Error setting option: {0} to {1}",
|
|
long_option, option_arg);
|
|
return error;
|
|
}
|
|
|
|
void OptionParsingStarting(ExecutionContext *execution_context) override {
|
|
ScriptInterpreter *scripter =
|
|
m_interpreter.GetDebugger().GetScriptInterpreter();
|
|
if (!scripter || !m_cmd_obj_sp)
|
|
return;
|
|
|
|
scripter->OptionParsingStartedForCommandObject(m_cmd_obj_sp);
|
|
}
|
|
|
|
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
|
|
if (!m_options_definition_up)
|
|
return {};
|
|
return llvm::ArrayRef(m_options_definition_up.get(), m_num_options);
|
|
}
|
|
|
|
static Status ParseUsageMaskFromArray(StructuredData::ObjectSP obj_sp,
|
|
size_t counter, uint32_t &usage_mask) {
|
|
// If the usage entry is not provided, we use LLDB_OPT_SET_ALL.
|
|
// If the usage mask is a UINT, the option belongs to that group.
|
|
// If the usage mask is a vector of UINT's, the option belongs to all the
|
|
// groups listed.
|
|
// If a subelement of the vector is a vector of two ints, then the option
|
|
// belongs to the inclusive range from the first to the second element.
|
|
Status error;
|
|
if (!obj_sp) {
|
|
usage_mask = LLDB_OPT_SET_ALL;
|
|
return error;
|
|
}
|
|
|
|
usage_mask = 0;
|
|
|
|
StructuredData::UnsignedInteger *uint_val =
|
|
obj_sp->GetAsUnsignedInteger();
|
|
if (uint_val) {
|
|
// If this is an integer, then this specifies a single group:
|
|
uint32_t value = uint_val->GetValue();
|
|
if (value == 0) {
|
|
error.SetErrorStringWithFormatv(
|
|
"0 is not a valid group for option {0}", counter);
|
|
return error;
|
|
}
|
|
usage_mask = (1 << (value - 1));
|
|
return error;
|
|
}
|
|
// Otherwise it has to be an array:
|
|
StructuredData::Array *array_val = obj_sp->GetAsArray();
|
|
if (!array_val) {
|
|
error.SetErrorStringWithFormatv(
|
|
"required field is not a array for option {0}", counter);
|
|
return error;
|
|
}
|
|
// This is the array ForEach for accumulating a group usage mask from
|
|
// an array of string descriptions of groups.
|
|
auto groups_accumulator
|
|
= [counter, &usage_mask, &error]
|
|
(StructuredData::Object *obj) -> bool {
|
|
StructuredData::UnsignedInteger *int_val = obj->GetAsUnsignedInteger();
|
|
if (int_val) {
|
|
uint32_t value = int_val->GetValue();
|
|
if (value == 0) {
|
|
error.SetErrorStringWithFormatv(
|
|
"0 is not a valid group for element {0}", counter);
|
|
return false;
|
|
}
|
|
usage_mask |= (1 << (value - 1));
|
|
return true;
|
|
}
|
|
StructuredData::Array *arr_val = obj->GetAsArray();
|
|
if (!arr_val) {
|
|
error.SetErrorStringWithFormatv(
|
|
"Group element not an int or array of integers for element {0}",
|
|
counter);
|
|
return false;
|
|
}
|
|
size_t num_range_elem = arr_val->GetSize();
|
|
if (num_range_elem != 2) {
|
|
error.SetErrorStringWithFormatv(
|
|
"Subranges of a group not a start and a stop for element {0}",
|
|
counter);
|
|
return false;
|
|
}
|
|
int_val = arr_val->GetItemAtIndex(0)->GetAsUnsignedInteger();
|
|
if (!int_val) {
|
|
error.SetErrorStringWithFormatv("Start element of a subrange of a "
|
|
"group not unsigned int for element {0}", counter);
|
|
return false;
|
|
}
|
|
uint32_t start = int_val->GetValue();
|
|
int_val = arr_val->GetItemAtIndex(1)->GetAsUnsignedInteger();
|
|
if (!int_val) {
|
|
error.SetErrorStringWithFormatv("End element of a subrange of a group"
|
|
" not unsigned int for element {0}", counter);
|
|
return false;
|
|
}
|
|
uint32_t end = int_val->GetValue();
|
|
if (start == 0 || end == 0 || start > end) {
|
|
error.SetErrorStringWithFormatv("Invalid subrange of a group: {0} - "
|
|
"{1} for element {2}", start, end, counter);
|
|
return false;
|
|
}
|
|
for (uint32_t i = start; i <= end; i++) {
|
|
usage_mask |= (1 << (i - 1));
|
|
}
|
|
return true;
|
|
};
|
|
array_val->ForEach(groups_accumulator);
|
|
return error;
|
|
}
|
|
|
|
|
|
Status SetOptionsFromArray(StructuredData::Dictionary &options) {
|
|
Status error;
|
|
m_num_options = options.GetSize();
|
|
m_options_definition_up.reset(new OptionDefinition[m_num_options]);
|
|
// We need to hand out pointers to contents of these vectors; we reserve
|
|
// as much as we'll need up front so they don't get freed on resize...
|
|
m_usage_container.resize(m_num_options);
|
|
m_enum_storage.resize(m_num_options);
|
|
m_enum_vector.resize(m_num_options);
|
|
|
|
size_t counter = 0;
|
|
size_t short_opt_counter = 0;
|
|
// This is the Array::ForEach function for adding option elements:
|
|
auto add_element = [this, &error, &counter, &short_opt_counter]
|
|
(llvm::StringRef long_option, StructuredData::Object *object) -> bool {
|
|
StructuredData::Dictionary *opt_dict = object->GetAsDictionary();
|
|
if (!opt_dict) {
|
|
error.SetErrorString("Value in options dictionary is not a dictionary");
|
|
return false;
|
|
}
|
|
OptionDefinition &option_def = m_options_definition_up.get()[counter];
|
|
|
|
// We aren't exposing the validator yet, set it to null
|
|
option_def.validator = nullptr;
|
|
// We don't require usage masks, so set it to one group by default:
|
|
option_def.usage_mask = 1;
|
|
|
|
// Now set the fields of the OptionDefinition Array from the dictionary:
|
|
//
|
|
// Note that I don't check for unknown fields in the option dictionaries
|
|
// so a scriptor can add extra elements that are helpful when they go to
|
|
// do "set_option_value"
|
|
|
|
// Usage Mask:
|
|
StructuredData::ObjectSP obj_sp = opt_dict->GetValueForKey("groups");
|
|
if (obj_sp) {
|
|
error = ParseUsageMaskFromArray(obj_sp, counter,
|
|
option_def.usage_mask);
|
|
if (error.Fail())
|
|
return false;
|
|
}
|
|
|
|
// Required:
|
|
option_def.required = false;
|
|
obj_sp = opt_dict->GetValueForKey("required");
|
|
if (obj_sp) {
|
|
StructuredData::Boolean *boolean_val = obj_sp->GetAsBoolean();
|
|
if (!boolean_val) {
|
|
error.SetErrorStringWithFormatv("'required' field is not a boolean "
|
|
"for option {0}", counter);
|
|
return false;
|
|
}
|
|
option_def.required = boolean_val->GetValue();
|
|
}
|
|
|
|
// Short Option:
|
|
int short_option;
|
|
obj_sp = opt_dict->GetValueForKey("short_option");
|
|
if (obj_sp) {
|
|
// The value is a string, so pull the
|
|
llvm::StringRef short_str = obj_sp->GetStringValue();
|
|
if (short_str.empty()) {
|
|
error.SetErrorStringWithFormatv("short_option field empty for "
|
|
"option {0}", counter);
|
|
return false;
|
|
} else if (short_str.size() != 1) {
|
|
error.SetErrorStringWithFormatv("short_option field has extra "
|
|
"characters for option {0}", counter);
|
|
return false;
|
|
}
|
|
short_option = (int) short_str[0];
|
|
} else {
|
|
// If the short option is not provided, then we need a unique value
|
|
// less than the lowest printable ASCII character.
|
|
short_option = short_opt_counter++;
|
|
}
|
|
option_def.short_option = short_option;
|
|
|
|
// Long Option is the key from the outer dict:
|
|
if (long_option.empty()) {
|
|
error.SetErrorStringWithFormatv("empty long_option for option {0}",
|
|
counter);
|
|
return false;
|
|
}
|
|
auto inserted = g_string_storer.insert(long_option.str());
|
|
option_def.long_option = ((*(inserted.first)).data());
|
|
|
|
// Value Type:
|
|
obj_sp = opt_dict->GetValueForKey("value_type");
|
|
if (obj_sp) {
|
|
StructuredData::UnsignedInteger *uint_val
|
|
= obj_sp->GetAsUnsignedInteger();
|
|
if (!uint_val) {
|
|
error.SetErrorStringWithFormatv("Value type must be an unsigned "
|
|
"integer");
|
|
return false;
|
|
}
|
|
uint64_t val_type = uint_val->GetValue();
|
|
if (val_type >= eArgTypeLastArg) {
|
|
error.SetErrorStringWithFormatv("Value type {0} beyond the "
|
|
"CommandArgumentType bounds", val_type);
|
|
return false;
|
|
}
|
|
option_def.argument_type = (CommandArgumentType) val_type;
|
|
option_def.option_has_arg = true;
|
|
} else {
|
|
option_def.argument_type = eArgTypeNone;
|
|
option_def.option_has_arg = false;
|
|
}
|
|
|
|
// Completion Type:
|
|
obj_sp = opt_dict->GetValueForKey("completion_type");
|
|
if (obj_sp) {
|
|
StructuredData::UnsignedInteger *uint_val = obj_sp->GetAsUnsignedInteger();
|
|
if (!uint_val) {
|
|
error.SetErrorStringWithFormatv("Completion type must be an "
|
|
"unsigned integer for option {0}", counter);
|
|
return false;
|
|
}
|
|
uint64_t completion_type = uint_val->GetValue();
|
|
if (completion_type > eCustomCompletion) {
|
|
error.SetErrorStringWithFormatv("Completion type for option {0} "
|
|
"beyond the CompletionType bounds", completion_type);
|
|
return false;
|
|
}
|
|
option_def.completion_type = (CommandArgumentType) completion_type;
|
|
} else
|
|
option_def.completion_type = eNoCompletion;
|
|
|
|
// Usage Text:
|
|
std::string usage_text;
|
|
obj_sp = opt_dict->GetValueForKey("help");
|
|
if (!obj_sp) {
|
|
error.SetErrorStringWithFormatv("required usage missing from option "
|
|
"{0}", counter);
|
|
return false;
|
|
}
|
|
llvm::StringRef usage_stref;
|
|
usage_stref = obj_sp->GetStringValue();
|
|
if (usage_stref.empty()) {
|
|
error.SetErrorStringWithFormatv("empty usage text for option {0}",
|
|
counter);
|
|
return false;
|
|
}
|
|
m_usage_container[counter] = usage_stref.str().c_str();
|
|
option_def.usage_text = m_usage_container[counter].data();
|
|
|
|
// Enum Values:
|
|
|
|
obj_sp = opt_dict->GetValueForKey("enum_values");
|
|
if (obj_sp) {
|
|
StructuredData::Array *array = obj_sp->GetAsArray();
|
|
if (!array) {
|
|
error.SetErrorStringWithFormatv("enum values must be an array for "
|
|
"option {0}", counter);
|
|
return false;
|
|
}
|
|
size_t num_elem = array->GetSize();
|
|
size_t enum_ctr = 0;
|
|
m_enum_storage[counter] = std::vector<EnumValueStorage>(num_elem);
|
|
std::vector<EnumValueStorage> &curr_elem = m_enum_storage[counter];
|
|
|
|
// This is the Array::ForEach function for adding enum elements:
|
|
// Since there are only two fields to specify the enum, use a simple
|
|
// two element array with value first, usage second.
|
|
// counter is only used for reporting so I pass it by value here.
|
|
auto add_enum = [&enum_ctr, &curr_elem, counter, &error]
|
|
(StructuredData::Object *object) -> bool {
|
|
StructuredData::Array *enum_arr = object->GetAsArray();
|
|
if (!enum_arr) {
|
|
error.SetErrorStringWithFormatv("Enum values for option {0} not "
|
|
"an array", counter);
|
|
return false;
|
|
}
|
|
size_t num_enum_elements = enum_arr->GetSize();
|
|
if (num_enum_elements != 2) {
|
|
error.SetErrorStringWithFormatv("Wrong number of elements: {0} "
|
|
"for enum {1} in option {2}",
|
|
num_enum_elements, enum_ctr, counter);
|
|
return false;
|
|
}
|
|
// Enum Value:
|
|
StructuredData::ObjectSP obj_sp = enum_arr->GetItemAtIndex(0);
|
|
llvm::StringRef val_stref = obj_sp->GetStringValue();
|
|
std::string value_cstr_str = val_stref.str().c_str();
|
|
|
|
// Enum Usage:
|
|
obj_sp = enum_arr->GetItemAtIndex(1);
|
|
if (!obj_sp) {
|
|
error.SetErrorStringWithFormatv("No usage for enum {0} in option "
|
|
"{1}", enum_ctr, counter);
|
|
return false;
|
|
}
|
|
llvm::StringRef usage_stref = obj_sp->GetStringValue();
|
|
std::string usage_cstr_str = usage_stref.str().c_str();
|
|
curr_elem[enum_ctr] = EnumValueStorage(value_cstr_str,
|
|
usage_cstr_str, enum_ctr);
|
|
|
|
enum_ctr++;
|
|
return true;
|
|
}; // end of add_enum
|
|
|
|
array->ForEach(add_enum);
|
|
if (!error.Success())
|
|
return false;
|
|
// We have to have a vector of elements to set in the options, make
|
|
// that here:
|
|
for (auto &elem : curr_elem)
|
|
m_enum_vector[counter].emplace_back(elem.element);
|
|
|
|
option_def.enum_values = llvm::ArrayRef(m_enum_vector[counter]);
|
|
}
|
|
counter++;
|
|
return true;
|
|
}; // end of add_element
|
|
|
|
options.ForEach(add_element);
|
|
return error;
|
|
}
|
|
|
|
private:
|
|
struct EnumValueStorage {
|
|
EnumValueStorage() {
|
|
element.string_value = "value not set";
|
|
element.usage = "usage not set";
|
|
element.value = 0;
|
|
}
|
|
|
|
EnumValueStorage(std::string in_str_val, std::string in_usage,
|
|
size_t in_value) : value(std::move(in_str_val)), usage(std::move(in_usage)) {
|
|
SetElement(in_value);
|
|
}
|
|
|
|
EnumValueStorage(const EnumValueStorage &in) : value(in.value),
|
|
usage(in.usage) {
|
|
SetElement(in.element.value);
|
|
}
|
|
|
|
EnumValueStorage &operator=(const EnumValueStorage &in) {
|
|
value = in.value;
|
|
usage = in.usage;
|
|
SetElement(in.element.value);
|
|
return *this;
|
|
}
|
|
|
|
void SetElement(size_t in_value) {
|
|
element.value = in_value;
|
|
element.string_value = value.data();
|
|
element.usage = usage.data();
|
|
}
|
|
|
|
std::string value;
|
|
std::string usage;
|
|
OptionEnumValueElement element;
|
|
};
|
|
// We have to provide char * values for the long option, usage and enum
|
|
// values, that's what the option definitions hold.
|
|
// The long option strings are quite likely to be reused in other added
|
|
// commands, so those are stored in a global set: g_string_storer.
|
|
// But the usages are much less likely to be reused, so those are stored in
|
|
// a vector in the command instance. It gets resized to the correct size
|
|
// and then filled with null-terminated strings in the std::string, so the
|
|
// are valid C-strings that won't move around.
|
|
// The enum values and descriptions are treated similarly - these aren't
|
|
// all that common so it's not worth the effort to dedup them.
|
|
size_t m_num_options = 0;
|
|
std::unique_ptr<OptionDefinition> m_options_definition_up;
|
|
std::vector<std::vector<EnumValueStorage>> m_enum_storage;
|
|
std::vector<std::vector<OptionEnumValueElement>> m_enum_vector;
|
|
std::vector<std::string> m_usage_container;
|
|
CommandInterpreter &m_interpreter;
|
|
StructuredData::GenericSP m_cmd_obj_sp;
|
|
static std::unordered_set<std::string> g_string_storer;
|
|
};
|
|
|
|
public:
|
|
static CommandObjectSP Create(CommandInterpreter &interpreter,
|
|
std::string name,
|
|
StructuredData::GenericSP cmd_obj_sp,
|
|
ScriptedCommandSynchronicity synch,
|
|
CommandReturnObject &result) {
|
|
CommandObjectSP new_cmd_sp(new CommandObjectScriptingObjectParsed(
|
|
interpreter, name, cmd_obj_sp, synch));
|
|
|
|
CommandObjectScriptingObjectParsed *parsed_cmd
|
|
= static_cast<CommandObjectScriptingObjectParsed *>(new_cmd_sp.get());
|
|
// Now check all the failure modes, and report if found.
|
|
Status opt_error = parsed_cmd->GetOptionsError();
|
|
Status arg_error = parsed_cmd->GetArgsError();
|
|
|
|
if (opt_error.Fail())
|
|
result.AppendErrorWithFormat("failed to parse option definitions: %s",
|
|
opt_error.AsCString());
|
|
if (arg_error.Fail())
|
|
result.AppendErrorWithFormat("%sfailed to parse argument definitions: %s",
|
|
opt_error.Fail() ? ", also " : "",
|
|
arg_error.AsCString());
|
|
|
|
if (!result.Succeeded())
|
|
return {};
|
|
|
|
return new_cmd_sp;
|
|
}
|
|
|
|
CommandObjectScriptingObjectParsed(CommandInterpreter &interpreter,
|
|
std::string name,
|
|
StructuredData::GenericSP cmd_obj_sp,
|
|
ScriptedCommandSynchronicity synch)
|
|
: CommandObjectParsed(interpreter, name.c_str()),
|
|
m_cmd_obj_sp(cmd_obj_sp), m_synchro(synch),
|
|
m_options(interpreter, cmd_obj_sp), m_fetched_help_short(false),
|
|
m_fetched_help_long(false) {
|
|
StreamString stream;
|
|
ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
|
|
if (!scripter) {
|
|
m_options_error.SetErrorString("No script interpreter");
|
|
return;
|
|
}
|
|
|
|
// Set the flags:
|
|
GetFlags().Set(scripter->GetFlagsForCommandObject(cmd_obj_sp));
|
|
|
|
// Now set up the options definitions from the options:
|
|
StructuredData::ObjectSP options_object_sp
|
|
= scripter->GetOptionsForCommandObject(cmd_obj_sp);
|
|
// It's okay not to have an options dict.
|
|
if (options_object_sp) {
|
|
// The options come as a dictionary of dictionaries. The key of the
|
|
// outer dict is the long option name (since that's required). The
|
|
// value holds all the other option specification bits.
|
|
StructuredData::Dictionary *options_dict
|
|
= options_object_sp->GetAsDictionary();
|
|
// but if it exists, it has to be an array.
|
|
if (options_dict) {
|
|
m_options_error = m_options.SetOptionsFromArray(*(options_dict));
|
|
// If we got an error don't bother with the arguments...
|
|
if (m_options_error.Fail())
|
|
return;
|
|
} else {
|
|
m_options_error.SetErrorString("Options array not an array");
|
|
return;
|
|
}
|
|
}
|
|
// Then fetch the args. Since the arguments can have usage masks you need
|
|
// an array of arrays.
|
|
StructuredData::ObjectSP args_object_sp
|
|
= scripter->GetArgumentsForCommandObject(cmd_obj_sp);
|
|
if (args_object_sp) {
|
|
StructuredData::Array *args_array = args_object_sp->GetAsArray();
|
|
if (!args_array) {
|
|
m_args_error.SetErrorString("Argument specification is not an array");
|
|
return;
|
|
}
|
|
size_t counter = 0;
|
|
|
|
// This is the Array::ForEach function that handles the
|
|
// CommandArgumentEntry arrays one by one:
|
|
auto arg_array_adder = [this, &counter] (StructuredData::Object *object)
|
|
-> bool {
|
|
// This is the Array::ForEach function to add argument entries:
|
|
CommandArgumentEntry this_entry;
|
|
size_t elem_counter = 0;
|
|
auto args_adder = [this, counter, &elem_counter, &this_entry]
|
|
(StructuredData::Object *object) -> bool {
|
|
// The arguments definition has three fields, the argument type, the
|
|
// repeat and the usage mask.
|
|
CommandArgumentType arg_type = eArgTypeNone;
|
|
ArgumentRepetitionType arg_repetition = eArgRepeatOptional;
|
|
uint32_t arg_opt_set_association;
|
|
|
|
auto report_error = [this, elem_counter, counter]
|
|
(const char *err_txt) -> bool {
|
|
m_args_error.SetErrorStringWithFormatv("Element {0} of arguments "
|
|
"list element {1}: %s.", elem_counter, counter, err_txt);
|
|
return false;
|
|
};
|
|
|
|
StructuredData::Dictionary *arg_dict = object->GetAsDictionary();
|
|
if (!arg_dict) {
|
|
report_error("is not a dictionary.");
|
|
return false;
|
|
}
|
|
// Argument Type:
|
|
StructuredData::ObjectSP obj_sp
|
|
= arg_dict->GetValueForKey("arg_type");
|
|
if (obj_sp) {
|
|
StructuredData::UnsignedInteger *uint_val
|
|
= obj_sp->GetAsUnsignedInteger();
|
|
if (!uint_val) {
|
|
report_error("value type must be an unsigned integer");
|
|
return false;
|
|
}
|
|
uint64_t arg_type_int = uint_val->GetValue();
|
|
if (arg_type_int >= eArgTypeLastArg) {
|
|
report_error("value type beyond ArgumentRepetitionType bounds");
|
|
return false;
|
|
}
|
|
arg_type = (CommandArgumentType) arg_type_int;
|
|
}
|
|
// Repeat Value:
|
|
obj_sp = arg_dict->GetValueForKey("repeat");
|
|
std::optional<ArgumentRepetitionType> repeat;
|
|
if (obj_sp) {
|
|
llvm::StringRef repeat_str = obj_sp->GetStringValue();
|
|
if (repeat_str.empty()) {
|
|
report_error("repeat value is empty");
|
|
return false;
|
|
}
|
|
repeat = ArgRepetitionFromString(repeat_str);
|
|
if (!repeat) {
|
|
report_error("invalid repeat value");
|
|
return false;
|
|
}
|
|
arg_repetition = *repeat;
|
|
}
|
|
|
|
// Usage Mask:
|
|
obj_sp = arg_dict->GetValueForKey("groups");
|
|
m_args_error = CommandOptions::ParseUsageMaskFromArray(obj_sp,
|
|
counter, arg_opt_set_association);
|
|
this_entry.emplace_back(arg_type, arg_repetition,
|
|
arg_opt_set_association);
|
|
elem_counter++;
|
|
return true;
|
|
};
|
|
StructuredData::Array *args_array = object->GetAsArray();
|
|
if (!args_array) {
|
|
m_args_error.SetErrorStringWithFormatv("Argument definition element "
|
|
"{0} is not an array", counter);
|
|
}
|
|
|
|
args_array->ForEach(args_adder);
|
|
if (m_args_error.Fail())
|
|
return false;
|
|
if (this_entry.empty()) {
|
|
m_args_error.SetErrorStringWithFormatv("Argument definition element "
|
|
"{0} is empty", counter);
|
|
return false;
|
|
}
|
|
m_arguments.push_back(this_entry);
|
|
counter++;
|
|
return true;
|
|
}; // end of arg_array_adder
|
|
// Here we actually parse the args definition:
|
|
args_array->ForEach(arg_array_adder);
|
|
}
|
|
}
|
|
|
|
~CommandObjectScriptingObjectParsed() override = default;
|
|
|
|
Status GetOptionsError() { return m_options_error; }
|
|
Status GetArgsError() { return m_args_error; }
|
|
bool WantsCompletion() override { return true; }
|
|
|
|
bool IsRemovable() const override { return true; }
|
|
|
|
ScriptedCommandSynchronicity GetSynchronicity() { return m_synchro; }
|
|
|
|
llvm::StringRef GetHelp() override {
|
|
if (m_fetched_help_short)
|
|
return CommandObjectParsed::GetHelp();
|
|
ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
|
|
if (!scripter)
|
|
return CommandObjectParsed::GetHelp();
|
|
std::string docstring;
|
|
m_fetched_help_short =
|
|
scripter->GetShortHelpForCommandObject(m_cmd_obj_sp, docstring);
|
|
if (!docstring.empty())
|
|
SetHelp(docstring);
|
|
|
|
return CommandObjectParsed::GetHelp();
|
|
}
|
|
|
|
llvm::StringRef GetHelpLong() override {
|
|
if (m_fetched_help_long)
|
|
return CommandObjectParsed::GetHelpLong();
|
|
|
|
ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
|
|
if (!scripter)
|
|
return CommandObjectParsed::GetHelpLong();
|
|
|
|
std::string docstring;
|
|
m_fetched_help_long =
|
|
scripter->GetLongHelpForCommandObject(m_cmd_obj_sp, docstring);
|
|
if (!docstring.empty())
|
|
SetHelpLong(docstring);
|
|
return CommandObjectParsed::GetHelpLong();
|
|
}
|
|
|
|
Options *GetOptions() override { return &m_options; }
|
|
|
|
|
|
protected:
|
|
void DoExecute(Args &args,
|
|
CommandReturnObject &result) override {
|
|
ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter();
|
|
|
|
Status error;
|
|
|
|
result.SetStatus(eReturnStatusInvalid);
|
|
|
|
if (!scripter ||
|
|
!scripter->RunScriptBasedParsedCommand(m_cmd_obj_sp, args,
|
|
m_synchro, result, error, m_exe_ctx)) {
|
|
result.AppendError(error.AsCString());
|
|
} else {
|
|
// Don't change the status if the command already set it...
|
|
if (result.GetStatus() == eReturnStatusInvalid) {
|
|
if (result.GetOutputData().empty())
|
|
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
|
else
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
StructuredData::GenericSP m_cmd_obj_sp;
|
|
ScriptedCommandSynchronicity m_synchro;
|
|
CommandOptions m_options;
|
|
Status m_options_error;
|
|
Status m_args_error;
|
|
bool m_fetched_help_short : 1;
|
|
bool m_fetched_help_long : 1;
|
|
};
|
|
|
|
std::unordered_set<std::string>
|
|
CommandObjectScriptingObjectParsed::CommandOptions::g_string_storer;
|
|
|
|
// CommandObjectCommandsScriptImport
|
|
#define LLDB_OPTIONS_script_import
|
|
#include "CommandOptions.inc"
|
|
|
|
class CommandObjectCommandsScriptImport : public CommandObjectParsed {
|
|
public:
|
|
CommandObjectCommandsScriptImport(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(interpreter, "command script import",
|
|
"Import a scripting module in LLDB.", nullptr) {
|
|
CommandArgumentEntry arg1;
|
|
CommandArgumentData cmd_arg;
|
|
|
|
// Define the first (and only) variant of this arg.
|
|
cmd_arg.arg_type = eArgTypeFilename;
|
|
cmd_arg.arg_repetition = eArgRepeatPlus;
|
|
|
|
// There is only one variant this argument could be; put it into the
|
|
// argument entry.
|
|
arg1.push_back(cmd_arg);
|
|
|
|
// Push the data for the first argument into the m_arguments vector.
|
|
m_arguments.push_back(arg1);
|
|
}
|
|
|
|
~CommandObjectCommandsScriptImport() override = default;
|
|
|
|
Options *GetOptions() override { return &m_options; }
|
|
|
|
protected:
|
|
class CommandOptions : public Options {
|
|
public:
|
|
CommandOptions() = default;
|
|
|
|
~CommandOptions() override = default;
|
|
|
|
Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
|
|
ExecutionContext *execution_context) override {
|
|
Status error;
|
|
const int short_option = m_getopt_table[option_idx].val;
|
|
|
|
switch (short_option) {
|
|
case 'r':
|
|
// NO-OP
|
|
break;
|
|
case 'c':
|
|
relative_to_command_file = true;
|
|
break;
|
|
case 's':
|
|
silent = true;
|
|
break;
|
|
default:
|
|
llvm_unreachable("Unimplemented option");
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
void OptionParsingStarting(ExecutionContext *execution_context) override {
|
|
relative_to_command_file = false;
|
|
}
|
|
|
|
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
|
|
return llvm::ArrayRef(g_script_import_options);
|
|
}
|
|
bool relative_to_command_file = false;
|
|
bool silent = false;
|
|
};
|
|
|
|
void DoExecute(Args &command, CommandReturnObject &result) override {
|
|
if (command.empty()) {
|
|
result.AppendError("command script import needs one or more arguments");
|
|
return;
|
|
}
|
|
|
|
FileSpec source_dir = {};
|
|
if (m_options.relative_to_command_file) {
|
|
source_dir = GetDebugger().GetCommandInterpreter().GetCurrentSourceDir();
|
|
if (!source_dir) {
|
|
result.AppendError("command script import -c can only be specified "
|
|
"from a command file");
|
|
return;
|
|
}
|
|
}
|
|
|
|
for (auto &entry : command.entries()) {
|
|
Status error;
|
|
|
|
LoadScriptOptions options;
|
|
options.SetInitSession(true);
|
|
options.SetSilent(m_options.silent);
|
|
|
|
// FIXME: this is necessary because CommandObject::CheckRequirements()
|
|
// assumes that commands won't ever be recursively invoked, but it's
|
|
// actually possible to craft a Python script that does other "command
|
|
// script imports" in __lldb_init_module the real fix is to have
|
|
// recursive commands possible with a CommandInvocation object separate
|
|
// from the CommandObject itself, so that recursive command invocations
|
|
// won't stomp on each other (wrt to execution contents, options, and
|
|
// more)
|
|
m_exe_ctx.Clear();
|
|
if (GetDebugger().GetScriptInterpreter()->LoadScriptingModule(
|
|
entry.c_str(), options, error, /*module_sp=*/nullptr,
|
|
source_dir)) {
|
|
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
|
} else {
|
|
result.AppendErrorWithFormat("module importing failed: %s",
|
|
error.AsCString());
|
|
}
|
|
}
|
|
}
|
|
|
|
CommandOptions m_options;
|
|
};
|
|
|
|
#define LLDB_OPTIONS_script_add
|
|
#include "CommandOptions.inc"
|
|
|
|
class CommandObjectCommandsScriptAdd : public CommandObjectParsed,
|
|
public IOHandlerDelegateMultiline {
|
|
public:
|
|
CommandObjectCommandsScriptAdd(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(interpreter, "command script add",
|
|
"Add a scripted function as an LLDB command.",
|
|
"Add a scripted function as an lldb command. "
|
|
"If you provide a single argument, the command "
|
|
"will be added at the root level of the command "
|
|
"hierarchy. If there are more arguments they "
|
|
"must be a path to a user-added container "
|
|
"command, and the last element will be the new "
|
|
"command name."),
|
|
IOHandlerDelegateMultiline("DONE") {
|
|
CommandArgumentEntry arg1;
|
|
CommandArgumentData cmd_arg;
|
|
|
|
// This is one or more command names, which form the path to the command
|
|
// you want to add.
|
|
cmd_arg.arg_type = eArgTypeCommand;
|
|
cmd_arg.arg_repetition = eArgRepeatPlus;
|
|
|
|
// There is only one variant this argument could be; put it into the
|
|
// argument entry.
|
|
arg1.push_back(cmd_arg);
|
|
|
|
// Push the data for the first argument into the m_arguments vector.
|
|
m_arguments.push_back(arg1);
|
|
}
|
|
|
|
~CommandObjectCommandsScriptAdd() override = default;
|
|
|
|
Options *GetOptions() override { return &m_options; }
|
|
|
|
void
|
|
HandleArgumentCompletion(CompletionRequest &request,
|
|
OptionElementVector &opt_element_vector) override {
|
|
CommandCompletions::CompleteModifiableCmdPathArgs(m_interpreter, request,
|
|
opt_element_vector);
|
|
}
|
|
|
|
protected:
|
|
class CommandOptions : public Options {
|
|
public:
|
|
CommandOptions() = default;
|
|
|
|
~CommandOptions() override = default;
|
|
|
|
Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
|
|
ExecutionContext *execution_context) override {
|
|
Status error;
|
|
const int short_option = m_getopt_table[option_idx].val;
|
|
|
|
switch (short_option) {
|
|
case 'f':
|
|
if (!option_arg.empty())
|
|
m_funct_name = std::string(option_arg);
|
|
break;
|
|
case 'c':
|
|
if (!option_arg.empty())
|
|
m_class_name = std::string(option_arg);
|
|
break;
|
|
case 'h':
|
|
if (!option_arg.empty())
|
|
m_short_help = std::string(option_arg);
|
|
break;
|
|
case 'o':
|
|
m_overwrite_lazy = eLazyBoolYes;
|
|
break;
|
|
case 'p':
|
|
m_parsed_command = true;
|
|
break;
|
|
case 's':
|
|
m_synchronicity =
|
|
(ScriptedCommandSynchronicity)OptionArgParser::ToOptionEnum(
|
|
option_arg, GetDefinitions()[option_idx].enum_values, 0, error);
|
|
if (!error.Success())
|
|
error.SetErrorStringWithFormat(
|
|
"unrecognized value for synchronicity '%s'",
|
|
option_arg.str().c_str());
|
|
break;
|
|
case 'C': {
|
|
Status error;
|
|
OptionDefinition definition = GetDefinitions()[option_idx];
|
|
lldb::CompletionType completion_type =
|
|
static_cast<lldb::CompletionType>(OptionArgParser::ToOptionEnum(
|
|
option_arg, definition.enum_values, eNoCompletion, error));
|
|
if (!error.Success())
|
|
error.SetErrorStringWithFormat(
|
|
"unrecognized value for command completion type '%s'",
|
|
option_arg.str().c_str());
|
|
m_completion_type = completion_type;
|
|
} break;
|
|
default:
|
|
llvm_unreachable("Unimplemented option");
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
void OptionParsingStarting(ExecutionContext *execution_context) override {
|
|
m_class_name.clear();
|
|
m_funct_name.clear();
|
|
m_short_help.clear();
|
|
m_completion_type = eNoCompletion;
|
|
m_overwrite_lazy = eLazyBoolCalculate;
|
|
m_synchronicity = eScriptedCommandSynchronicitySynchronous;
|
|
m_parsed_command = false;
|
|
}
|
|
|
|
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
|
|
return llvm::ArrayRef(g_script_add_options);
|
|
}
|
|
|
|
// Instance variables to hold the values for command options.
|
|
|
|
std::string m_class_name;
|
|
std::string m_funct_name;
|
|
std::string m_short_help;
|
|
LazyBool m_overwrite_lazy = eLazyBoolCalculate;
|
|
ScriptedCommandSynchronicity m_synchronicity =
|
|
eScriptedCommandSynchronicitySynchronous;
|
|
CompletionType m_completion_type = eNoCompletion;
|
|
bool m_parsed_command = false;
|
|
};
|
|
|
|
void IOHandlerActivated(IOHandler &io_handler, bool interactive) override {
|
|
StreamFileSP output_sp(io_handler.GetOutputStreamFileSP());
|
|
if (output_sp && interactive) {
|
|
output_sp->PutCString(g_python_command_instructions);
|
|
output_sp->Flush();
|
|
}
|
|
}
|
|
|
|
void IOHandlerInputComplete(IOHandler &io_handler,
|
|
std::string &data) override {
|
|
StreamFileSP error_sp = io_handler.GetErrorStreamFileSP();
|
|
|
|
ScriptInterpreter *interpreter = GetDebugger().GetScriptInterpreter();
|
|
if (interpreter) {
|
|
StringList lines;
|
|
lines.SplitIntoLines(data);
|
|
if (lines.GetSize() > 0) {
|
|
std::string funct_name_str;
|
|
if (interpreter->GenerateScriptAliasFunction(lines, funct_name_str)) {
|
|
if (funct_name_str.empty()) {
|
|
error_sp->Printf("error: unable to obtain a function name, didn't "
|
|
"add python command.\n");
|
|
error_sp->Flush();
|
|
} else {
|
|
// everything should be fine now, let's add this alias
|
|
|
|
CommandObjectSP command_obj_sp(new CommandObjectPythonFunction(
|
|
m_interpreter, m_cmd_name, funct_name_str, m_short_help,
|
|
m_synchronicity, m_completion_type));
|
|
if (!m_container) {
|
|
Status error = m_interpreter.AddUserCommand(
|
|
m_cmd_name, command_obj_sp, m_overwrite);
|
|
if (error.Fail()) {
|
|
error_sp->Printf("error: unable to add selected command: '%s'",
|
|
error.AsCString());
|
|
error_sp->Flush();
|
|
}
|
|
} else {
|
|
llvm::Error llvm_error = m_container->LoadUserSubcommand(
|
|
m_cmd_name, command_obj_sp, m_overwrite);
|
|
if (llvm_error) {
|
|
error_sp->Printf("error: unable to add selected command: '%s'",
|
|
llvm::toString(std::move(llvm_error)).c_str());
|
|
error_sp->Flush();
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
error_sp->Printf(
|
|
"error: unable to create function, didn't add python command\n");
|
|
error_sp->Flush();
|
|
}
|
|
} else {
|
|
error_sp->Printf("error: empty function, didn't add python command\n");
|
|
error_sp->Flush();
|
|
}
|
|
} else {
|
|
error_sp->Printf(
|
|
"error: script interpreter missing, didn't add python command\n");
|
|
error_sp->Flush();
|
|
}
|
|
|
|
io_handler.SetIsDone(true);
|
|
}
|
|
|
|
void DoExecute(Args &command, CommandReturnObject &result) override {
|
|
if (GetDebugger().GetScriptLanguage() != lldb::eScriptLanguagePython) {
|
|
result.AppendError("only scripting language supported for scripted "
|
|
"commands is currently Python");
|
|
return;
|
|
}
|
|
|
|
if (command.GetArgumentCount() == 0) {
|
|
result.AppendError("'command script add' requires at least one argument");
|
|
return;
|
|
}
|
|
// Store the options in case we get multi-line input, also figure out the
|
|
// default if not user supplied:
|
|
switch (m_options.m_overwrite_lazy) {
|
|
case eLazyBoolCalculate:
|
|
m_overwrite = !GetDebugger().GetCommandInterpreter().GetRequireCommandOverwrite();
|
|
break;
|
|
case eLazyBoolYes:
|
|
m_overwrite = true;
|
|
break;
|
|
case eLazyBoolNo:
|
|
m_overwrite = false;
|
|
}
|
|
|
|
Status path_error;
|
|
m_container = GetCommandInterpreter().VerifyUserMultiwordCmdPath(
|
|
command, true, path_error);
|
|
|
|
if (path_error.Fail()) {
|
|
result.AppendErrorWithFormat("error in command path: %s",
|
|
path_error.AsCString());
|
|
return;
|
|
}
|
|
|
|
if (!m_container) {
|
|
// This is getting inserted into the root of the interpreter.
|
|
m_cmd_name = std::string(command[0].ref());
|
|
} else {
|
|
size_t num_args = command.GetArgumentCount();
|
|
m_cmd_name = std::string(command[num_args - 1].ref());
|
|
}
|
|
|
|
m_short_help.assign(m_options.m_short_help);
|
|
m_synchronicity = m_options.m_synchronicity;
|
|
m_completion_type = m_options.m_completion_type;
|
|
|
|
// Handle the case where we prompt for the script code first:
|
|
if (m_options.m_class_name.empty() && m_options.m_funct_name.empty()) {
|
|
m_interpreter.GetPythonCommandsFromIOHandler(" ", // Prompt
|
|
*this); // IOHandlerDelegate
|
|
return;
|
|
}
|
|
|
|
CommandObjectSP new_cmd_sp;
|
|
if (m_options.m_class_name.empty()) {
|
|
new_cmd_sp.reset(new CommandObjectPythonFunction(
|
|
m_interpreter, m_cmd_name, m_options.m_funct_name,
|
|
m_options.m_short_help, m_synchronicity, m_completion_type));
|
|
} else {
|
|
ScriptInterpreter *interpreter = GetDebugger().GetScriptInterpreter();
|
|
if (!interpreter) {
|
|
result.AppendError("cannot find ScriptInterpreter");
|
|
return;
|
|
}
|
|
|
|
auto cmd_obj_sp = interpreter->CreateScriptCommandObject(
|
|
m_options.m_class_name.c_str());
|
|
if (!cmd_obj_sp) {
|
|
result.AppendErrorWithFormatv("cannot create helper object for: "
|
|
"'{0}'", m_options.m_class_name);
|
|
return;
|
|
}
|
|
|
|
if (m_options.m_parsed_command) {
|
|
new_cmd_sp = CommandObjectScriptingObjectParsed::Create(m_interpreter,
|
|
m_cmd_name, cmd_obj_sp, m_synchronicity, result);
|
|
if (!result.Succeeded())
|
|
return;
|
|
} else
|
|
new_cmd_sp.reset(new CommandObjectScriptingObjectRaw(
|
|
m_interpreter, m_cmd_name, cmd_obj_sp, m_synchronicity,
|
|
m_completion_type));
|
|
}
|
|
|
|
// Assume we're going to succeed...
|
|
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
|
if (!m_container) {
|
|
Status add_error =
|
|
m_interpreter.AddUserCommand(m_cmd_name, new_cmd_sp, m_overwrite);
|
|
if (add_error.Fail())
|
|
result.AppendErrorWithFormat("cannot add command: %s",
|
|
add_error.AsCString());
|
|
} else {
|
|
llvm::Error llvm_error =
|
|
m_container->LoadUserSubcommand(m_cmd_name, new_cmd_sp, m_overwrite);
|
|
if (llvm_error)
|
|
result.AppendErrorWithFormat(
|
|
"cannot add command: %s",
|
|
llvm::toString(std::move(llvm_error)).c_str());
|
|
}
|
|
}
|
|
|
|
CommandOptions m_options;
|
|
std::string m_cmd_name;
|
|
CommandObjectMultiword *m_container = nullptr;
|
|
std::string m_short_help;
|
|
bool m_overwrite = false;
|
|
ScriptedCommandSynchronicity m_synchronicity =
|
|
eScriptedCommandSynchronicitySynchronous;
|
|
CompletionType m_completion_type = eNoCompletion;
|
|
};
|
|
|
|
// CommandObjectCommandsScriptList
|
|
|
|
class CommandObjectCommandsScriptList : public CommandObjectParsed {
|
|
public:
|
|
CommandObjectCommandsScriptList(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(interpreter, "command script list",
|
|
"List defined top-level scripted commands.",
|
|
nullptr) {}
|
|
|
|
~CommandObjectCommandsScriptList() override = default;
|
|
|
|
void DoExecute(Args &command, CommandReturnObject &result) override {
|
|
m_interpreter.GetHelp(result, CommandInterpreter::eCommandTypesUserDef);
|
|
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
}
|
|
};
|
|
|
|
// CommandObjectCommandsScriptClear
|
|
|
|
class CommandObjectCommandsScriptClear : public CommandObjectParsed {
|
|
public:
|
|
CommandObjectCommandsScriptClear(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(interpreter, "command script clear",
|
|
"Delete all scripted commands.", nullptr) {}
|
|
|
|
~CommandObjectCommandsScriptClear() override = default;
|
|
|
|
protected:
|
|
void DoExecute(Args &command, CommandReturnObject &result) override {
|
|
m_interpreter.RemoveAllUser();
|
|
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
}
|
|
};
|
|
|
|
// CommandObjectCommandsScriptDelete
|
|
|
|
class CommandObjectCommandsScriptDelete : public CommandObjectParsed {
|
|
public:
|
|
CommandObjectCommandsScriptDelete(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(
|
|
interpreter, "command script delete",
|
|
"Delete a scripted command by specifying the path to the command.",
|
|
nullptr) {
|
|
CommandArgumentEntry arg1;
|
|
CommandArgumentData cmd_arg;
|
|
|
|
// This is a list of command names forming the path to the command
|
|
// to be deleted.
|
|
cmd_arg.arg_type = eArgTypeCommand;
|
|
cmd_arg.arg_repetition = eArgRepeatPlus;
|
|
|
|
// There is only one variant this argument could be; put it into the
|
|
// argument entry.
|
|
arg1.push_back(cmd_arg);
|
|
|
|
// Push the data for the first argument into the m_arguments vector.
|
|
m_arguments.push_back(arg1);
|
|
}
|
|
|
|
~CommandObjectCommandsScriptDelete() override = default;
|
|
|
|
void
|
|
HandleArgumentCompletion(CompletionRequest &request,
|
|
OptionElementVector &opt_element_vector) override {
|
|
lldb_private::CommandCompletions::CompleteModifiableCmdPathArgs(
|
|
m_interpreter, request, opt_element_vector);
|
|
}
|
|
|
|
protected:
|
|
void DoExecute(Args &command, CommandReturnObject &result) override {
|
|
|
|
llvm::StringRef root_cmd = command[0].ref();
|
|
size_t num_args = command.GetArgumentCount();
|
|
|
|
if (root_cmd.empty()) {
|
|
result.AppendErrorWithFormat("empty root command name");
|
|
return;
|
|
}
|
|
if (!m_interpreter.HasUserCommands() &&
|
|
!m_interpreter.HasUserMultiwordCommands()) {
|
|
result.AppendErrorWithFormat("can only delete user defined commands, "
|
|
"but no user defined commands found");
|
|
return;
|
|
}
|
|
|
|
CommandObjectSP cmd_sp = m_interpreter.GetCommandSPExact(root_cmd);
|
|
if (!cmd_sp) {
|
|
result.AppendErrorWithFormat("command '%s' not found.",
|
|
command[0].c_str());
|
|
return;
|
|
}
|
|
if (!cmd_sp->IsUserCommand()) {
|
|
result.AppendErrorWithFormat("command '%s' is not a user command.",
|
|
command[0].c_str());
|
|
return;
|
|
}
|
|
if (cmd_sp->GetAsMultiwordCommand() && num_args == 1) {
|
|
result.AppendErrorWithFormat("command '%s' is a multi-word command.\n "
|
|
"Delete with \"command container delete\"",
|
|
command[0].c_str());
|
|
return;
|
|
}
|
|
|
|
if (command.GetArgumentCount() == 1) {
|
|
m_interpreter.RemoveUser(root_cmd);
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
return;
|
|
}
|
|
// We're deleting a command from a multiword command. Verify the command
|
|
// path:
|
|
Status error;
|
|
CommandObjectMultiword *container =
|
|
GetCommandInterpreter().VerifyUserMultiwordCmdPath(command, true,
|
|
error);
|
|
if (error.Fail()) {
|
|
result.AppendErrorWithFormat("could not resolve command path: %s",
|
|
error.AsCString());
|
|
return;
|
|
}
|
|
if (!container) {
|
|
// This means that command only had a leaf command, so the container is
|
|
// the root. That should have been handled above.
|
|
result.AppendErrorWithFormat("could not find a container for '%s'",
|
|
command[0].c_str());
|
|
return;
|
|
}
|
|
const char *leaf_cmd = command[num_args - 1].c_str();
|
|
llvm::Error llvm_error =
|
|
container->RemoveUserSubcommand(leaf_cmd,
|
|
/* multiword not okay */ false);
|
|
if (llvm_error) {
|
|
result.AppendErrorWithFormat(
|
|
"could not delete command '%s': %s", leaf_cmd,
|
|
llvm::toString(std::move(llvm_error)).c_str());
|
|
return;
|
|
}
|
|
|
|
Stream &out_stream = result.GetOutputStream();
|
|
|
|
out_stream << "Deleted command:";
|
|
for (size_t idx = 0; idx < num_args; idx++) {
|
|
out_stream << ' ';
|
|
out_stream << command[idx].c_str();
|
|
}
|
|
out_stream << '\n';
|
|
result.SetStatus(eReturnStatusSuccessFinishResult);
|
|
}
|
|
};
|
|
|
|
#pragma mark CommandObjectMultiwordCommandsScript
|
|
|
|
// CommandObjectMultiwordCommandsScript
|
|
|
|
class CommandObjectMultiwordCommandsScript : public CommandObjectMultiword {
|
|
public:
|
|
CommandObjectMultiwordCommandsScript(CommandInterpreter &interpreter)
|
|
: CommandObjectMultiword(
|
|
interpreter, "command script",
|
|
"Commands for managing custom "
|
|
"commands implemented by "
|
|
"interpreter scripts.",
|
|
"command script <subcommand> [<subcommand-options>]") {
|
|
LoadSubCommand("add", CommandObjectSP(
|
|
new CommandObjectCommandsScriptAdd(interpreter)));
|
|
LoadSubCommand(
|
|
"delete",
|
|
CommandObjectSP(new CommandObjectCommandsScriptDelete(interpreter)));
|
|
LoadSubCommand(
|
|
"clear",
|
|
CommandObjectSP(new CommandObjectCommandsScriptClear(interpreter)));
|
|
LoadSubCommand("list", CommandObjectSP(new CommandObjectCommandsScriptList(
|
|
interpreter)));
|
|
LoadSubCommand(
|
|
"import",
|
|
CommandObjectSP(new CommandObjectCommandsScriptImport(interpreter)));
|
|
}
|
|
|
|
~CommandObjectMultiwordCommandsScript() override = default;
|
|
};
|
|
|
|
#pragma mark CommandObjectCommandContainer
|
|
#define LLDB_OPTIONS_container_add
|
|
#include "CommandOptions.inc"
|
|
|
|
class CommandObjectCommandsContainerAdd : public CommandObjectParsed {
|
|
public:
|
|
CommandObjectCommandsContainerAdd(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(
|
|
interpreter, "command container add",
|
|
"Add a container command to lldb. Adding to built-"
|
|
"in container commands is not allowed.",
|
|
"command container add [[path1]...] container-name") {
|
|
CommandArgumentEntry arg1;
|
|
CommandArgumentData cmd_arg;
|
|
|
|
// This is one or more command names, which form the path to the command
|
|
// you want to add.
|
|
cmd_arg.arg_type = eArgTypeCommand;
|
|
cmd_arg.arg_repetition = eArgRepeatPlus;
|
|
|
|
// There is only one variant this argument could be; put it into the
|
|
// argument entry.
|
|
arg1.push_back(cmd_arg);
|
|
|
|
// Push the data for the first argument into the m_arguments vector.
|
|
m_arguments.push_back(arg1);
|
|
}
|
|
|
|
~CommandObjectCommandsContainerAdd() override = default;
|
|
|
|
Options *GetOptions() override { return &m_options; }
|
|
|
|
void
|
|
HandleArgumentCompletion(CompletionRequest &request,
|
|
OptionElementVector &opt_element_vector) override {
|
|
lldb_private::CommandCompletions::CompleteModifiableCmdPathArgs(
|
|
m_interpreter, request, opt_element_vector);
|
|
}
|
|
|
|
protected:
|
|
class CommandOptions : public Options {
|
|
public:
|
|
CommandOptions() = default;
|
|
|
|
~CommandOptions() override = default;
|
|
|
|
Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
|
|
ExecutionContext *execution_context) override {
|
|
Status error;
|
|
const int short_option = m_getopt_table[option_idx].val;
|
|
|
|
switch (short_option) {
|
|
case 'h':
|
|
if (!option_arg.empty())
|
|
m_short_help = std::string(option_arg);
|
|
break;
|
|
case 'o':
|
|
m_overwrite = true;
|
|
break;
|
|
case 'H':
|
|
if (!option_arg.empty())
|
|
m_long_help = std::string(option_arg);
|
|
break;
|
|
default:
|
|
llvm_unreachable("Unimplemented option");
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
void OptionParsingStarting(ExecutionContext *execution_context) override {
|
|
m_short_help.clear();
|
|
m_long_help.clear();
|
|
m_overwrite = false;
|
|
}
|
|
|
|
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
|
|
return llvm::ArrayRef(g_container_add_options);
|
|
}
|
|
|
|
// Instance variables to hold the values for command options.
|
|
|
|
std::string m_short_help;
|
|
std::string m_long_help;
|
|
bool m_overwrite = false;
|
|
};
|
|
void DoExecute(Args &command, CommandReturnObject &result) override {
|
|
size_t num_args = command.GetArgumentCount();
|
|
|
|
if (num_args == 0) {
|
|
result.AppendError("no command was specified");
|
|
return;
|
|
}
|
|
|
|
if (num_args == 1) {
|
|
// We're adding this as a root command, so use the interpreter.
|
|
const char *cmd_name = command.GetArgumentAtIndex(0);
|
|
auto cmd_sp = CommandObjectSP(new CommandObjectMultiword(
|
|
GetCommandInterpreter(), cmd_name, m_options.m_short_help.c_str(),
|
|
m_options.m_long_help.c_str()));
|
|
cmd_sp->GetAsMultiwordCommand()->SetRemovable(true);
|
|
Status add_error = GetCommandInterpreter().AddUserCommand(
|
|
cmd_name, cmd_sp, m_options.m_overwrite);
|
|
if (add_error.Fail()) {
|
|
result.AppendErrorWithFormat("error adding command: %s",
|
|
add_error.AsCString());
|
|
return;
|
|
}
|
|
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
|
return;
|
|
}
|
|
|
|
// We're adding this to a subcommand, first find the subcommand:
|
|
Status path_error;
|
|
CommandObjectMultiword *add_to_me =
|
|
GetCommandInterpreter().VerifyUserMultiwordCmdPath(command, true,
|
|
path_error);
|
|
|
|
if (!add_to_me) {
|
|
result.AppendErrorWithFormat("error adding command: %s",
|
|
path_error.AsCString());
|
|
return;
|
|
}
|
|
|
|
const char *cmd_name = command.GetArgumentAtIndex(num_args - 1);
|
|
auto cmd_sp = CommandObjectSP(new CommandObjectMultiword(
|
|
GetCommandInterpreter(), cmd_name, m_options.m_short_help.c_str(),
|
|
m_options.m_long_help.c_str()));
|
|
llvm::Error llvm_error =
|
|
add_to_me->LoadUserSubcommand(cmd_name, cmd_sp, m_options.m_overwrite);
|
|
if (llvm_error) {
|
|
result.AppendErrorWithFormat("error adding subcommand: %s",
|
|
llvm::toString(std::move(llvm_error)).c_str());
|
|
return;
|
|
}
|
|
|
|
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
|
}
|
|
|
|
private:
|
|
CommandOptions m_options;
|
|
};
|
|
|
|
#define LLDB_OPTIONS_multiword_delete
|
|
#include "CommandOptions.inc"
|
|
class CommandObjectCommandsContainerDelete : public CommandObjectParsed {
|
|
public:
|
|
CommandObjectCommandsContainerDelete(CommandInterpreter &interpreter)
|
|
: CommandObjectParsed(
|
|
interpreter, "command container delete",
|
|
"Delete a container command previously added to "
|
|
"lldb.",
|
|
"command container delete [[path1] ...] container-cmd") {
|
|
CommandArgumentEntry arg1;
|
|
CommandArgumentData cmd_arg;
|
|
|
|
// This is one or more command names, which form the path to the command
|
|
// you want to add.
|
|
cmd_arg.arg_type = eArgTypeCommand;
|
|
cmd_arg.arg_repetition = eArgRepeatPlus;
|
|
|
|
// There is only one variant this argument could be; put it into the
|
|
// argument entry.
|
|
arg1.push_back(cmd_arg);
|
|
|
|
// Push the data for the first argument into the m_arguments vector.
|
|
m_arguments.push_back(arg1);
|
|
}
|
|
|
|
~CommandObjectCommandsContainerDelete() override = default;
|
|
|
|
void
|
|
HandleArgumentCompletion(CompletionRequest &request,
|
|
OptionElementVector &opt_element_vector) override {
|
|
lldb_private::CommandCompletions::CompleteModifiableCmdPathArgs(
|
|
m_interpreter, request, opt_element_vector);
|
|
}
|
|
|
|
protected:
|
|
void DoExecute(Args &command, CommandReturnObject &result) override {
|
|
size_t num_args = command.GetArgumentCount();
|
|
|
|
if (num_args == 0) {
|
|
result.AppendError("No command was specified.");
|
|
return;
|
|
}
|
|
|
|
if (num_args == 1) {
|
|
// We're removing a root command, so we need to delete it from the
|
|
// interpreter.
|
|
const char *cmd_name = command.GetArgumentAtIndex(0);
|
|
// Let's do a little more work here so we can do better error reporting.
|
|
CommandInterpreter &interp = GetCommandInterpreter();
|
|
CommandObjectSP cmd_sp = interp.GetCommandSPExact(cmd_name);
|
|
if (!cmd_sp) {
|
|
result.AppendErrorWithFormat("container command %s doesn't exist.",
|
|
cmd_name);
|
|
return;
|
|
}
|
|
if (!cmd_sp->IsUserCommand()) {
|
|
result.AppendErrorWithFormat(
|
|
"container command %s is not a user command", cmd_name);
|
|
return;
|
|
}
|
|
if (!cmd_sp->GetAsMultiwordCommand()) {
|
|
result.AppendErrorWithFormat("command %s is not a container command",
|
|
cmd_name);
|
|
return;
|
|
}
|
|
|
|
bool did_remove = GetCommandInterpreter().RemoveUserMultiword(cmd_name);
|
|
if (!did_remove) {
|
|
result.AppendErrorWithFormat("error removing command %s.", cmd_name);
|
|
return;
|
|
}
|
|
|
|
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
|
return;
|
|
}
|
|
|
|
// We're removing a subcommand, first find the subcommand's owner:
|
|
Status path_error;
|
|
CommandObjectMultiword *container =
|
|
GetCommandInterpreter().VerifyUserMultiwordCmdPath(command, true,
|
|
path_error);
|
|
|
|
if (!container) {
|
|
result.AppendErrorWithFormat("error removing container command: %s",
|
|
path_error.AsCString());
|
|
return;
|
|
}
|
|
const char *leaf = command.GetArgumentAtIndex(num_args - 1);
|
|
llvm::Error llvm_error =
|
|
container->RemoveUserSubcommand(leaf, /* multiword okay */ true);
|
|
if (llvm_error) {
|
|
result.AppendErrorWithFormat("error removing container command: %s",
|
|
llvm::toString(std::move(llvm_error)).c_str());
|
|
return;
|
|
}
|
|
result.SetStatus(eReturnStatusSuccessFinishNoResult);
|
|
}
|
|
};
|
|
|
|
class CommandObjectCommandContainer : public CommandObjectMultiword {
|
|
public:
|
|
CommandObjectCommandContainer(CommandInterpreter &interpreter)
|
|
: CommandObjectMultiword(
|
|
interpreter, "command container",
|
|
"Commands for adding container commands to lldb. "
|
|
"Container commands are containers for other commands. You can "
|
|
"add nested container commands by specifying a command path, "
|
|
"but you can't add commands into the built-in command hierarchy.",
|
|
"command container <subcommand> [<subcommand-options>]") {
|
|
LoadSubCommand("add", CommandObjectSP(new CommandObjectCommandsContainerAdd(
|
|
interpreter)));
|
|
LoadSubCommand(
|
|
"delete",
|
|
CommandObjectSP(new CommandObjectCommandsContainerDelete(interpreter)));
|
|
}
|
|
|
|
~CommandObjectCommandContainer() override = default;
|
|
};
|
|
|
|
#pragma mark CommandObjectMultiwordCommands
|
|
|
|
// CommandObjectMultiwordCommands
|
|
|
|
CommandObjectMultiwordCommands::CommandObjectMultiwordCommands(
|
|
CommandInterpreter &interpreter)
|
|
: CommandObjectMultiword(interpreter, "command",
|
|
"Commands for managing custom LLDB commands.",
|
|
"command <subcommand> [<subcommand-options>]") {
|
|
LoadSubCommand("source",
|
|
CommandObjectSP(new CommandObjectCommandsSource(interpreter)));
|
|
LoadSubCommand("alias",
|
|
CommandObjectSP(new CommandObjectCommandsAlias(interpreter)));
|
|
LoadSubCommand("unalias", CommandObjectSP(
|
|
new CommandObjectCommandsUnalias(interpreter)));
|
|
LoadSubCommand("delete",
|
|
CommandObjectSP(new CommandObjectCommandsDelete(interpreter)));
|
|
LoadSubCommand("container", CommandObjectSP(new CommandObjectCommandContainer(
|
|
interpreter)));
|
|
LoadSubCommand(
|
|
"regex", CommandObjectSP(new CommandObjectCommandsAddRegex(interpreter)));
|
|
LoadSubCommand(
|
|
"script",
|
|
CommandObjectSP(new CommandObjectMultiwordCommandsScript(interpreter)));
|
|
}
|
|
|
|
CommandObjectMultiwordCommands::~CommandObjectMultiwordCommands() = default;
|