Add a "command container" hierarchy to allow users to add container nodes.

The point is to allow users with a related set of script based commands
to organize their commands in a hierarchy in the command set, rather than
having to have only top-level commands.

Differential Revision: https://reviews.llvm.org/D110298
This commit is contained in:
Jim Ingham
2021-10-12 10:55:24 -07:00
parent 41f814589f
commit c5011aed9c
21 changed files with 1275 additions and 141 deletions

View File

@@ -13,6 +13,7 @@
#include "lldb/Core/FileSpecList.h"
#include "lldb/Core/SearchFilter.h"
#include "lldb/Interpreter/Options.h"
#include "lldb/Utility/CompletionRequest.h"
#include "lldb/Utility/RegularExpression.h"
#include "lldb/lldb-private.h"
@@ -151,6 +152,15 @@ public:
static void TypeCategoryNames(CommandInterpreter &interpreter,
CompletionRequest &request,
SearchFilter *searcher);
/// This completer works for commands whose only arguments are a command path.
/// It isn't tied to an argument type because it completes not on a single
/// argument but on the sequence of arguments, so you have to invoke it by
/// hand.
static void
CompleteModifiableCmdPathArgs(CommandInterpreter &interpreter,
CompletionRequest &request,
OptionElementVector &opt_element_vector);
};
} // namespace lldb_private

View File

@@ -231,11 +231,12 @@ public:
};
enum CommandTypes {
eCommandTypesBuiltin = 0x0001, // native commands such as "frame"
eCommandTypesUserDef = 0x0002, // scripted commands
eCommandTypesAliases = 0x0004, // aliases such as "po"
eCommandTypesHidden = 0x0008, // commands prefixed with an underscore
eCommandTypesAllThem = 0xFFFF // all commands
eCommandTypesBuiltin = 0x0001, //< native commands such as "frame"
eCommandTypesUserDef = 0x0002, //< scripted commands
eCommandTypesUserMW = 0x0004, //< multiword commands (command containers)
eCommandTypesAliases = 0x0008, //< aliases such as "po"
eCommandTypesHidden = 0x0010, //< commands prefixed with an underscore
eCommandTypesAllThem = 0xFFFF //< all commands
};
CommandInterpreter(Debugger &debugger, bool synchronous_execution);
@@ -256,8 +257,8 @@ public:
bool AddCommand(llvm::StringRef name, const lldb::CommandObjectSP &cmd_sp,
bool can_replace);
bool AddUserCommand(llvm::StringRef name, const lldb::CommandObjectSP &cmd_sp,
bool can_replace);
Status AddUserCommand(llvm::StringRef name,
const lldb::CommandObjectSP &cmd_sp, bool can_replace);
lldb::CommandObjectSP GetCommandSPExact(llvm::StringRef cmd,
bool include_aliases = false) const;
@@ -266,12 +267,49 @@ public:
StringList *matches = nullptr,
StringList *descriptions = nullptr) const;
CommandObject *GetUserCommandObject(llvm::StringRef cmd,
StringList *matches = nullptr,
StringList *descriptions = nullptr) const;
/// Determine whether a root level, built-in command with this name exists.
bool CommandExists(llvm::StringRef cmd) const;
/// Determine whether an alias command with this name exists
bool AliasExists(llvm::StringRef cmd) const;
/// Determine whether a root-level user command with this name exists.
bool UserCommandExists(llvm::StringRef cmd) const;
/// Determine whether a root-level user multiword command with this name
/// exists.
bool UserMultiwordCommandExists(llvm::StringRef cmd) const;
/// Look up the command pointed to by path encoded in the arguments of
/// the incoming command object. If all the path components exist
/// and are all actual commands - not aliases, and the leaf command is a
/// multiword command, return the command. Otherwise return nullptr, and put
/// a useful diagnostic in the Status object.
///
/// \param[in] path
/// An Args object holding the path in its arguments
/// \param[in] leaf_is_command
/// If true, return the container of the leaf name rather than looking up
/// the whole path as a leaf command. The leaf needn't exist in this case.
/// \param[in,out] result
/// If the path is not found, this error shows where we got off track.
/// \return
/// If found, a pointer to the CommandObjectMultiword pointed to by path,
/// or to the container of the leaf element is is_leaf_command.
/// Returns nullptr under two circumstances:
/// 1) The command in not found (check error.Fail)
/// 2) is_leaf is true and the path has only a leaf. We don't have a
/// dummy "contains everything MWC, so we return null here, but
/// in this case error.Success is true.
CommandObjectMultiword *VerifyUserMultiwordCmdPath(Args &path,
bool leaf_is_command,
Status &result);
CommandAlias *AddAlias(llvm::StringRef alias_name,
lldb::CommandObjectSP &command_obj_sp,
llvm::StringRef args_string = llvm::StringRef());
@@ -283,6 +321,11 @@ public:
bool GetAliasFullName(llvm::StringRef cmd, std::string &full_name) const;
bool RemoveUserMultiword(llvm::StringRef multiword_name);
// Do we want to allow top-level user multiword commands to be deleted?
void RemoveAllUserMultiword() { m_user_mw_dict.clear(); }
bool RemoveUser(llvm::StringRef alias_name);
void RemoveAllUser() { m_user_dict.clear(); }
@@ -414,6 +457,8 @@ public:
bool HasUserCommands() const;
bool HasUserMultiwordCommands() const;
bool HasAliasOptions() const;
void BuildAliasCommandArgs(CommandObject *alias_cmd_obj,
@@ -421,6 +466,7 @@ public:
std::string &raw_input_string,
CommandReturnObject &result);
/// Picks the number out of a string of the form "%NNN", otherwise return 0.
int GetOptionArgumentPosition(const char *in_string);
void SkipLLDBInitFiles(bool skip_lldbinit_files) {
@@ -437,7 +483,8 @@ public:
StringList &commands_help,
bool search_builtin_commands,
bool search_user_commands,
bool search_alias_commands);
bool search_alias_commands,
bool search_user_mw_commands);
bool GetBatchCommandMode() { return m_batch_command_mode; }
@@ -506,6 +553,10 @@ public:
return m_user_dict;
}
const CommandObject::CommandMap &GetUserMultiwordCommands() const {
return m_user_mw_dict;
}
const CommandObject::CommandMap &GetCommands() const {
return m_command_dict;
}
@@ -636,6 +687,8 @@ private:
CommandObject::CommandMap
m_alias_dict; // Stores user aliases/abbreviations for commands
CommandObject::CommandMap m_user_dict; // Stores user-defined commands
CommandObject::CommandMap
m_user_mw_dict; // Stores user-defined multiword commands
CommandHistory m_command_history;
std::string m_repeat_command; // Stores the command that will be executed for
// an empty command string.

View File

