| //===-- 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/Host/StreamFile.h" | |
| #include "lldb/Interpreter/CommandHistory.h" | |
| #include "lldb/Interpreter/CommandInterpreter.h" | |
| #include "lldb/Interpreter/CommandOptionArgumentTable.h" | |
| #include "lldb/Interpreter/CommandReturnObject.h" | |
| #include "lldb/Interpreter/Interfaces/ScriptedCommandInterface.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 <memory> | |
| #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) { | |
| AddSimpleArgumentList(eArgTypeFilename); | |
| } | |
| ~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", | |
| 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. | |
| )" | |
| "If the alias abbreviation or the full alias command collides with another \ | |
| existing command, the command resolver will prefer to use the alias over any \ | |
| other command as far as there is only one alias command match."); | |
| 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", | |
| 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'", | |
| 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 = | |
| std::make_shared<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.AppendWarningWithFormatv( | |
| "overwriting existing definition for '{0}'", alias_command); | |
| } | |
| 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", | |
| 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", | |
| actual_command.c_str()); | |
| return false; | |
| } | |
| CommandObject *cmd_obj = command_obj_sp.get(); | |
| CommandObject *sub_cmd_obj = nullptr; | |
| OptionArgVectorSP option_arg_vector_sp = | |
| std::make_shared<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", | |
| 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.AppendWarningWithFormatv( | |
| "overwriting existing definition for '{0}'", alias_command); | |
| } | |
| 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) { | |
| AddSimpleArgumentList(eArgTypeAliasName); | |
| } | |
| ~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 *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", | |
| 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", | |
| args[0].c_str()); | |
| } else { | |
| result.AppendErrorWithFormat( | |
| "'%s' is a permanent debugger command and cannot be removed", | |
| 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'", args[0].c_str()); | |
| else | |
| result.AppendErrorWithFormat("'%s' is not an existing alias", | |
| 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) { | |
| AddSimpleArgumentList(eArgTypeCommandName); | |
| } | |
| ~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 { | |
| 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", | |
| 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/')"); | |
| AddSimpleArgumentList(eArgTypeSEDStylePair, eArgRepeatOptional); | |
| } | |
| ~CommandObjectCommandsAddRegex() override = default; | |
| protected: | |
| void IOHandlerActivated(IOHandler &io_handler, bool interactive) override { | |
| if (interactive) { | |
| if (lldb::LockableStreamFileSP output_sp = | |
| io_handler.GetOutputStreamFileSP()) { | |
| LockedStreamFile locked_stream = output_sp->Lock(); | |
| locked_stream.PutCString( | |
| "Enter one or more sed substitution commands in " | |
| "the form: 's/<regex>/<subst>/'.\nTerminate the " | |
| "substitution list with an empty line.\n"); | |
| } | |
| } | |
| } | |
| 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()) | |
| GetDebugger().GetAsyncOutputStream()->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(); | |
| result.SetStatus(eReturnStatusSuccessFinishNoResult); | |
| } | |
| } | |
| if (error.Fail()) { | |
| result.AppendError(error.AsCString()); | |
| } | |
| } | |
| Status AppendRegexSubstitution(const llvm::StringRef ®ex_sed, | |
| bool check_only) { | |
| Status error; | |
| if (!m_regex_cmd_up) { | |
| return Status::FromErrorStringWithFormat( | |
| "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) { | |
| return Status::FromErrorStringWithFormat( | |
| "regular expression substitution string is too short: '%.*s'", | |
| (int)regex_sed.size(), regex_sed.data()); | |
| return error; | |
| } | |
| if (regex_sed[0] != 's') { | |
| return Status::FromErrorStringWithFormat( | |
| "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) { | |
| return Status::FromErrorStringWithFormat( | |
| "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) { | |
| return Status::FromErrorStringWithFormat( | |
| "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) { | |
| return Status::FromErrorStringWithFormat( | |
| "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) { | |
| return Status::FromErrorStringWithFormat( | |
| "<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) { | |
| return Status::FromErrorStringWithFormat( | |
| "<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.GetOutputString().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, | |
| lldb::ScriptedCommandInterfaceSP cmd_interface_sp, | |
| ScriptedCommandSynchronicity synch, CompletionType completion_type) | |
| : CommandObjectRaw(interpreter, name), | |
| m_cmd_interface_sp(cmd_interface_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 (m_cmd_interface_sp) | |
| GetFlags().Set(m_cmd_interface_sp->GetFlags()); | |
| } | |
| ~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; } | |
| std::optional<std::string> GetRepeatCommand(Args &args, | |
| uint32_t index) override { | |
| if (!m_cmd_interface_sp) | |
| return std::nullopt; | |
| return m_cmd_interface_sp->GetRepeatCommand(args); | |
| } | |
| llvm::StringRef GetHelp() override { | |
| if (m_fetched_help_short) | |
| return CommandObjectRaw::GetHelp(); | |
| if (!m_cmd_interface_sp) | |
| return CommandObjectRaw::GetHelp(); | |
| std::string docstring; | |
| m_fetched_help_short = m_cmd_interface_sp->GetShortHelp(docstring); | |
| if (!docstring.empty()) | |
| SetHelp(docstring); | |
| return CommandObjectRaw::GetHelp(); | |
| } | |
| llvm::StringRef GetHelpLong() override { | |
| if (m_fetched_help_long) | |
| return CommandObjectRaw::GetHelpLong(); | |
| if (!m_cmd_interface_sp) | |
| return CommandObjectRaw::GetHelpLong(); | |
| std::string docstring; | |
| m_fetched_help_long = m_cmd_interface_sp->GetLongHelp(docstring); | |
| if (!docstring.empty()) | |
| SetHelpLong(docstring); | |
| return CommandObjectRaw::GetHelpLong(); | |
| } | |
| protected: | |
| void DoExecute(llvm::StringRef raw_command_line, | |
| CommandReturnObject &result) override { | |
| Status error; | |
| result.SetStatus(eReturnStatusInvalid); | |
| if (!m_cmd_interface_sp || | |
| !m_cmd_interface_sp->RunRawCommand(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.GetOutputString().empty()) | |
| result.SetStatus(eReturnStatusSuccessFinishNoResult); | |
| else | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| } | |
| } | |
| private: | |
| lldb::ScriptedCommandInterfaceSP m_cmd_interface_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(lldb::ScriptedCommandInterfaceSP cmd_interface_sp) | |
| : m_cmd_interface_sp(cmd_interface_sp) {} | |
| ~CommandOptions() override = default; | |
| Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, | |
| ExecutionContext *execution_context) override { | |
| Status error; | |
| if (!m_cmd_interface_sp) { | |
| return Status::FromErrorString( | |
| "SetOptionValue called with empty cmd_obj."); | |
| return error; | |
| } | |
| if (!m_options_definition_up) { | |
| return Status::FromErrorString( | |
| "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 = m_cmd_interface_sp->SetOptionValue( | |
| execution_context, long_option, option_arg); | |
| if (!success) | |
| return Status::FromErrorStringWithFormatv( | |
| "Error setting option: {0} to {1}", long_option, option_arg); | |
| return error; | |
| } | |
| void OptionParsingStarting(ExecutionContext *execution_context) override { | |
| if (!m_cmd_interface_sp) | |
| return; | |
| m_cmd_interface_sp->OptionParsingStarted(); | |
| } | |
| 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) { | |
| return Status::FromErrorStringWithFormatv( | |
| "0 is not a valid group for option {0}", counter); | |
| } | |
| usage_mask = (1 << (value - 1)); | |
| return error; | |
| } | |
| // Otherwise it has to be an array: | |
| StructuredData::Array *array_val = obj_sp->GetAsArray(); | |
| if (!array_val) { | |
| return Status::FromErrorStringWithFormatv( | |
| "required field is not a array for option {0}", counter); | |
| } | |
| // 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 = Status::FromErrorStringWithFormatv( | |
| "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 = Status::FromErrorStringWithFormatv( | |
| "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 = Status::FromErrorStringWithFormatv( | |
| "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 = Status::FromErrorStringWithFormatv( | |
| "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 = Status::FromErrorStringWithFormatv( | |
| "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 = Status::FromErrorStringWithFormatv( | |
| "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 = Status::FromErrorString( | |
| "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 = Status::FromErrorStringWithFormatv( | |
| "'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 = Status::FromErrorStringWithFormatv( | |
| "short_option field empty for " | |
| "option {0}", | |
| counter); | |
| return false; | |
| } else if (short_str.size() != 1) { | |
| error = Status::FromErrorStringWithFormatv( | |
| "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 = Status::FromErrorStringWithFormatv( | |
| "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 = Status::FromErrorStringWithFormatv( | |
| "Value type must be an unsigned " | |
| "integer"); | |
| return false; | |
| } | |
| uint64_t val_type = uint_val->GetValue(); | |
| if (val_type >= eArgTypeLastArg) { | |
| error = | |
| Status::FromErrorStringWithFormatv("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 = Status::FromErrorStringWithFormatv( | |
| "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 = Status::FromErrorStringWithFormatv( | |
| "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: | |
| obj_sp = opt_dict->GetValueForKey("help"); | |
| if (!obj_sp) { | |
| error = Status::FromErrorStringWithFormatv( | |
| "required usage missing from option " | |
| "{0}", | |
| counter); | |
| return false; | |
| } | |
| llvm::StringRef usage_stref; | |
| usage_stref = obj_sp->GetStringValue(); | |
| if (usage_stref.empty()) { | |
| error = Status::FromErrorStringWithFormatv( | |
| "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 = Status::FromErrorStringWithFormatv( | |
| "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 = Status::FromErrorStringWithFormatv( | |
| "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 = Status::FromErrorStringWithFormatv( | |
| "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 = Status::FromErrorStringWithFormatv( | |
| "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; | |
| } | |
| size_t GetNumOptions() { return m_num_options; } | |
| void PrepareOptionsForCompletion(CompletionRequest &request, | |
| OptionElementVector &option_vec, | |
| ExecutionContext *exe_ctx) { | |
| // I'm not sure if we'll get into trouble doing an option parsing start | |
| // and end in this context. If so, then I'll have to directly tell the | |
| // scripter to do this. | |
| OptionParsingStarting(exe_ctx); | |
| auto opt_defs = GetDefinitions(); | |
| // Iterate through the options we found so far, and push them into | |
| // the scripted side. | |
| for (auto option_elem : option_vec) { | |
| int cur_defs_index = option_elem.opt_defs_index; | |
| // If we don't recognize this option we can't set it. | |
| if (cur_defs_index == OptionArgElement::eUnrecognizedArg || | |
| cur_defs_index == OptionArgElement::eBareDash || | |
| cur_defs_index == OptionArgElement::eBareDoubleDash) | |
| continue; | |
| bool option_has_arg = opt_defs[cur_defs_index].option_has_arg; | |
| llvm::StringRef cur_arg_value; | |
| if (option_has_arg) { | |
| int cur_arg_pos = option_elem.opt_arg_pos; | |
| if (cur_arg_pos != OptionArgElement::eUnrecognizedArg && | |
| cur_arg_pos != OptionArgElement::eBareDash && | |
| cur_arg_pos != OptionArgElement::eBareDoubleDash) { | |
| cur_arg_value = | |
| request.GetParsedLine().GetArgumentAtIndex(cur_arg_pos); | |
| } | |
| } | |
| SetOptionValue(cur_defs_index, cur_arg_value, exe_ctx); | |
| } | |
| OptionParsingFinished(exe_ctx); | |
| } | |
| void | |
| ProcessCompletionDict(CompletionRequest &request, | |
| StructuredData::DictionarySP &completion_dict_sp) { | |
| // We don't know how to process an empty completion dict, our callers have | |
| // to do that. | |
| assert(completion_dict_sp && "Must have valid completion dict"); | |
| // First handle the case of a single completion: | |
| llvm::StringRef completion; | |
| // If the dictionary has one element "no-completion" then we return here | |
| if (completion_dict_sp->GetValueForKeyAsString("no-completion", | |
| completion)) | |
| return; | |
| if (completion_dict_sp->GetValueForKeyAsString("completion", | |
| completion)) { | |
| llvm::StringRef mode_str; | |
| CompletionMode mode = CompletionMode::Normal; | |
| if (completion_dict_sp->GetValueForKeyAsString("mode", mode_str)) { | |
| if (mode_str == "complete") | |
| mode = CompletionMode::Normal; | |
| else if (mode_str == "partial") | |
| mode = CompletionMode::Partial; | |
| else { | |
| // FIXME - how do I report errors here? | |
| return; | |
| } | |
| } | |
| request.AddCompletion(completion, "", mode); | |
| return; | |
| } | |
| // The completions are required, the descriptions are not: | |
| StructuredData::Array *completions; | |
| StructuredData::Array *descriptions; | |
| if (completion_dict_sp->GetValueForKeyAsArray("values", completions)) { | |
| completion_dict_sp->GetValueForKeyAsArray("descriptions", descriptions); | |
| size_t num_completions = completions->GetSize(); | |
| for (size_t idx = 0; idx < num_completions; idx++) { | |
| auto val = completions->GetItemAtIndexAsString(idx); | |
| if (!val) | |
| // FIXME: How do I report this error? | |
| return; | |
| if (descriptions) { | |
| auto desc = descriptions->GetItemAtIndexAsString(idx); | |
| request.AddCompletion(*val, desc ? *desc : ""); | |
| } else | |
| request.AddCompletion(*val); | |
| } | |
| } | |
| } | |
| void | |
| HandleOptionArgumentCompletion(lldb_private::CompletionRequest &request, | |
| OptionElementVector &option_vec, | |
| int opt_element_index, | |
| CommandInterpreter &interpreter) override { | |
| if (!m_cmd_interface_sp) | |
| return; | |
| ExecutionContext exe_ctx = interpreter.GetExecutionContext(); | |
| PrepareOptionsForCompletion(request, option_vec, &exe_ctx); | |
| auto defs = GetDefinitions(); | |
| size_t defs_index = option_vec[opt_element_index].opt_defs_index; | |
| llvm::StringRef option_name = defs[defs_index].long_option; | |
| bool is_enum = defs[defs_index].enum_values.size() != 0; | |
| if (option_name.empty()) | |
| return; | |
| // If this is an enum, we don't call the custom completer, just let the | |
| // regular option completer handle that: | |
| StructuredData::DictionarySP completion_dict_sp; | |
| if (!is_enum) | |
| completion_dict_sp = m_cmd_interface_sp->HandleOptionArgumentCompletion( | |
| option_name, request.GetCursorCharPos()); | |
| if (!completion_dict_sp) { | |
| Options::HandleOptionArgumentCompletion(request, option_vec, | |
| opt_element_index, interpreter); | |
| return; | |
| } | |
| ProcessCompletionDict(request, completion_dict_sp); | |
| } | |
| 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; | |
| lldb::ScriptedCommandInterfaceSP m_cmd_interface_sp; | |
| static std::unordered_set<std::string> g_string_storer; | |
| }; | |
| public: | |
| static CommandObjectSP | |
| Create(CommandInterpreter &interpreter, std::string name, | |
| lldb::ScriptedCommandInterfaceSP cmd_interface_sp, | |
| ScriptedCommandSynchronicity synch, CommandReturnObject &result) { | |
| CommandObjectSP new_cmd_sp(new CommandObjectScriptingObjectParsed( | |
| interpreter, name, cmd_interface_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, | |
| lldb::ScriptedCommandInterfaceSP cmd_interface_sp, | |
| ScriptedCommandSynchronicity synch) | |
| : CommandObjectParsed(interpreter, name.c_str()), | |
| m_cmd_interface_sp(cmd_interface_sp), m_synchro(synch), | |
| m_options(cmd_interface_sp), m_fetched_help_short(false), | |
| m_fetched_help_long(false) { | |
| StreamString stream; | |
| if (!m_cmd_interface_sp) { | |
| m_options_error = Status::FromErrorString("No script interpreter"); | |
| return; | |
| } | |
| // Set the flags: | |
| GetFlags().Set(m_cmd_interface_sp->GetFlags()); | |
| // Now set up the options definitions from the options: | |
| StructuredData::ObjectSP options_object_sp = | |
| m_cmd_interface_sp->GetOptionsDefinition(); | |
| // 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 = Status::FromErrorString("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 = | |
| m_cmd_interface_sp->GetArgumentsDefinition(); | |
| if (args_object_sp) { | |
| StructuredData::Array *args_array = args_object_sp->GetAsArray(); | |
| if (!args_array) { | |
| m_args_error = | |
| Status::FromErrorString("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 = Status::FromErrorStringWithFormatv( | |
| "element {} of arguments list element {}: {}", 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 = | |
| Status::FromErrorStringWithFormatv("Argument definition element " | |
| "{0} is not an array", | |
| counter); | |
| return false; | |
| } | |
| args_array->ForEach(args_adder); | |
| if (m_args_error.Fail()) | |
| return false; | |
| if (this_entry.empty()) { | |
| m_args_error = | |
| Status::FromErrorStringWithFormatv("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.Clone(); } | |
| Status GetArgsError() { return m_args_error.Clone(); } | |
| bool WantsCompletion() override { return true; } | |
| private: | |
| void PrepareOptionsForCompletion(CompletionRequest &request, | |
| OptionElementVector &option_vec) { | |
| // First, we have to tell the Scripted side to set the values in its | |
| // option store, then we call into the handle_completion passing in | |
| // an array of the args, the arg index and the cursor position in the arg. | |
| // We want the script side to have a chance to clear its state, so tell | |
| // it argument parsing has started: | |
| Options *options = GetOptions(); | |
| // If there are not options, this will be nullptr, and in that case we | |
| // can just skip setting the options on the scripted side: | |
| if (options) | |
| m_options.PrepareOptionsForCompletion(request, option_vec, &m_exe_ctx); | |
| } | |
| public: | |
| void HandleArgumentCompletion(CompletionRequest &request, | |
| OptionElementVector &option_vec) override { | |
| if (!m_cmd_interface_sp) | |
| return; | |
| // Set up the options values on the scripted side: | |
| PrepareOptionsForCompletion(request, option_vec); | |
| // Now we have to make up the argument list. | |
| // The ParseForCompletion only identifies tokens in the m_parsed_line | |
| // it doesn't remove the options leaving only the args as it does for | |
| // the regular Parse, so we have to filter out the option ones using the | |
| // option_element_vector: | |
| Options *options = GetOptions(); | |
| auto defs = options ? options->GetDefinitions() | |
| : llvm::ArrayRef<OptionDefinition>(); | |
| std::unordered_set<size_t> option_slots; | |
| for (const auto &elem : option_vec) { | |
| if (elem.opt_defs_index == -1) | |
| continue; | |
| option_slots.insert(elem.opt_pos); | |
| if (defs[elem.opt_defs_index].option_has_arg) | |
| option_slots.insert(elem.opt_arg_pos); | |
| } | |
| std::vector<std::string> args_vec; | |
| Args &args = request.GetParsedLine(); | |
| size_t num_args = args.GetArgumentCount(); | |
| size_t cursor_idx = request.GetCursorIndex(); | |
| size_t args_elem_pos = cursor_idx; | |
| for (size_t idx = 0; idx < num_args; idx++) { | |
| if (option_slots.count(idx) == 0) | |
| args_vec.push_back(args[idx].ref().str()); | |
| else if (idx < cursor_idx) | |
| args_elem_pos--; | |
| } | |
| StructuredData::DictionarySP completion_dict_sp = | |
| m_cmd_interface_sp->HandleArgumentCompletion( | |
| args_vec, args_elem_pos, request.GetCursorCharPos()); | |
| if (!completion_dict_sp) { | |
| CommandObject::HandleArgumentCompletion(request, option_vec); | |
| return; | |
| } | |
| m_options.ProcessCompletionDict(request, completion_dict_sp); | |
| } | |
| bool IsRemovable() const override { return true; } | |
| ScriptedCommandSynchronicity GetSynchronicity() { return m_synchro; } | |
| std::optional<std::string> GetRepeatCommand(Args &args, | |
| uint32_t index) override { | |
| if (!m_cmd_interface_sp) | |
| return std::nullopt; | |
| return m_cmd_interface_sp->GetRepeatCommand(args); | |
| } | |
| llvm::StringRef GetHelp() override { | |
| if (m_fetched_help_short) | |
| return CommandObjectParsed::GetHelp(); | |
| if (!m_cmd_interface_sp) | |
| return CommandObjectParsed::GetHelp(); | |
| std::string docstring; | |
| m_fetched_help_short = m_cmd_interface_sp->GetShortHelp(docstring); | |
| if (!docstring.empty()) | |
| SetHelp(docstring); | |
| return CommandObjectParsed::GetHelp(); | |
| } | |
| llvm::StringRef GetHelpLong() override { | |
| if (m_fetched_help_long) | |
| return CommandObjectParsed::GetHelpLong(); | |
| if (!m_cmd_interface_sp) | |
| return CommandObjectParsed::GetHelpLong(); | |
| std::string docstring; | |
| m_fetched_help_long = m_cmd_interface_sp->GetLongHelp(docstring); | |
| if (!docstring.empty()) | |
| SetHelpLong(docstring); | |
| return CommandObjectParsed::GetHelpLong(); | |
| } | |
| Options *GetOptions() override { | |
| // CommandObjectParsed requires that a command with no options return | |
| // nullptr. | |
| if (m_options.GetNumOptions() == 0) | |
| return nullptr; | |
| return &m_options; | |
| } | |
| protected: | |
| void DoExecute(Args &args, CommandReturnObject &result) override { | |
| Status error; | |
| result.SetStatus(eReturnStatusInvalid); | |
| if (!m_cmd_interface_sp || !m_cmd_interface_sp->RunParsedCommand( | |
| 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.GetOutputString().empty()) | |
| result.SetStatus(eReturnStatusSuccessFinishNoResult); | |
| else | |
| result.SetStatus(eReturnStatusSuccessFinishResult); | |
| } | |
| } | |
| } | |
| private: | |
| lldb::ScriptedCommandInterfaceSP m_cmd_interface_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) { | |
| AddSimpleArgumentList(eArgTypeFilename, eArgRepeatPlus); | |
| } | |
| ~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") { | |
| AddSimpleArgumentList(eArgTypeCommand, eArgRepeatPlus); | |
| } | |
| ~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()) | |
| return Status::FromErrorStringWithFormat( | |
| "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()) | |
| return Status::FromErrorStringWithFormat( | |
| "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 { | |
| if (interactive) { | |
| if (lldb::LockableStreamFileSP output_sp = | |
| io_handler.GetOutputStreamFileSP()) { | |
| LockedStreamFile locked_stream = output_sp->Lock(); | |
| locked_stream.PutCString(g_python_command_instructions); | |
| } | |
| } | |
| } | |
| void IOHandlerInputComplete(IOHandler &io_handler, | |
| std::string &data) override { | |
| LockableStreamFileSP 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()) { | |
| LockedStreamFile locked_stream = error_sp->Lock(); | |
| locked_stream.Printf( | |
| "error: unable to obtain a function name, didn't " | |
| "add python command.\n"); | |
| } 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()) { | |
| LockedStreamFile locked_stream = error_sp->Lock(); | |
| locked_stream.Printf( | |
| "error: unable to add selected command: '%s'", | |
| error.AsCString()); | |
| } | |
| } else { | |
| llvm::Error llvm_error = m_container->LoadUserSubcommand( | |
| m_cmd_name, command_obj_sp, m_overwrite); | |
| if (llvm_error) { | |
| LockedStreamFile locked_stream = error_sp->Lock(); | |
| locked_stream.Printf( | |
| "error: unable to add selected command: '%s'", | |
| llvm::toString(std::move(llvm_error)).c_str()); | |
| } | |
| } | |
| } | |
| } else { | |
| LockedStreamFile locked_stream = error_sp->Lock(); | |
| locked_stream.Printf( | |
| "error: unable to create function, didn't add python command\n"); | |
| } | |
| } else { | |
| LockedStreamFile locked_stream = error_sp->Lock(); | |
| locked_stream.Printf( | |
| "error: empty function, didn't add python command\n"); | |
| } | |
| } else { | |
| LockedStreamFile locked_stream = error_sp->Lock(); | |
| locked_stream.Printf( | |
| "error: script interpreter missing, didn't add python command\n"); | |
| } | |
| 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 | |
| // Still gathering input; the IOHandler will set the final status. | |
| result.SetStatus(eReturnStatusStarted); | |
| return; | |
| } | |
| CommandObjectSP new_cmd_sp; | |
| if (m_options.m_class_name.empty()) { | |
| new_cmd_sp = std::make_shared<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; | |
| } | |
| lldb::ScriptedCommandInterfaceSP cmd_interface_sp = | |
| interpreter->CreateScriptedCommandInterface(); | |
| if (!cmd_interface_sp) { | |
| result.AppendError("cannot create ScriptedCommandInterface"); | |
| return; | |
| } | |
| auto obj_or_err = cmd_interface_sp->CreatePluginObject( | |
| m_options.m_class_name, GetDebugger().shared_from_this()); | |
| if (!obj_or_err) { | |
| result.AppendErrorWithFormatv("cannot create helper object for: " | |
| "'{0}': {1}", | |
| m_options.m_class_name, | |
| llvm::toString(obj_or_err.takeError())); | |
| return; | |
| } | |
| if (m_options.m_parsed_command) { | |
| new_cmd_sp = CommandObjectScriptingObjectParsed::Create( | |
| m_interpreter, m_cmd_name, cmd_interface_sp, m_synchronicity, | |
| result); | |
| if (!result.Succeeded()) | |
| return; | |
| } else | |
| new_cmd_sp = std::make_shared<CommandObjectScriptingObjectRaw>( | |
| m_interpreter, m_cmd_name, cmd_interface_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) { | |
| AddSimpleArgumentList(eArgTypeCommand, eArgRepeatPlus); | |
| } | |
| ~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") { | |
| AddSimpleArgumentList(eArgTypeCommand, eArgRepeatPlus); | |
| } | |
| ~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") { | |
| AddSimpleArgumentList(eArgTypeCommand, eArgRepeatPlus); | |
| } | |
| ~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; |