@@ -145,6 +145,10 @@ public:
virtual bool IsMultiwordObject() { return false; }
bool IsUserCommand() { return m_is_user_command; }
void SetIsUserCommand(bool is_user) { m_is_user_command = is_user; }
virtual CommandObjectMultiword *GetAsMultiwordCommand() { return nullptr; }
virtual bool IsAlias() { return false; }
@@ -159,6 +163,10 @@ public:
return lldb::CommandObjectSP();
}
virtual lldb::CommandObjectSP GetSubcommandSPExact(llvm::StringRef sub_cmd) {
return lldb::CommandObjectSP();
}
virtual CommandObject *GetSubcommandObject(llvm::StringRef sub_cmd,
StringList *matches = nullptr) {
return nullptr;
@@ -183,6 +191,13 @@ public:
return false;
}
virtual llvm::Error LoadUserSubcommand(llvm::StringRef cmd_name,
const lldb::CommandObjectSP &command_obj,
bool can_replace) {
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"can only add commands to container commands");
}
virtual bool WantsRawCommandString() = 0;
// By default, WantsCompletion = !WantsRawCommandString. Subclasses who want
@@ -367,6 +382,7 @@ protected:
lldb::CommandOverrideCallback m_deprecated_command_override_callback;
lldb::CommandOverrideCallbackWithResult m_command_override_callback;
void *m_command_override_baton;
bool m_is_user_command = false;
// Helper function to populate IDs or ID ranges as the command argument data
// to the specified command argument entry.

View File

@@ -35,11 +35,19 @@ public:
bool LoadSubCommand(llvm::StringRef cmd_name,
const lldb::CommandObjectSP &command_obj) override;
llvm::Error LoadUserSubcommand(llvm::StringRef cmd_name,
const lldb::CommandObjectSP &command_obj,
bool can_replace) override;
llvm::Error RemoveUserSubcommand(llvm::StringRef cmd_name, bool multiword_okay);
void GenerateHelpText(Stream &output_stream) override;
lldb::CommandObjectSP GetSubcommandSP(llvm::StringRef sub_cmd,
StringList *matches = nullptr) override;
lldb::CommandObjectSP GetSubcommandSPExact(llvm::StringRef sub_cmd) override;
CommandObject *GetSubcommandObject(llvm::StringRef sub_cmd,
StringList *matches = nullptr) override;

View File

@@ -574,12 +574,11 @@ lldb::SBCommand SBCommandInterpreter::AddMultiwordCommand(const char *name,
LLDB_RECORD_METHOD(lldb::SBCommand, SBCommandInterpreter, AddMultiwordCommand,
(const char *, const char *), name, help);
CommandObjectMultiword *new_command =
new CommandObjectMultiword(*m_opaque_ptr, name, help);
new_command->SetRemovable(true);
lldb::CommandObjectSP new_command_sp(new_command);
if (new_command_sp &&
m_opaque_ptr->AddUserCommand(name, new_command_sp, true))
lldb::CommandObjectSP new_command_sp(
new CommandObjectMultiword(*m_opaque_ptr, name, help));
new_command_sp->GetAsMultiwordCommand()->SetRemovable(true);
Status add_error = m_opaque_ptr->AddUserCommand(name, new_command_sp, true);
if (add_error.Success())
return LLDB_RECORD_RESULT(lldb::SBCommand(new_command_sp));
return LLDB_RECORD_RESULT(lldb::SBCommand());
}
@@ -620,8 +619,8 @@ lldb::SBCommand SBCommandInterpreter::AddCommand(
*m_opaque_ptr, name, impl, help, syntax, /*flags=*/0,
auto_repeat_command);
if (new_command_sp &&
m_opaque_ptr->AddUserCommand(name, new_command_sp, true))
Status add_error = m_opaque_ptr->AddUserCommand(name, new_command_sp, true);
if (add_error.Success())
return LLDB_RECORD_RESULT(lldb::SBCommand(new_command_sp));
return LLDB_RECORD_RESULT(lldb::SBCommand());
}

View File

@@ -17,6 +17,8 @@
#include "lldb/Host/FileSystem.h"
#include "lldb/Interpreter/CommandCompletions.h"
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/CommandObject.h"
#include "lldb/Interpreter/CommandObjectMultiword.h"
#include "lldb/Interpreter/OptionValueProperties.h"
#include "lldb/Symbol/CompileUnit.h"
#include "lldb/Symbol/Variable.h"
@@ -792,3 +794,60 @@ void CommandCompletions::TypeCategoryNames(CommandInterpreter &interpreter,
return true;
});
}
void CommandCompletions::CompleteModifiableCmdPathArgs(
CommandInterpreter &interpreter, CompletionRequest &request,
OptionElementVector &opt_element_vector) {
// The only arguments constitute a command path, however, there might be
// options interspersed among the arguments, and we need to skip those. Do that
// by copying the args vector, and just dropping all the option bits:
Args args = request.GetParsedLine();
std::vector<size_t> to_delete;
for (auto &elem : opt_element_vector) {
to_delete.push_back(elem.opt_pos);
if (elem.opt_arg_pos != 0)
to_delete.push_back(elem.opt_arg_pos);
}
sort(to_delete.begin(), to_delete.end(), std::greater<size_t>());
for (size_t idx : to_delete)
args.DeleteArgumentAtIndex(idx);
// At this point, we should only have args, so now lookup the command up to
// the cursor element.
// There's nothing here but options. It doesn't seem very useful here to
// dump all the commands, so just return.
size_t num_args = args.GetArgumentCount();
if (num_args == 0)
return;
// There's just one argument, so we should complete its name:
StringList matches;
if (num_args == 1) {
interpreter.GetUserCommandObject(args.GetArgumentAtIndex(0), &matches,
nullptr);
request.AddCompletions(matches);
return;
}
// There was more than one path element, lets find the containing command:
Status error;
CommandObjectMultiword *mwc =
interpreter.VerifyUserMultiwordCmdPath(args, true, error);
// Something was wrong somewhere along the path, but I don't think there's
// a good way to go back and fill in the missing elements:
if (error.Fail())
return;
// This should never happen. We already handled the case of one argument
// above, and we can only get Success & nullptr back if there's a one-word
// leaf.
assert(mwc != nullptr);
mwc->GetSubcommandObject(args.GetArgumentAtIndex(num_args - 1), &matches);
if (matches.GetSize() == 0)
return;
request.AddCompletions(matches);
}

View File

@@ -49,8 +49,8 @@ bool CommandObjectApropos::DoExecute(Args &args, CommandReturnObject &result) {
StringList commands_found;
StringList commands_help;
m_interpreter.FindCommandsForApropos(search_word, commands_found,
commands_help, true, true, true);
m_interpreter.FindCommandsForApropos(
search_word, commands_found, commands_help, true, true, true, true);
if (commands_found.GetSize() == 0) {
result.AppendMessageWithFormat("No commands found pertaining to '%s'. "

View File

@@ -443,6 +443,14 @@ protected:
return false;
}
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 false;
}
// 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.
@@ -528,6 +536,14 @@ protected:
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;
@@ -1371,14 +1387,21 @@ public:
CommandObjectCommandsScriptAdd(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "command script add",
"Add a scripted function as an LLDB command.",
nullptr),
"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"), m_options() {
CommandArgumentEntry arg1;
CommandArgumentData cmd_arg;
// Define the first (and only) variant of this arg.
cmd_arg.arg_type = eArgTypeCommandName;
cmd_arg.arg_repetition = eArgRepeatPlain;
// 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.
@@ -1392,6 +1415,13 @@ public:
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:
@@ -1418,6 +1448,9 @@ protected:
if (!option_arg.empty())
m_short_help = std::string(option_arg);
break;
case 'o':
m_overwrite = true;
break;
case 's':
m_synchronicity =
(ScriptedCommandSynchronicity)OptionArgParser::ToOptionEnum(
@@ -1438,6 +1471,7 @@ protected:
m_class_name.clear();
m_funct_name.clear();
m_short_help.clear();
m_overwrite = false;
m_synchronicity = eScriptedCommandSynchronicitySynchronous;
}
@@ -1450,6 +1484,7 @@ protected:
std::string m_class_name;
std::string m_funct_name;
std::string m_short_help;
bool m_overwrite;
ScriptedCommandSynchronicity m_synchronicity =
eScriptedCommandSynchronicitySynchronous;
};
@@ -1484,26 +1519,36 @@ protected:
CommandObjectSP command_obj_sp(new CommandObjectPythonFunction(
m_interpreter, m_cmd_name, funct_name_str, m_short_help,
m_synchronicity));
if (!m_interpreter.AddUserCommand(m_cmd_name, command_obj_sp,
true)) {
error_sp->Printf("error: unable to add selected command, didn't "
"add python command.\n");
error_sp->Flush();
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: 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->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: script interpreter missing, didn't add python command\n");
error_sp->Flush();
}
@@ -1517,31 +1562,45 @@ protected:
return false;
}
if (command.GetArgumentCount() != 1) {
result.AppendError("'command script add' requires one argument");
if (command.GetArgumentCount() == 0) {
result.AppendError("'command script add' requires at least one argument");
return false;
}
// Store the options in case we get multi-line input
m_overwrite = m_options.m_overwrite;
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 false;
}
// Store the options in case we get multi-line input
m_cmd_name = std::string(command[0].ref());
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;
// 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 result.Succeeded();
}
CommandObjectSP new_cmd_sp;
if (m_options.m_class_name.empty()) {
if (m_options.m_funct_name.empty()) {
m_interpreter.GetPythonCommandsFromIOHandler(
" ", // Prompt
*this); // IOHandlerDelegate
} else {
CommandObjectSP new_cmd(new CommandObjectPythonFunction(
m_interpreter, m_cmd_name, m_options.m_funct_name,
m_options.m_short_help, m_synchronicity));
if (m_interpreter.AddUserCommand(m_cmd_name, new_cmd, true)) {
result.SetStatus(eReturnStatusSuccessFinishNoResult);
} else {
result.AppendError("cannot add command");
}
}
new_cmd_sp.reset(new CommandObjectPythonFunction(
m_interpreter, m_cmd_name, m_options.m_funct_name,
m_options.m_short_help, m_synchronicity));
} else {
ScriptInterpreter *interpreter = GetDebugger().GetScriptInterpreter();
if (!interpreter) {
@@ -1556,21 +1615,33 @@ protected:
return false;
}
CommandObjectSP new_cmd(new CommandObjectScriptingObject(
new_cmd_sp.reset(new CommandObjectScriptingObject(
m_interpreter, m_cmd_name, cmd_obj_sp, m_synchronicity));
if (m_interpreter.AddUserCommand(m_cmd_name, new_cmd, true)) {
result.SetStatus(eReturnStatusSuccessFinishNoResult);
} else {
result.AppendError("cannot add command");
}
}
// 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());
}
return result.Succeeded();
}
CommandOptions m_options;
std::string m_cmd_name;
CommandObjectMultiword *m_container = nullptr;
std::string m_short_help;
bool m_overwrite;
ScriptedCommandSynchronicity m_synchronicity;
};
@@ -1580,7 +1651,8 @@ class CommandObjectCommandsScriptList : public CommandObjectParsed {
public:
CommandObjectCommandsScriptList(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "command script list",
"List defined scripted commands.", nullptr) {}
"List defined top-level scripted commands.",
nullptr) {}
~CommandObjectCommandsScriptList() override = default;
@@ -1628,14 +1700,17 @@ protected:
class CommandObjectCommandsScriptDelete : public CommandObjectParsed {
public:
CommandObjectCommandsScriptDelete(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "command script delete",
"Delete a scripted command.", nullptr) {
: CommandObjectParsed(
interpreter, "command script delete",
"Delete a scripted command by specifying the path to the command.",
nullptr) {
CommandArgumentEntry arg1;
CommandArgumentData cmd_arg;
// Define the first (and only) variant of this arg.
cmd_arg.arg_type = eArgTypeCommandName;
cmd_arg.arg_repetition = eArgRepeatPlain;
// 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.
@@ -1650,30 +1725,86 @@ public:
void
HandleArgumentCompletion(CompletionRequest &request,
OptionElementVector &opt_element_vector) override {
if (!m_interpreter.HasCommands() || request.GetCursorIndex() != 0)
return;
for (const auto &c : m_interpreter.GetUserCommands())
request.TryCompleteCurrentArg(c.first, c.second->GetHelp());
CommandCompletions::CompleteModifiableCmdPathArgs(m_interpreter, request,
opt_element_vector);
}
protected:
bool DoExecute(Args &command, CommandReturnObject &result) override {
if (command.GetArgumentCount() != 1) {
result.AppendError("'command script delete' requires one argument");
llvm::StringRef root_cmd = command[0].ref();
size_t num_args = command.GetArgumentCount();
if (root_cmd.empty()) {
result.AppendErrorWithFormat("empty root command name");
return false;
}
if (!m_interpreter.HasUserCommands() &&
!m_interpreter.HasUserMultiwordCommands()) {
result.AppendErrorWithFormat("can only delete user defined commands, "
"but no user defined commands found");
return false;
}
auto cmd_name = command[0].ref();
if (cmd_name.empty() || !m_interpreter.HasUserCommands() ||
!m_interpreter.UserCommandExists(cmd_name)) {
result.AppendErrorWithFormat("command %s not found", command[0].c_str());
CommandObjectSP cmd_sp = m_interpreter.GetCommandSPExact(root_cmd);
if (!cmd_sp) {
result.AppendErrorWithFormat("command '%s' not found.",
command[0].c_str());
return false;
}
if (!cmd_sp->IsUserCommand()) {
result.AppendErrorWithFormat("command '%s' is not a user command.",
command[0].c_str());
return false;
}
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 false;
}
m_interpreter.RemoveUser(cmd_name);
if (command.GetArgumentCount() == 1) {
m_interpreter.RemoveUser(root_cmd);
result.SetStatus(eReturnStatusSuccessFinishResult);
return true;
}
// 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 false;
}
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 false;
}
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 false;
}
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);
return true;
}
@@ -1710,6 +1841,271 @@ public:
~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 {
CommandCompletions::CompleteModifiableCmdPathArgs(m_interpreter, request,
opt_element_vector);
}
protected:
class CommandOptions : public Options {
public:
CommandOptions() : Options(), m_short_help(), m_long_help() {}
~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::makeArrayRef(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;
};
bool DoExecute(Args &command, CommandReturnObject &result) override {
size_t num_args = command.GetArgumentCount();
if (num_args == 0) {
result.AppendError("no command was specified");
return false;
}
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 false;
}
result.SetStatus(eReturnStatusSuccessFinishNoResult);
return true;
}
// 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 false;
}
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 false;
}
result.SetStatus(eReturnStatusSuccessFinishNoResult);
return true;
}
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 {
CommandCompletions::CompleteModifiableCmdPathArgs(m_interpreter, request,
opt_element_vector);
}
protected:
bool DoExecute(Args &command, CommandReturnObject &result) override {
size_t num_args = command.GetArgumentCount();
if (num_args == 0) {
result.AppendError("No command was specified.");
return false;
}
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 false;
}
if (!cmd_sp->IsUserCommand()) {
result.AppendErrorWithFormat(
"container command %s is not a user command", cmd_name);
return false;
}
if (!cmd_sp->GetAsMultiwordCommand()) {
result.AppendErrorWithFormat("command %s is not a container command",
cmd_name);
return false;
}
bool did_remove = GetCommandInterpreter().RemoveUserMultiword(cmd_name);
if (!did_remove) {
result.AppendErrorWithFormat("error removing command %s.", cmd_name);
return false;
}
result.SetStatus(eReturnStatusSuccessFinishNoResult);
return true;
}
// 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 false;
}
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 false;
}
result.SetStatus(eReturnStatusSuccessFinishNoResult);
return true;
}
};
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 "
"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
@@ -1727,6 +2123,8 @@ CommandObjectMultiwordCommands::CommandObjectMultiwordCommands(
new CommandObjectCommandsUnalias(interpreter)));
LoadSubCommand("delete",
CommandObjectSP(new CommandObjectCommandsDelete(interpreter)));
LoadSubCommand("container", CommandObjectSP(new CommandObjectCommandContainer(
interpreter)));
LoadSubCommand(
"regex", CommandObjectSP(new CommandObjectCommandsAddRegex(interpreter)));
LoadSubCommand(

View File

@@ -51,8 +51,9 @@ CommandObjectHelp::CommandObjectHelp(CommandInterpreter &interpreter)
CommandArgumentEntry arg;
CommandArgumentData command_arg;
// Define the first (and only) variant of this arg.
command_arg.arg_type = eArgTypeCommandName;
// A list of command names forming a path to the command we want help on.
// No names is allowed - in which case we dump the top-level help.
command_arg.arg_type = eArgTypeCommand;
command_arg.arg_repetition = eArgRepeatStar;
// There is only one variant this argument could be; put it into the argument
@@ -85,8 +86,10 @@ bool CommandObjectHelp::DoExecute(Args &command, CommandReturnObject &result) {
uint32_t cmd_types = CommandInterpreter::eCommandTypesBuiltin;
if (m_options.m_show_aliases)
cmd_types |= CommandInterpreter::eCommandTypesAliases;
if (m_options.m_show_user_defined)
if (m_options.m_show_user_defined) {
cmd_types |= CommandInterpreter::eCommandTypesUserDef;
cmd_types |= CommandInterpreter::eCommandTypesUserMW;
}
if (m_options.m_show_hidden)
cmd_types |= CommandInterpreter::eCommandTypesHidden;

View File

@@ -26,36 +26,48 @@ CommandObjectMultiword::CommandObjectMultiword(CommandInterpreter &interpreter,
CommandObjectMultiword::~CommandObjectMultiword() = default;
CommandObjectSP
CommandObjectMultiword::GetSubcommandSPExact(llvm::StringRef sub_cmd) {
if (m_subcommand_dict.empty())
return {};
auto pos = m_subcommand_dict.find(std::string(sub_cmd));
if (pos == m_subcommand_dict.end())
return {};
return pos->second;
}
CommandObjectSP CommandObjectMultiword::GetSubcommandSP(llvm::StringRef sub_cmd,
StringList *matches) {
CommandObjectSP return_cmd_sp;
if (m_subcommand_dict.empty())
return {};
CommandObjectSP return_cmd_sp = GetSubcommandSPExact(sub_cmd);
if (return_cmd_sp) {
if (matches)
matches->AppendString(sub_cmd);
return return_cmd_sp;
}
CommandObject::CommandMap::iterator pos;
if (!m_subcommand_dict.empty()) {
StringList local_matches;
if (matches == nullptr)
matches = &local_matches;
int num_matches =
AddNamesMatchingPartialString(m_subcommand_dict, sub_cmd, *matches);
if (num_matches == 1) {
// Cleaner, but slightly less efficient would be to call back into this
// function, since I now know I have an exact match...
sub_cmd = matches->GetStringAtIndex(0);
pos = m_subcommand_dict.find(std::string(sub_cmd));
if (pos != m_subcommand_dict.end()) {
// An exact match; append the sub_cmd to the 'matches' string list.
if (matches)
matches->AppendString(sub_cmd);
if (pos != m_subcommand_dict.end())
return_cmd_sp = pos->second;
} else {
StringList local_matches;
if (matches == nullptr)
matches = &local_matches;
int num_matches =
AddNamesMatchingPartialString(m_subcommand_dict, sub_cmd, *matches);
if (num_matches == 1) {
// Cleaner, but slightly less efficient would be to call back into this
// function, since I now know I have an exact match...
sub_cmd = matches->GetStringAtIndex(0);
pos = m_subcommand_dict.find(std::string(sub_cmd));
if (pos != m_subcommand_dict.end())
return_cmd_sp = pos->second;
}
}
}
return return_cmd_sp;
}
@@ -66,9 +78,9 @@ CommandObjectMultiword::GetSubcommandObject(llvm::StringRef sub_cmd,
}
bool CommandObjectMultiword::LoadSubCommand(llvm::StringRef name,
const CommandObjectSP &cmd_obj) {
if (cmd_obj)
assert((&GetCommandInterpreter() == &cmd_obj->GetCommandInterpreter()) &&
const CommandObjectSP &cmd_obj_sp) {
if (cmd_obj_sp)
lldbassert((&GetCommandInterpreter() == &cmd_obj_sp->GetCommandInterpreter()) &&
"tried to add a CommandObject from a different interpreter");
CommandMap::iterator pos;
@@ -76,13 +88,76 @@ bool CommandObjectMultiword::LoadSubCommand(llvm::StringRef name,
pos = m_subcommand_dict.find(std::string(name));
if (pos == m_subcommand_dict.end()) {
m_subcommand_dict[std::string(name)] = cmd_obj;
m_subcommand_dict[std::string(name)] = cmd_obj_sp;
} else
success = false;
return success;
}
llvm::Error CommandObjectMultiword::LoadUserSubcommand(
llvm::StringRef name, const CommandObjectSP &cmd_obj_sp, bool can_replace) {
Status result;
if (cmd_obj_sp)
lldbassert((&GetCommandInterpreter() == &cmd_obj_sp->GetCommandInterpreter()) &&
"tried to add a CommandObject from a different interpreter");
if (!IsUserCommand()) {
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"can't add a user subcommand to a builtin container command.");
}
// Make sure this a user command if it isn't already:
cmd_obj_sp->SetIsUserCommand(true);
std::string str_name(name);
auto pos = m_subcommand_dict.find(str_name);
if (pos == m_subcommand_dict.end()) {
m_subcommand_dict[str_name] = cmd_obj_sp;
return llvm::Error::success();
}
const char *error_str = nullptr;
if (!can_replace)
error_str = "sub-command already exists";
if (!(*pos).second->IsUserCommand())
error_str = "can't replace a builtin subcommand";
if (error_str) {
return llvm::createStringError(llvm::inconvertibleErrorCode(), error_str);
}
m_subcommand_dict[str_name] = cmd_obj_sp;
return llvm::Error::success();
}
llvm::Error CommandObjectMultiword::RemoveUserSubcommand(llvm::StringRef cmd_name,
bool must_be_multiword) {
CommandMap::iterator pos;
std::string str_name(cmd_name);
pos = m_subcommand_dict.find(str_name);
if (pos == m_subcommand_dict.end()) {
return llvm::createStringError(llvm::inconvertibleErrorCode(),"subcommand '%s' not found.",
str_name.c_str());
}
if (!(*pos).second->IsUserCommand()) {
return llvm::createStringError(llvm::inconvertibleErrorCode(),"subcommand '%s' not a user command.",
str_name.c_str());
}
if (must_be_multiword && !(*pos).second->IsMultiwordObject()) {
return llvm::createStringError(llvm::inconvertibleErrorCode(),"subcommand '%s' is not a container command",
str_name.c_str());
}
if (!must_be_multiword && (*pos).second->IsMultiwordObject()) {
return llvm::createStringError(llvm::inconvertibleErrorCode(),"subcommand '%s' is not a user command",
str_name.c_str());
}
m_subcommand_dict.erase(pos);
return llvm::Error::success();
}
bool CommandObjectMultiword::Execute(const char *args_string,
CommandReturnObject &result) {
Args args(args_string);

View File

@@ -787,12 +787,23 @@ let Command = "script add" in {
Desc<"Name of the Python class to bind to this command name.">;
def script_add_help : Option<"help", "h">, Group<1>, Arg<"HelpText">,
Desc<"The help text to display for this command.">;
def script_add_overwrite : Option<"overwrite", "o">, Groups<[1,2]>,
Desc<"Overwrite an existing command at this node.">;
def script_add_synchronicity : Option<"synchronicity", "s">,
EnumArg<"ScriptedCommandSynchronicity", "ScriptSynchroType()">,
Desc<"Set the synchronicity of this command's executions with regard to "
"LLDB event system.">;
}
let Command = "container add" in {
def container_add_help : Option<"help", "h">, Arg<"HelpText">,
Desc<"Help text for this command">;
def container_add_long_help : Option<"long-help", "H">, Arg<"HelpText">,
Desc<"Long help text for this command">;
def container_add_overwrite : Option<"overwrite", "o">, Group<1>,
Desc<"Overwrite an existing command at this node.">;
}
let Command = "script" in {
def script_language : Option<"language", "l">,
EnumArg<"ScriptLang", "ScriptOptionEnum()">, Desc<"Specify the scripting "

View File

@@ -897,6 +897,63 @@ int CommandInterpreter::GetCommandNamesMatchingPartialString(
return matches.GetSize();
}
CommandObjectMultiword *CommandInterpreter::VerifyUserMultiwordCmdPath(
Args &path, bool leaf_is_command, Status &result) {
result.Clear();
auto get_multi_or_report_error =
[&result](CommandObjectSP cmd_sp,
const char *name) -> CommandObjectMultiword * {
if (!cmd_sp) {
result.SetErrorStringWithFormat("Path component: '%s' not found", name);
return nullptr;
}
if (!cmd_sp->IsUserCommand()) {
result.SetErrorStringWithFormat("Path component: '%s' is not a user "
"command",
name);
return nullptr;
}
CommandObjectMultiword *cmd_as_multi = cmd_sp->GetAsMultiwordCommand();
if (!cmd_as_multi) {
result.SetErrorStringWithFormat("Path component: '%s' is not a container "
"command",
name);
return nullptr;
}
return cmd_as_multi;
};
size_t num_args = path.GetArgumentCount();
if (num_args == 0) {
result.SetErrorString("empty command path");
return nullptr;
}
if (num_args == 1 && leaf_is_command) {
// We just got a leaf command to be added to the root. That's not an error,
// just return null for the container.
return nullptr;
}
// Start by getting the root command from the interpreter.
const char *cur_name = path.GetArgumentAtIndex(0);
CommandObjectSP cur_cmd_sp = GetCommandSPExact(cur_name);
CommandObjectMultiword *cur_as_multi =
get_multi_or_report_error(cur_cmd_sp, cur_name);
if (cur_as_multi == nullptr)
return nullptr;
size_t num_path_elements = num_args - (leaf_is_command ? 1 : 0);
for (size_t cursor = 1; cursor < num_path_elements && cur_as_multi != nullptr;
cursor++) {
cur_name = path.GetArgumentAtIndex(cursor);
cur_cmd_sp = cur_as_multi->GetSubcommandSPExact(cur_name);
cur_as_multi = get_multi_or_report_error(cur_cmd_sp, cur_name);
}
return cur_as_multi;
}
CommandObjectSP
CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str, bool include_aliases,
bool exact, StringList *matches,
@@ -923,10 +980,17 @@ CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str, bool include_aliases,
command_sp = pos->second;
}
if (HasUserMultiwordCommands()) {
auto pos = m_user_mw_dict.find(cmd);
if (pos != m_user_mw_dict.end())
command_sp = pos->second;
}
if (!exact && !command_sp) {
// We will only get into here if we didn't find any exact matches.
CommandObjectSP user_match_sp, alias_match_sp, real_match_sp;
CommandObjectSP user_match_sp, user_mw_match_sp, alias_match_sp,
real_match_sp;
StringList local_matches;
if (matches == nullptr)
@@ -935,6 +999,7 @@ CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str, bool include_aliases,
unsigned int num_cmd_matches = 0;
unsigned int num_alias_matches = 0;
unsigned int num_user_matches = 0;
unsigned int num_user_mw_matches = 0;
// Look through the command dictionaries one by one, and if we get only one
// match from any of them in toto, then return that, otherwise return an
@@ -978,14 +1043,32 @@ CommandInterpreter::GetCommandSP(llvm::StringRef cmd_str, bool include_aliases,
user_match_sp = pos->second;
}
if (HasUserMultiwordCommands()) {
num_user_mw_matches = AddNamesMatchingPartialString(
m_user_mw_dict, cmd_str, *matches, descriptions);
}
if (num_user_mw_matches == 1) {
cmd.assign(matches->GetStringAtIndex(num_cmd_matches + num_alias_matches +
num_user_matches));
auto pos = m_user_mw_dict.find(cmd);
if (pos != m_user_mw_dict.end())
user_mw_match_sp = pos->second;
}
// If we got exactly one match, return that, otherwise return the match
// list.
if (num_user_matches + num_cmd_matches + num_alias_matches == 1) {
if (num_user_matches + num_user_mw_matches + num_cmd_matches +
num_alias_matches ==
1) {
if (num_cmd_matches)
return real_match_sp;
else if (num_alias_matches)
return alias_match_sp;
else if (num_user_mw_matches)
return user_mw_match_sp;
else
return user_match_sp;
}
@@ -1008,6 +1091,8 @@ bool CommandInterpreter::AddCommand(llvm::StringRef name,
if (name.empty())
return false;
cmd_sp->SetIsUserCommand(false);
std::string name_sstr(name);
auto name_iter = m_command_dict.find(name_sstr);
if (name_iter != m_command_dict.end()) {
@@ -1020,33 +1105,49 @@ bool CommandInterpreter::AddCommand(llvm::StringRef name,
return true;
}
bool CommandInterpreter::AddUserCommand(llvm::StringRef name,
const lldb::CommandObjectSP &cmd_sp,
bool can_replace) {
Status CommandInterpreter::AddUserCommand(llvm::StringRef name,
const lldb::CommandObjectSP &cmd_sp,
bool can_replace) {
Status result;
if (cmd_sp.get())
lldbassert((this == &cmd_sp->GetCommandInterpreter()) &&
"tried to add a CommandObject from a different interpreter");
if (!name.empty()) {
// do not allow replacement of internal commands
if (CommandExists(name)) {
if (!can_replace)
return false;
if (!m_command_dict[std::string(name)]->IsRemovable())
return false;
}
if (UserCommandExists(name)) {
if (!can_replace)
return false;
if (!m_user_dict[std::string(name)]->IsRemovable())
return false;
}
m_user_dict[std::string(name)] = cmd_sp;
return true;
if (name.empty()) {
result.SetErrorString("can't use the empty string for a command name");
return result;
}
return false;
// do not allow replacement of internal commands
if (CommandExists(name)) {
result.SetErrorString("can't replace builtin command");
return result;
}
if (UserCommandExists(name)) {
if (!can_replace) {
result.SetErrorString("user command exists and force replace not set");
return result;
}
if (cmd_sp->IsMultiwordObject()) {
if (!m_user_mw_dict[std::string(name)]->IsRemovable()) {
result.SetErrorString(
"can't replace explicitly non-removable multi-word command");
return result;
}
} else {
if (!m_user_dict[std::string(name)]->IsRemovable()) {
result.SetErrorString("can't replace explicitly non-removable command");
return result;
}
}
}
cmd_sp->SetIsUserCommand(true);
if (cmd_sp->IsMultiwordObject())
m_user_mw_dict[std::string(name)] = cmd_sp;
else
m_user_dict[std::string(name)] = cmd_sp;
return result;
}
CommandObjectSP
@@ -1127,6 +1228,44 @@ CommandInterpreter::GetCommandObject(llvm::StringRef cmd_str,
return GetCommandSP(cmd_str, true, false, matches, descriptions).get();
}
CommandObject *CommandInterpreter::GetUserCommandObject(
llvm::StringRef cmd, StringList *matches, StringList *descriptions) const {
std::string cmd_str(cmd);
auto find_exact = [&](const CommandObject::CommandMap &map) {
auto found_elem = map.find(std::string(cmd));
if (found_elem == map.end())
return (CommandObject *)nullptr;
CommandObject *exact_cmd = found_elem->second.get();
if (exact_cmd) {
if (matches)
matches->AppendString(exact_cmd->GetCommandName());
if (descriptions)
descriptions->AppendString(exact_cmd->GetHelp());
return exact_cmd;
}
return (CommandObject *)nullptr;
};
CommandObject *exact_cmd = find_exact(GetUserCommands());
if (exact_cmd)
return exact_cmd;
exact_cmd = find_exact(GetUserMultiwordCommands());
if (exact_cmd)
return exact_cmd;
// We didn't have an exact command, so now look for partial matches.
size_t num_found;
StringList tmp_list;
StringList *matches_ptr = matches ? matches : &tmp_list;
num_found =
AddNamesMatchingPartialString(GetUserCommands(), cmd_str, *matches_ptr);
num_found += AddNamesMatchingPartialString(GetUserMultiwordCommands(),
cmd_str, *matches_ptr);
return {};
}
bool CommandInterpreter::CommandExists(llvm::StringRef cmd) const {
return m_command_dict.find(std::string(cmd)) != m_command_dict.end();
}
@@ -1169,6 +1308,10 @@ bool CommandInterpreter::UserCommandExists(llvm::StringRef cmd) const {
return m_user_dict.find(std::string(cmd)) != m_user_dict.end();
}
bool CommandInterpreter::UserMultiwordCommandExists(llvm::StringRef cmd) const {
return m_user_mw_dict.find(std::string(cmd)) != m_user_mw_dict.end();
}
CommandAlias *
CommandInterpreter::AddAlias(llvm::StringRef alias_name,
lldb::CommandObjectSP &command_obj_sp,
@@ -1209,9 +1352,10 @@ bool CommandInterpreter::RemoveCommand(llvm::StringRef cmd) {
}
return false;
}
bool CommandInterpreter::RemoveUser(llvm::StringRef alias_name) {
bool CommandInterpreter::RemoveUser(llvm::StringRef user_name) {
CommandObject::CommandMap::iterator pos =
m_user_dict.find(std::string(alias_name));
m_user_dict.find(std::string(user_name));
if (pos != m_user_dict.end()) {
m_user_dict.erase(pos);
return true;
@@ -1219,6 +1363,16 @@ bool CommandInterpreter::RemoveUser(llvm::StringRef alias_name) {
return false;
}
bool CommandInterpreter::RemoveUserMultiword(llvm::StringRef multi_name) {
CommandObject::CommandMap::iterator pos =
m_user_mw_dict.find(std::string(multi_name));
if (pos != m_user_mw_dict.end()) {
m_user_mw_dict.erase(pos);
return true;
}
return false;
}
void CommandInterpreter::GetHelp(CommandReturnObject &result,
uint32_t cmd_types) {
llvm::StringRef help_prologue(GetDebugger().GetIOHandlerHelpPrologue());
@@ -1274,6 +1428,18 @@ void CommandInterpreter::GetHelp(CommandReturnObject &result,
result.AppendMessage("");
}
if (!m_user_mw_dict.empty() &&
((cmd_types & eCommandTypesUserMW) == eCommandTypesUserMW)) {
result.AppendMessage("Current user-defined container commands:");
result.AppendMessage("");
max_len = FindLongestCommandWord(m_user_mw_dict);
for (pos = m_user_dict.begin(); pos != m_user_mw_dict.end(); ++pos) {
OutputFormattedHelpText(result.GetOutputStream(), pos->first, "--",
pos->second->GetHelp(), max_len);
}
result.AppendMessage("");
}
result.AppendMessageWithFormat(
"For more information on any command, type '%shelp <command-name>'.\n",
GetCommandPrefix());
@@ -1931,6 +2097,10 @@ bool CommandInterpreter::HasAliases() const { return (!m_alias_dict.empty()); }
bool CommandInterpreter::HasUserCommands() const { return (!m_user_dict.empty()); }
bool CommandInterpreter::HasUserMultiwordCommands() const {
return (!m_user_mw_dict.empty());
}
bool CommandInterpreter::HasAliasOptions() const { return HasAliases(); }
void CommandInterpreter::BuildAliasCommandArgs(CommandObject *alias_cmd_obj,
@@ -2581,6 +2751,9 @@ void CommandInterpreter::OutputFormattedHelpText(Stream &strm,
strm.IndentMore(prefix.size());
bool prefixed_yet = false;
// Even if we have no help text we still want to emit the command name.
if (help_text.empty())
help_text = "No help text";
while (!help_text.empty()) {
// Prefix the first line, indent subsequent lines to line up
if (!prefixed_yet) {
@@ -2700,7 +2873,8 @@ void CommandInterpreter::FindCommandsForApropos(llvm::StringRef search_word,
StringList &commands_help,
bool search_builtin_commands,
bool search_user_commands,
bool search_alias_commands) {
bool search_alias_commands,
bool search_user_mw_commands) {
CommandObject::CommandMap::const_iterator pos;
if (search_builtin_commands)
@@ -2711,6 +2885,10 @@ void CommandInterpreter::FindCommandsForApropos(llvm::StringRef search_word,
FindCommandsForApropos(search_word, commands_found, commands_help,
m_user_dict);
if (search_user_mw_commands)
FindCommandsForApropos(search_word, commands_found, commands_help,
m_user_mw_dict);
if (search_alias_commands)
FindCommandsForApropos(search_word, commands_found, commands_help,
m_alias_dict);

View File

@@ -1120,7 +1120,7 @@ CommandObject::ArgumentTableEntry CommandObject::g_arguments_data[] = {
{ eArgTypeWatchpointIDRange, "watchpt-id-list", CommandCompletions::eNoCompletion, { nullptr, false }, "For example, '1-3' or '1 to 3'." },
{ eArgTypeWatchType, "watch-type", CommandCompletions::eNoCompletion, { nullptr, false }, "Specify the type for a watchpoint." },
{ eArgRawInput, "raw-input", CommandCompletions::eNoCompletion, { nullptr, false }, "Free-form text passed to a command without prior interpretation, allowing spaces without requiring quotes. To pass arguments and free form text put two dashes ' -- ' between the last argument and any raw input." },
{ eArgTypeCommand, "command", CommandCompletions::eNoCompletion, { nullptr, false }, "An LLDB Command line command." },
{ eArgTypeCommand, "command", CommandCompletions::eNoCompletion, { nullptr, false }, "An LLDB Command line command element." },
{ eArgTypeColumnNum, "column", CommandCompletions::eNoCompletion, { nullptr, false }, "Column number in a source file." },
{ eArgTypeModuleUUID, "module-uuid", CommandCompletions::eModuleUUIDCompletion, { nullptr, false }, "A module UUID value." },
{ eArgTypeSaveCoreStyle, "corefile-style", CommandCompletions::eNoCompletion, { nullptr, false }, "The type of corefile that lldb will try to create, dependant on this target's capabilities." }

View File

@@ -0,0 +1,127 @@
"""
Test user added container commands
"""
import sys
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
class TestCmdContainer(TestBase):
mydir = TestBase.compute_mydir(__file__)
NO_DEBUG_INFO_TESTCASE = True
def test_container_add(self):
self.container_add()
def check_command_tree_exists(self):
"""This makes sure we can still run the command tree we added."""
self.runCmd("test-multi")
self.runCmd("test-multi test-multi-sub")
self.runCmd("test-multi test-multi-sub welcome")
def container_add(self):
# Make sure we can't overwrite built-in commands:
self.expect("command container add process", "Can't replace builtin container command",
substrs=["can't replace builtin command"], error=True)
self.expect("command container add process non_such_subcommand", "Can't add to built-in subcommand",
substrs=["Path component: 'process' is not a user command"], error=True)
self.expect("command container add process launch", "Can't replace builtin subcommand",
substrs=["Path component: 'process' is not a user command"], error=True)
# Now lets make a container command:
self.runCmd("command container add -h 'A test container command' test-multi")
# Make sure the help works:
self.expect("help test-multi", "Help works for top-level multi",
substrs=["A test container command"])
# Add a subcommand:
self.runCmd("command container add -h 'A test container sub-command' test-multi test-multi-sub")
# Make sure the help works:
self.expect("help test-multi", "Help shows sub-multi",
substrs=["A test container command", "test-multi-sub -- A test container sub-command"])
self.expect("help test-multi test-multi-sub", "Help shows sub-multi",
substrs=["A test container sub-command"])
# Now add a script based command to the container command:
self.runCmd("command script import welcome.py")
self.runCmd("command script add -c welcome.WelcomeCommand test-multi test-multi-sub welcome")
# Make sure the help still works:
self.expect("help test-multi test-multi-sub", "Listing subcommands works",
substrs=["A test container sub-command", "welcome -- Just a docstring for Welcome"])
self.expect("help test-multi test-multi-sub welcome", "Subcommand help works",
substrs=["Just a docstring for Welcome"])
# And make sure it actually works:
self.expect("test-multi test-multi-sub welcome friend", "Test command works",
substrs=["Hello friend, welcome to LLDB"])
# Make sure overwriting works, first the leaf command:
# We should not be able to remove extant commands by default:
self.expect("command script add -c welcome.WelcomeCommand2 test-multi test-multi-sub welcome",
"overwrite command w/o -o",
substrs=["cannot add command: sub-command already exists"], error=True)
# But we can with the -o option:
self.runCmd("command script add -c welcome.WelcomeCommand2 -o test-multi test-multi-sub welcome")
# Make sure we really did overwrite:
self.expect("test-multi test-multi-sub welcome friend", "Used the new command class",
substrs=["Hello friend, welcome again to LLDB"])
self.expect("apropos welcome", "welcome should show up in apropos", substrs=["Just a docstring for the second Welcome"])
# Make sure we give good errors when the input is wrong:
self.expect("command script delete test-mult test-multi-sub welcome", "Delete script command - wrong first path component",
substrs=["'test-mult' not found"], error=True)
self.expect("command script delete test-multi test-multi-su welcome", "Delete script command - wrong second path component",
substrs=["'test-multi-su' not found"], error=True)
self.check_command_tree_exists()
self.expect("command script delete test-multi test-multi-sub welcom", "Delete script command - wrong leaf component",
substrs=["'welcom' not found"], error=True)
self.check_command_tree_exists()
self.expect("command script delete test-multi test-multi-sub", "Delete script command - no leaf component",
substrs=["subcommand 'test-multi-sub' is not a user command"], error=True)
self.check_command_tree_exists()
# You can't use command script delete to delete container commands:
self.expect("command script delete test-multi", "Delete script - can't delete container",
substrs=["command 'test-multi' is a multi-word command."], error=True)
self.expect("command script delete test-multi test-multi-sub", "Delete script - can't delete container",
substrs=["subcommand 'test-multi-sub' is not a user command"], error = True)
# You can't use command container delete to delete scripted commands:
self.expect("command container delete test-multi test-multi-sub welcome", "command container can't delete user commands",
substrs=["subcommand 'welcome' is not a container command"], error = True)
# Also make sure you can't alias on top of container commands:
self.expect("command alias test-multi process launch", "Tried to alias on top of a container command",
substrs=["'test-multi' is a user container command and cannot be overwritten."], error=True)
self.check_command_tree_exists()
# Also assert that we can't delete builtin commands:
self.expect("command script delete process launch", "Delete builtin command fails", substrs=["command 'process' is not a user command"], error=True)
# Now let's do the version that works
self.expect("command script delete test-multi test-multi-sub welcome", "Delete script command by path", substrs=["Deleted command: test-multi test-multi-sub welcome"])
# Now overwrite the sub-command, it should end up empty:
self.runCmd("command container add -h 'A different help string' -o test-multi test-multi-sub")
# welcome should be gone:
self.expect("test-multi test-multi-sub welcome friend", "did remove subcommand",
substrs=["'test-multi-sub' does not have any subcommands."], error=True)
# We should have the new help:
self.expect("help test-multi test-multi-sub", "help changed",
substrs=["A different help string"])
# Now try deleting commands.
self.runCmd("command container delete test-multi test-multi-sub")
self.expect("test-multi test-multi-sub", "Command is not active", error=True,
substrs = ["'test-multi' does not have any subcommands"])
self.expect("help test-multi", matching=False, substrs=["test-multi-sub"])
# Next the root command:
self.runCmd("command container delete test-multi")
self.expect("test-multi", "Root command gone", substrs=["'test-multi' is not a valid command."], error=True)

View File

@@ -0,0 +1,28 @@
from __future__ import print_function
import lldb
import sys
class WelcomeCommand(object):
def __init__(self, debugger, session_dict):
pass
def get_short_help(self):
return "Just a docstring for Welcome\nA command that says hello to LLDB users"
def __call__(self, debugger, args, exe_ctx, result):
print('Hello ' + args + ', welcome to LLDB', file=result)
return None
class WelcomeCommand2(object):
def __init__(self, debugger, session_dict):
pass
def get_short_help(self):
return "Just a docstring for the second Welcome\nA command that says hello to LLDB users"
def __call__(self, debugger, args, exe_ctx, result):
print('Hello ' + args + ', welcome again to LLDB', file=result)
return None

View File

@@ -9,10 +9,10 @@ class InvalidArgsCommandTestCase(TestBase):
@no_debug_info_test
def test_script_add(self):
self.expect("command script add 1 2", error=True,
substrs=["'command script add' requires one argument"])
substrs=["Path component: '1' not found"])
self.expect("command script add", error=True,
substrs=["'command script add' requires one argument"])
substrs=["'command script add' requires at least one argument"])
@no_debug_info_test
def test_script_clear(self):

View File

@@ -147,7 +147,7 @@ class CmdPythonTestCase(TestBase):
self.expect('my_command Blah', substrs=['Hello Blah, welcome to LLDB'])
self.runCmd(
'command script add my_command --class welcome.TargetnameCommand')
'command script add my_command -o --class welcome.TargetnameCommand')
self.expect('my_command', substrs=['a.out'])
self.runCmd("command script clear")

View File

@@ -1,3 +1,5 @@
#include <stdio.h>
int foo(char c) { return 1; }
int foo(signed char c) { return 2; }
int foo(unsigned char c) { return 3; }
@@ -6,5 +8,6 @@ int main() {
char c = 0;
signed char sc = 0;
unsigned char uc = 0;
printf("%d %d %d\n", foo(c), foo(sc), foo(uc));
return 0; // Break here
}

View File

@@ -510,7 +510,7 @@ class CommandLineCompletionTestCase(TestBase):
def test_command_script_delete(self):
self.runCmd("command script add -h test_desc -f none -s current usercmd1")
self.check_completion_with_desc('command script delete ', [['usercmd1', 'test_desc']])
self.check_completion_with_desc('command script delete ', [['usercmd1', '']])
def test_command_delete(self):
self.runCmd(r"command regex test_command s/^$/finish/ 's/([0-9]+)/frame select %1/'")

View File

@@ -1,10 +1,17 @@
add_lldb_unittest(InterpreterTests
TestCommandPaths.cpp
TestCompletion.cpp
TestOptionArgParser.cpp
TestOptionValue.cpp
TestOptionValueFileColonLine.cpp
LINK_LIBS
lldbInterpreter
lldbUtilityHelpers
)
lldbCore
lldbHost
lldbTarget
lldbSymbol
lldbUtility
lldbUtilityHelpers
lldbInterpreter
lldbPluginPlatformMacOSX
)

View File

@@ -0,0 +1,159 @@
//===-- ProcessEventDataTest.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 "Plugins/Platform/MacOSX/PlatformMacOSX.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/CommandObject.h"
#include "lldb/Interpreter/CommandObjectMultiword.h"
#include "lldb/Interpreter/CommandReturnObject.h"
#include "lldb/Utility/Args.h"
#include "lldb/Utility/Reproducer.h"
#include "lldb/Utility/Status.h"
#include "gtest/gtest.h"
using namespace lldb_private;
using namespace lldb_private::repro;
using namespace lldb;
namespace {
class VerifyUserMultiwordCmdPathTest : public ::testing::Test {
void SetUp() override {
llvm::cantFail(Reproducer::Initialize(ReproducerMode::Off, llvm::None));
FileSystem::Initialize();
HostInfo::Initialize();
PlatformMacOSX::Initialize();
}
void TearDown() override {
PlatformMacOSX::Terminate();
HostInfo::Terminate();
FileSystem::Terminate();
Reproducer::Terminate();
}
};
} // namespace
class CommandObjectLeaf : public CommandObjectParsed {
public:
CommandObjectLeaf(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "dummy subcommand leaf",
"Does nothing", "dummy subcommand leaf") {
SetIsUserCommand(true);
}
protected:
virtual bool DoExecute(Args &command, CommandReturnObject &result) {
result.SetStatus(eReturnStatusSuccessFinishResult);
result.AppendMessage("I did nothing");
return true;
}
};
class CommandObjectMultiwordSubDummy : public CommandObjectMultiword {
public:
CommandObjectMultiwordSubDummy(CommandInterpreter &interpreter)
: CommandObjectMultiword(interpreter, "dummy subcommand", "Does nothing",
"dummy subcommand") {
SetIsUserCommand(true);
LoadSubCommand("leaf", CommandObjectSP(new CommandObjectLeaf(interpreter)));
}
~CommandObjectMultiwordSubDummy() override = default;
};
class CommandObjectMultiwordDummy : public CommandObjectMultiword {
public:
CommandObjectMultiwordDummy(CommandInterpreter &interpreter)
: CommandObjectMultiword(interpreter, "dummy", "Does nothing", "dummy") {
SetIsUserCommand(true);
LoadSubCommand(
"subcommand",
CommandObjectSP(new CommandObjectMultiwordSubDummy(interpreter)));
}
~CommandObjectMultiwordDummy() override = default;
};
// Pass in the command path to args. If success is true, we make sure the MWC
// returned matches the test string. If success is false, we make sure the
// lookup error matches test_str.
void RunTest(CommandInterpreter &interp, const char *args, bool is_leaf,
bool success, const char *test_str) {
CommandObjectMultiword *multi_word_cmd = nullptr;
Args test_args(args);
Status error;
multi_word_cmd =
interp.VerifyUserMultiwordCmdPath(test_args, is_leaf, error);
if (success) {
ASSERT_NE(multi_word_cmd, nullptr);
ASSERT_TRUE(error.Success());
ASSERT_STREQ(multi_word_cmd->GetCommandName().str().c_str(), test_str);
} else {
ASSERT_EQ(multi_word_cmd, nullptr);
ASSERT_TRUE(error.Fail());
ASSERT_STREQ(error.AsCString(), test_str);
}
}
TEST_F(VerifyUserMultiwordCmdPathTest, TestErrors) {
DebuggerSP debugger_sp = Debugger::CreateInstance();
ASSERT_TRUE(debugger_sp);
CommandInterpreter &interp = debugger_sp->GetCommandInterpreter();
Status error;
bool success;
bool is_leaf;
// Test that we reject non-user path components:
success = false;
is_leaf = true;
RunTest(interp, "process launch", is_leaf, success,
"Path component: 'process' is not a user command");
// Test that we reject non-existent commands:
is_leaf = true;
success = false;
RunTest(interp, "wewouldnevernameacommandthis subcommand", is_leaf, success,
"Path component: 'wewouldnevernameacommandthis' not found");
// Now we have to add a multiword command, and then probe it.
error = interp.AddUserCommand(
"dummy", CommandObjectSP(new CommandObjectMultiwordDummy(interp)), true);
ASSERT_TRUE(error.Success());
// Now pass the correct path, and make sure we get back the right MWC.
is_leaf = false;
success = true;
RunTest(interp, "dummy subcommand", is_leaf, success, "dummy subcommand");
is_leaf = true;
RunTest(interp, "dummy subcommand", is_leaf, success, "dummy");
// If you tell us the last node is a leaf, we don't check that. Make sure
// that is true:
is_leaf = true;
success = true;
RunTest(interp, "dummy subcommand leaf", is_leaf, success,
"dummy subcommand");
// But we should fail if we say the last component is a multiword:
is_leaf = false;
success = false;
RunTest(interp, "dummy subcommand leaf", is_leaf, success,
"Path component: 'leaf' is not a container command");
// We should fail if we get the second path component wrong:
is_leaf = false;
success = false;
RunTest(interp, "dummy not-subcommand", is_leaf, success,
"Path component: 'not-subcommand' not found");
}