1 //===-- CommandObjectCommands.cpp -------------------------------*- C++ -*-===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 // C Includes 11 // C++ Includes 12 // Other libraries and framework includes 13 #include "llvm/ADT/StringRef.h" 14 15 // Project includes 16 #include "CommandObjectCommands.h" 17 #include "CommandObjectHelp.h" 18 #include "lldb/Core/Debugger.h" 19 #include "lldb/Core/IOHandler.h" 20 #include "lldb/Host/OptionParser.h" 21 #include "lldb/Interpreter/CommandHistory.h" 22 #include "lldb/Interpreter/CommandInterpreter.h" 23 #include "lldb/Interpreter/CommandObjectRegexCommand.h" 24 #include "lldb/Interpreter/CommandReturnObject.h" 25 #include "lldb/Interpreter/OptionArgParser.h" 26 #include "lldb/Interpreter/OptionValueBoolean.h" 27 #include "lldb/Interpreter/OptionValueString.h" 28 #include "lldb/Interpreter/OptionValueUInt64.h" 29 #include "lldb/Interpreter/Options.h" 30 #include "lldb/Interpreter/ScriptInterpreter.h" 31 #include "lldb/Utility/Args.h" 32 #include "lldb/Utility/StringList.h" 33 34 using namespace lldb; 35 using namespace lldb_private; 36 37 //------------------------------------------------------------------------- 38 // CommandObjectCommandsSource 39 //------------------------------------------------------------------------- 40 41 static OptionDefinition g_history_options[] = { 42 // clang-format off 43 { LLDB_OPT_SET_1, false, "count", 'c', OptionParser::eRequiredArgument, nullptr, nullptr, 0, eArgTypeUnsignedInteger, "How many history commands to print." }, 44 { LLDB_OPT_SET_1, false, "start-index", 's', OptionParser::eRequiredArgument, nullptr, nullptr, 0, eArgTypeUnsignedInteger, "Index at which to start printing history commands (or end to mean tail mode)." }, 45 { LLDB_OPT_SET_1, false, "end-index", 'e', OptionParser::eRequiredArgument, nullptr, nullptr, 0, eArgTypeUnsignedInteger, "Index at which to stop printing history commands." }, 46 { LLDB_OPT_SET_2, false, "clear", 'C', OptionParser::eNoArgument, nullptr, nullptr, 0, eArgTypeBoolean, "Clears the current command history." }, 47 // clang-format on 48 }; 49 50 class CommandObjectCommandsHistory : public CommandObjectParsed { 51 public: 52 CommandObjectCommandsHistory(CommandInterpreter &interpreter) 53 : CommandObjectParsed(interpreter, "command history", 54 "Dump the history of commands in this session.\n" 55 "Commands in the history list can be run again " 56 "using \"!<INDEX>\". \"!-<OFFSET>\" will re-run " 57 "the command that is <OFFSET> commands from the end" 58 " of the list (counting the current command).", 59 nullptr), 60 m_options() {} 61 62 ~CommandObjectCommandsHistory() override = default; 63 64 Options *GetOptions() override { return &m_options; } 65 66 protected: 67 class CommandOptions : public Options { 68 public: 69 CommandOptions() 70 : Options(), m_start_idx(0), m_stop_idx(0), m_count(0), m_clear(false) { 71 } 72 73 ~CommandOptions() override = default; 74 75 Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, 76 ExecutionContext *execution_context) override { 77 Status error; 78 const int short_option = m_getopt_table[option_idx].val; 79 80 switch (short_option) { 81 case 'c': 82 error = m_count.SetValueFromString(option_arg, eVarSetOperationAssign); 83 break; 84 case 's': 85 if (option_arg == "end") { 86 m_start_idx.SetCurrentValue(UINT64_MAX); 87 m_start_idx.SetOptionWasSet(); 88 } else 89 error = m_start_idx.SetValueFromString(option_arg, 90 eVarSetOperationAssign); 91 break; 92 case 'e': 93 error = 94 m_stop_idx.SetValueFromString(option_arg, eVarSetOperationAssign); 95 break; 96 case 'C': 97 m_clear.SetCurrentValue(true); 98 m_clear.SetOptionWasSet(); 99 break; 100 default: 101 error.SetErrorStringWithFormat("unrecognized option '%c'", 102 short_option); 103 break; 104 } 105 106 return error; 107 } 108 109 void OptionParsingStarting(ExecutionContext *execution_context) override { 110 m_start_idx.Clear(); 111 m_stop_idx.Clear(); 112 m_count.Clear(); 113 m_clear.Clear(); 114 } 115 116 llvm::ArrayRef<OptionDefinition> GetDefinitions() override { 117 return llvm::makeArrayRef(g_history_options); 118 } 119 120 // Instance variables to hold the values for command options. 121 122 OptionValueUInt64 m_start_idx; 123 OptionValueUInt64 m_stop_idx; 124 OptionValueUInt64 m_count; 125 OptionValueBoolean m_clear; 126 }; 127 128 bool DoExecute(Args &command, CommandReturnObject &result) override { 129 if (m_options.m_clear.GetCurrentValue() && 130 m_options.m_clear.OptionWasSet()) { 131 m_interpreter.GetCommandHistory().Clear(); 132 result.SetStatus(lldb::eReturnStatusSuccessFinishNoResult); 133 } else { 134 if (m_options.m_start_idx.OptionWasSet() && 135 m_options.m_stop_idx.OptionWasSet() && 136 m_options.m_count.OptionWasSet()) { 137 result.AppendError("--count, --start-index and --end-index cannot be " 138 "all specified in the same invocation"); 139 result.SetStatus(lldb::eReturnStatusFailed); 140 } else { 141 std::pair<bool, uint64_t> start_idx( 142 m_options.m_start_idx.OptionWasSet(), 143 m_options.m_start_idx.GetCurrentValue()); 144 std::pair<bool, uint64_t> stop_idx( 145 m_options.m_stop_idx.OptionWasSet(), 146 m_options.m_stop_idx.GetCurrentValue()); 147 std::pair<bool, uint64_t> count(m_options.m_count.OptionWasSet(), 148 m_options.m_count.GetCurrentValue()); 149 150 const CommandHistory &history(m_interpreter.GetCommandHistory()); 151 152 if (start_idx.first && start_idx.second == UINT64_MAX) { 153 if (count.first) { 154 start_idx.second = history.GetSize() - count.second; 155 stop_idx.second = history.GetSize() - 1; 156 } else if (stop_idx.first) { 157 start_idx.second = stop_idx.second; 158 stop_idx.second = history.GetSize() - 1; 159 } else { 160 start_idx.second = 0; 161 stop_idx.second = history.GetSize() - 1; 162 } 163 } else { 164 if (!start_idx.first && !stop_idx.first && !count.first) { 165 start_idx.second = 0; 166 stop_idx.second = history.GetSize() - 1; 167 } else if (start_idx.first) { 168 if (count.first) { 169 stop_idx.second = start_idx.second + count.second - 1; 170 } else if (!stop_idx.first) { 171 stop_idx.second = history.GetSize() - 1; 172 } 173 } else if (stop_idx.first) { 174 if (count.first) { 175 if (stop_idx.second >= count.second) 176 start_idx.second = stop_idx.second - count.second + 1; 177 else 178 start_idx.second = 0; 179 } 180 } else /* if (count.first) */ 181 { 182 start_idx.second = 0; 183 stop_idx.second = count.second - 1; 184 } 185 } 186 history.Dump(result.GetOutputStream(), start_idx.second, 187 stop_idx.second); 188 } 189 } 190 return result.Succeeded(); 191 } 192 193 CommandOptions m_options; 194 }; 195 196 //------------------------------------------------------------------------- 197 // CommandObjectCommandsSource 198 //------------------------------------------------------------------------- 199 200 static OptionDefinition g_source_options[] = { 201 // clang-format off 202 { LLDB_OPT_SET_ALL, false, "stop-on-error", 'e', OptionParser::eRequiredArgument, nullptr, nullptr, 0, eArgTypeBoolean, "If true, stop executing commands on error." }, 203 { LLDB_OPT_SET_ALL, false, "stop-on-continue", 'c', OptionParser::eRequiredArgument, nullptr, nullptr, 0, eArgTypeBoolean, "If true, stop executing commands on continue." }, 204 { LLDB_OPT_SET_ALL, false, "silent-run", 's', OptionParser::eRequiredArgument, nullptr, nullptr, 0, eArgTypeBoolean, "If true don't echo commands while executing." }, 205 // clang-format on 206 }; 207 208 class CommandObjectCommandsSource : public CommandObjectParsed { 209 public: 210 CommandObjectCommandsSource(CommandInterpreter &interpreter) 211 : CommandObjectParsed( 212 interpreter, "command source", 213 "Read and execute LLDB commands from the file <filename>.", 214 nullptr), 215 m_options() { 216 CommandArgumentEntry arg; 217 CommandArgumentData file_arg; 218 219 // Define the first (and only) variant of this arg. 220 file_arg.arg_type = eArgTypeFilename; 221 file_arg.arg_repetition = eArgRepeatPlain; 222 223 // There is only one variant this argument could be; put it into the 224 // argument entry. 225 arg.push_back(file_arg); 226 227 // Push the data for the first argument into the m_arguments vector. 228 m_arguments.push_back(arg); 229 } 230 231 ~CommandObjectCommandsSource() override = default; 232 233 const char *GetRepeatCommand(Args ¤t_command_args, 234 uint32_t index) override { 235 return ""; 236 } 237 238 int HandleArgumentCompletion( 239 CompletionRequest &request, 240 OptionElementVector &opt_element_vector) override { 241 CommandCompletions::InvokeCommonCompletionCallbacks( 242 GetCommandInterpreter(), CommandCompletions::eDiskFileCompletion, 243 request, nullptr); 244 return request.GetMatches().GetSize(); 245 } 246 247 Options *GetOptions() override { return &m_options; } 248 249 protected: 250 class CommandOptions : public Options { 251 public: 252 CommandOptions() 253 : Options(), m_stop_on_error(true), m_silent_run(false), 254 m_stop_on_continue(true) {} 255 256 ~CommandOptions() override = default; 257 258 Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, 259 ExecutionContext *execution_context) override { 260 Status error; 261 const int short_option = m_getopt_table[option_idx].val; 262 263 switch (short_option) { 264 case 'e': 265 error = m_stop_on_error.SetValueFromString(option_arg); 266 break; 267 268 case 'c': 269 error = m_stop_on_continue.SetValueFromString(option_arg); 270 break; 271 272 case 's': 273 error = m_silent_run.SetValueFromString(option_arg); 274 break; 275 276 default: 277 error.SetErrorStringWithFormat("unrecognized option '%c'", 278 short_option); 279 break; 280 } 281 282 return error; 283 } 284 285 void OptionParsingStarting(ExecutionContext *execution_context) override { 286 m_stop_on_error.Clear(); 287 m_silent_run.Clear(); 288 m_stop_on_continue.Clear(); 289 } 290 291 llvm::ArrayRef<OptionDefinition> GetDefinitions() override { 292 return llvm::makeArrayRef(g_source_options); 293 } 294 295 // Instance variables to hold the values for command options. 296 297 OptionValueBoolean m_stop_on_error; 298 OptionValueBoolean m_silent_run; 299 OptionValueBoolean m_stop_on_continue; 300 }; 301 302 bool DoExecute(Args &command, CommandReturnObject &result) override { 303 if (command.GetArgumentCount() != 1) { 304 result.AppendErrorWithFormat( 305 "'%s' takes exactly one executable filename argument.\n", 306 GetCommandName().str().c_str()); 307 result.SetStatus(eReturnStatusFailed); 308 return false; 309 } 310 311 FileSpec cmd_file(command[0].ref, true); 312 ExecutionContext *exe_ctx = nullptr; // Just use the default context. 313 314 // If any options were set, then use them 315 if (m_options.m_stop_on_error.OptionWasSet() || 316 m_options.m_silent_run.OptionWasSet() || 317 m_options.m_stop_on_continue.OptionWasSet()) { 318 // Use user set settings 319 CommandInterpreterRunOptions options; 320 options.SetStopOnContinue(m_options.m_stop_on_continue.GetCurrentValue()); 321 options.SetStopOnError(m_options.m_stop_on_error.GetCurrentValue()); 322 options.SetEchoCommands(!m_options.m_silent_run.GetCurrentValue()); 323 options.SetPrintResults(!m_options.m_silent_run.GetCurrentValue()); 324 325 m_interpreter.HandleCommandsFromFile(cmd_file, exe_ctx, options, result); 326 } else { 327 // No options were set, inherit any settings from nested "command source" 328 // commands, or set to sane default settings... 329 CommandInterpreterRunOptions options; 330 m_interpreter.HandleCommandsFromFile(cmd_file, exe_ctx, options, result); 331 } 332 return result.Succeeded(); 333 } 334 335 CommandOptions m_options; 336 }; 337 338 #pragma mark CommandObjectCommandsAlias 339 //------------------------------------------------------------------------- 340 // CommandObjectCommandsAlias 341 //------------------------------------------------------------------------- 342 343 static OptionDefinition g_alias_options[] = { 344 // clang-format off 345 { LLDB_OPT_SET_ALL, false, "help", 'h', OptionParser::eRequiredArgument, nullptr, nullptr, 0, eArgTypeHelpText, "Help text for this command" }, 346 { LLDB_OPT_SET_ALL, false, "long-help", 'H', OptionParser::eRequiredArgument, nullptr, nullptr, 0, eArgTypeHelpText, "Long help text for this command" }, 347 // clang-format on 348 }; 349 350 static const char *g_python_command_instructions = 351 "Enter your Python command(s). Type 'DONE' to end.\n" 352 "You must define a Python function with this signature:\n" 353 "def my_command_impl(debugger, args, result, internal_dict):\n"; 354 355 class CommandObjectCommandsAlias : public CommandObjectRaw { 356 protected: 357 class CommandOptions : public OptionGroup { 358 public: 359 CommandOptions() : OptionGroup(), m_help(), m_long_help() {} 360 361 ~CommandOptions() override = default; 362 363 llvm::ArrayRef<OptionDefinition> GetDefinitions() override { 364 return llvm::makeArrayRef(g_alias_options); 365 } 366 367 Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_value, 368 ExecutionContext *execution_context) override { 369 Status error; 370 371 const int short_option = GetDefinitions()[option_idx].short_option; 372 std::string option_str(option_value); 373 374 switch (short_option) { 375 case 'h': 376 m_help.SetCurrentValue(option_str); 377 m_help.SetOptionWasSet(); 378 break; 379 380 case 'H': 381 m_long_help.SetCurrentValue(option_str); 382 m_long_help.SetOptionWasSet(); 383 break; 384 385 default: 386 error.SetErrorStringWithFormat("invalid short option character '%c'", 387 short_option); 388 break; 389 } 390 391 return error; 392 } 393 394 void OptionParsingStarting(ExecutionContext *execution_context) override { 395 m_help.Clear(); 396 m_long_help.Clear(); 397 } 398 399 OptionValueString m_help; 400 OptionValueString m_long_help; 401 }; 402 403 OptionGroupOptions m_option_group; 404 CommandOptions m_command_options; 405 406 public: 407 Options *GetOptions() override { return &m_option_group; } 408 409 CommandObjectCommandsAlias(CommandInterpreter &interpreter) 410 : CommandObjectRaw( 411 interpreter, "command alias", 412 "Define a custom command in terms of an existing command."), 413 m_option_group(), m_command_options() { 414 m_option_group.Append(&m_command_options); 415 m_option_group.Finalize(); 416 417 SetHelpLong( 418 "'alias' allows the user to create a short-cut or abbreviation for long \ 419 commands, multi-word commands, and commands that take particular options. \ 420 Below are some simple examples of how one might use the 'alias' command:" 421 R"( 422 423 (lldb) command alias sc script 424 425 Creates the abbreviation 'sc' for the 'script' command. 426 427 (lldb) command alias bp breakpoint 428 429 )" 430 " Creates the abbreviation 'bp' for the 'breakpoint' command. Since \ 431 breakpoint commands are two-word commands, the user would still need to \ 432 enter the second word after 'bp', e.g. 'bp enable' or 'bp delete'." 433 R"( 434 435 (lldb) command alias bpl breakpoint list 436 437 Creates the abbreviation 'bpl' for the two-word command 'breakpoint list'. 438 439 )" 440 "An alias can include some options for the command, with the values either \ 441 filled in at the time the alias is created, or specified as positional \ 442 arguments, to be filled in when the alias is invoked. The following example \ 443 shows how to create aliases with options:" 444 R"( 445 446 (lldb) command alias bfl breakpoint set -f %1 -l %2 447 448 )" 449 " Creates the abbreviation 'bfl' (for break-file-line), with the -f and -l \ 450 options already part of the alias. So if the user wants to set a breakpoint \ 451 by file and line without explicitly having to use the -f and -l options, the \ 452 user can now use 'bfl' instead. The '%1' and '%2' are positional placeholders \ 453 for the actual arguments that will be passed when the alias command is used. \ 454 The number in the placeholder refers to the position/order the actual value \ 455 occupies when the alias is used. All the occurrences of '%1' in the alias \ 456 will be replaced with the first argument, all the occurrences of '%2' in the \ 457 alias will be replaced with the second argument, and so on. This also allows \ 458 actual arguments to be used multiple times within an alias (see 'process \ 459 launch' example below)." 460 R"( 461 462 )" 463 "Note: the positional arguments must substitute as whole words in the resultant \ 464 command, so you can't at present do something like this to append the file extension \ 465 \".cpp\":" 466 R"( 467 468 (lldb) command alias bcppfl breakpoint set -f %1.cpp -l %2 469 470 )" 471 "For more complex aliasing, use the \"command regex\" command instead. In the \ 472 'bfl' case above, the actual file value will be filled in with the first argument \ 473 following 'bfl' and the actual line number value will be filled in with the second \ 474 argument. The user would use this alias as follows:" 475 R"( 476 477 (lldb) command alias bfl breakpoint set -f %1 -l %2 478 (lldb) bfl my-file.c 137 479 480 This would be the same as if the user had entered 'breakpoint set -f my-file.c -l 137'. 481 482 Another example: 483 484 (lldb) command alias pltty process launch -s -o %1 -e %1 485 (lldb) pltty /dev/tty0 486 487 Interpreted as 'process launch -s -o /dev/tty0 -e /dev/tty0' 488 489 )" 490 "If the user always wanted to pass the same value to a particular option, the \ 491 alias could be defined with that value directly in the alias as a constant, \ 492 rather than using a positional placeholder:" 493 R"( 494 495 (lldb) command alias bl3 breakpoint set -f %1 -l 3 496 497 Always sets a breakpoint on line 3 of whatever file is indicated.)"); 498 499 CommandArgumentEntry arg1; 500 CommandArgumentEntry arg2; 501 CommandArgumentEntry arg3; 502 CommandArgumentData alias_arg; 503 CommandArgumentData cmd_arg; 504 CommandArgumentData options_arg; 505 506 // Define the first (and only) variant of this arg. 507 alias_arg.arg_type = eArgTypeAliasName; 508 alias_arg.arg_repetition = eArgRepeatPlain; 509 510 // There is only one variant this argument could be; put it into the 511 // argument entry. 512 arg1.push_back(alias_arg); 513 514 // Define the first (and only) variant of this arg. 515 cmd_arg.arg_type = eArgTypeCommandName; 516 cmd_arg.arg_repetition = eArgRepeatPlain; 517 518 // There is only one variant this argument could be; put it into the 519 // argument entry. 520 arg2.push_back(cmd_arg); 521 522 // Define the first (and only) variant of this arg. 523 options_arg.arg_type = eArgTypeAliasOptions; 524 options_arg.arg_repetition = eArgRepeatOptional; 525 526 // There is only one variant this argument could be; put it into the 527 // argument entry. 528 arg3.push_back(options_arg); 529 530 // Push the data for the first argument into the m_arguments vector. 531 m_arguments.push_back(arg1); 532 m_arguments.push_back(arg2); 533 m_arguments.push_back(arg3); 534 } 535 536 ~CommandObjectCommandsAlias() override = default; 537 538 protected: 539 bool DoExecute(llvm::StringRef raw_command_line, 540 CommandReturnObject &result) override { 541 if (raw_command_line.empty()) { 542 result.AppendError("'command alias' requires at least two arguments"); 543 return false; 544 } 545 546 ExecutionContext exe_ctx = GetCommandInterpreter().GetExecutionContext(); 547 m_option_group.NotifyOptionParsingStarting(&exe_ctx); 548 549 OptionsWithRaw args_with_suffix(raw_command_line); 550 const char *remainder = args_with_suffix.GetRawPart().c_str(); 551 552 if (args_with_suffix.HasArgs()) 553 if (!ParseOptionsAndNotify(args_with_suffix.GetArgs(), result, 554 m_option_group, exe_ctx)) 555 return false; 556 557 llvm::StringRef raw_command_string(remainder); 558 Args args(raw_command_string); 559 560 if (args.GetArgumentCount() < 2) { 561 result.AppendError("'command alias' requires at least two arguments"); 562 result.SetStatus(eReturnStatusFailed); 563 return false; 564 } 565 566 // Get the alias command. 567 568 auto alias_command = args[0].ref; 569 if (alias_command.startswith("-")) { 570 result.AppendError("aliases starting with a dash are not supported"); 571 if (alias_command == "--help" || alias_command == "--long-help") { 572 result.AppendWarning("if trying to pass options to 'command alias' add " 573 "a -- at the end of the options"); 574 } 575 result.SetStatus(eReturnStatusFailed); 576 return false; 577 } 578 579 // Strip the new alias name off 'raw_command_string' (leave it on args, 580 // which gets passed to 'Execute', which does the stripping itself. 581 size_t pos = raw_command_string.find(alias_command); 582 if (pos == 0) { 583 raw_command_string = raw_command_string.substr(alias_command.size()); 584 pos = raw_command_string.find_first_not_of(' '); 585 if ((pos != std::string::npos) && (pos > 0)) 586 raw_command_string = raw_command_string.substr(pos); 587 } else { 588 result.AppendError("Error parsing command string. No alias created."); 589 result.SetStatus(eReturnStatusFailed); 590 return false; 591 } 592 593 // Verify that the command is alias-able. 594 if (m_interpreter.CommandExists(alias_command)) { 595 result.AppendErrorWithFormat( 596 "'%s' is a permanent debugger command and cannot be redefined.\n", 597 args[0].c_str()); 598 result.SetStatus(eReturnStatusFailed); 599 return false; 600 } 601 602 // Get CommandObject that is being aliased. The command name is read from 603 // the front of raw_command_string. raw_command_string is returned with the 604 // name of the command object stripped off the front. 605 llvm::StringRef original_raw_command_string = raw_command_string; 606 CommandObject *cmd_obj = 607 m_interpreter.GetCommandObjectForCommand(raw_command_string); 608 609 if (!cmd_obj) { 610 result.AppendErrorWithFormat("invalid command given to 'command alias'. " 611 "'%s' does not begin with a valid command." 612 " No alias created.", 613 original_raw_command_string.str().c_str()); 614 result.SetStatus(eReturnStatusFailed); 615 return false; 616 } else if (!cmd_obj->WantsRawCommandString()) { 617 // Note that args was initialized with the original command, and has not 618 // been updated to this point. Therefore can we pass it to the version of 619 // Execute that does not need/expect raw input in the alias. 620 return HandleAliasingNormalCommand(args, result); 621 } else { 622 return HandleAliasingRawCommand(alias_command, raw_command_string, 623 *cmd_obj, result); 624 } 625 return result.Succeeded(); 626 } 627 628 bool HandleAliasingRawCommand(llvm::StringRef alias_command, 629 llvm::StringRef raw_command_string, 630 CommandObject &cmd_obj, 631 CommandReturnObject &result) { 632 // Verify & handle any options/arguments passed to the alias command 633 634 OptionArgVectorSP option_arg_vector_sp = 635 OptionArgVectorSP(new OptionArgVector); 636 637 if (CommandObjectSP cmd_obj_sp = 638 m_interpreter.GetCommandSPExact(cmd_obj.GetCommandName(), false)) { 639 if (m_interpreter.AliasExists(alias_command) || 640 m_interpreter.UserCommandExists(alias_command)) { 641 result.AppendWarningWithFormat( 642 "Overwriting existing definition for '%s'.\n", 643 alias_command.str().c_str()); 644 } 645 if (CommandAlias *alias = m_interpreter.AddAlias( 646 alias_command, cmd_obj_sp, raw_command_string)) { 647 if (m_command_options.m_help.OptionWasSet()) 648 alias->SetHelp(m_command_options.m_help.GetCurrentValue()); 649 if (m_command_options.m_long_help.OptionWasSet()) 650 alias->SetHelpLong(m_command_options.m_long_help.GetCurrentValue()); 651 result.SetStatus(eReturnStatusSuccessFinishNoResult); 652 } else { 653 result.AppendError("Unable to create requested alias.\n"); 654 result.SetStatus(eReturnStatusFailed); 655 } 656 657 } else { 658 result.AppendError("Unable to create requested alias.\n"); 659 result.SetStatus(eReturnStatusFailed); 660 } 661 662 return result.Succeeded(); 663 } 664 665 bool HandleAliasingNormalCommand(Args &args, CommandReturnObject &result) { 666 size_t argc = args.GetArgumentCount(); 667 668 if (argc < 2) { 669 result.AppendError("'command alias' requires at least two arguments"); 670 result.SetStatus(eReturnStatusFailed); 671 return false; 672 } 673 674 // Save these in std::strings since we're going to shift them off. 675 const std::string alias_command(args[0].ref); 676 const std::string actual_command(args[1].ref); 677 678 args.Shift(); // Shift the alias command word off the argument vector. 679 args.Shift(); // Shift the old command word off the argument vector. 680 681 // Verify that the command is alias'able, and get the appropriate command 682 // object. 683 684 if (m_interpreter.CommandExists(alias_command)) { 685 result.AppendErrorWithFormat( 686 "'%s' is a permanent debugger command and cannot be redefined.\n", 687 alias_command.c_str()); 688 result.SetStatus(eReturnStatusFailed); 689 return false; 690 } 691 692 CommandObjectSP command_obj_sp( 693 m_interpreter.GetCommandSPExact(actual_command, true)); 694 CommandObjectSP subcommand_obj_sp; 695 bool use_subcommand = false; 696 if (!command_obj_sp) { 697 result.AppendErrorWithFormat("'%s' is not an existing command.\n", 698 actual_command.c_str()); 699 result.SetStatus(eReturnStatusFailed); 700 return false; 701 } 702 CommandObject *cmd_obj = command_obj_sp.get(); 703 CommandObject *sub_cmd_obj = nullptr; 704 OptionArgVectorSP option_arg_vector_sp = 705 OptionArgVectorSP(new OptionArgVector); 706 707 while (cmd_obj->IsMultiwordObject() && !args.empty()) { 708 auto sub_command = args[0].ref; 709 assert(!sub_command.empty()); 710 subcommand_obj_sp = cmd_obj->GetSubcommandSP(sub_command); 711 if (!subcommand_obj_sp) { 712 result.AppendErrorWithFormat( 713 "'%s' is not a valid sub-command of '%s'. " 714 "Unable to create alias.\n", 715 args[0].c_str(), actual_command.c_str()); 716 result.SetStatus(eReturnStatusFailed); 717 return false; 718 } 719 720 sub_cmd_obj = subcommand_obj_sp.get(); 721 use_subcommand = true; 722 args.Shift(); // Shift the sub_command word off the argument vector. 723 cmd_obj = sub_cmd_obj; 724 } 725 726 // Verify & handle any options/arguments passed to the alias command 727 728 std::string args_string; 729 730 if (!args.empty()) { 731 CommandObjectSP tmp_sp = 732 m_interpreter.GetCommandSPExact(cmd_obj->GetCommandName(), false); 733 if (use_subcommand) 734 tmp_sp = m_interpreter.GetCommandSPExact(sub_cmd_obj->GetCommandName(), 735 false); 736 737 args.GetCommandString(args_string); 738 } 739 740 if (m_interpreter.AliasExists(alias_command) || 741 m_interpreter.UserCommandExists(alias_command)) { 742 result.AppendWarningWithFormat( 743 "Overwriting existing definition for '%s'.\n", alias_command.c_str()); 744 } 745 746 if (CommandAlias *alias = m_interpreter.AddAlias( 747 alias_command, use_subcommand ? subcommand_obj_sp : command_obj_sp, 748 args_string)) { 749 if (m_command_options.m_help.OptionWasSet()) 750 alias->SetHelp(m_command_options.m_help.GetCurrentValue()); 751 if (m_command_options.m_long_help.OptionWasSet()) 752 alias->SetHelpLong(m_command_options.m_long_help.GetCurrentValue()); 753 result.SetStatus(eReturnStatusSuccessFinishNoResult); 754 } else { 755 result.AppendError("Unable to create requested alias.\n"); 756 result.SetStatus(eReturnStatusFailed); 757 return false; 758 } 759 760 return result.Succeeded(); 761 } 762 }; 763 764 #pragma mark CommandObjectCommandsUnalias 765 //------------------------------------------------------------------------- 766 // CommandObjectCommandsUnalias 767 //------------------------------------------------------------------------- 768 769 class CommandObjectCommandsUnalias : public CommandObjectParsed { 770 public: 771 CommandObjectCommandsUnalias(CommandInterpreter &interpreter) 772 : CommandObjectParsed( 773 interpreter, "command unalias", 774 "Delete one or more custom commands defined by 'command alias'.", 775 nullptr) { 776 CommandArgumentEntry arg; 777 CommandArgumentData alias_arg; 778 779 // Define the first (and only) variant of this arg. 780 alias_arg.arg_type = eArgTypeAliasName; 781 alias_arg.arg_repetition = eArgRepeatPlain; 782 783 // There is only one variant this argument could be; put it into the 784 // argument entry. 785 arg.push_back(alias_arg); 786 787 // Push the data for the first argument into the m_arguments vector. 788 m_arguments.push_back(arg); 789 } 790 791 ~CommandObjectCommandsUnalias() override = default; 792 793 protected: 794 bool DoExecute(Args &args, CommandReturnObject &result) override { 795 CommandObject::CommandMap::iterator pos; 796 CommandObject *cmd_obj; 797 798 if (args.empty()) { 799 result.AppendError("must call 'unalias' with a valid alias"); 800 result.SetStatus(eReturnStatusFailed); 801 return false; 802 } 803 804 auto command_name = args[0].ref; 805 cmd_obj = m_interpreter.GetCommandObject(command_name); 806 if (!cmd_obj) { 807 result.AppendErrorWithFormat( 808 "'%s' is not a known command.\nTry 'help' to see a " 809 "current list of commands.\n", 810 args[0].c_str()); 811 result.SetStatus(eReturnStatusFailed); 812 return false; 813 } 814 815 if (m_interpreter.CommandExists(command_name)) { 816 if (cmd_obj->IsRemovable()) { 817 result.AppendErrorWithFormat( 818 "'%s' is not an alias, it is a debugger command which can be " 819 "removed using the 'command delete' command.\n", 820 args[0].c_str()); 821 } else { 822 result.AppendErrorWithFormat( 823 "'%s' is a permanent debugger command and cannot be removed.\n", 824 args[0].c_str()); 825 } 826 result.SetStatus(eReturnStatusFailed); 827 return false; 828 } 829 830 if (!m_interpreter.RemoveAlias(command_name)) { 831 if (m_interpreter.AliasExists(command_name)) 832 result.AppendErrorWithFormat( 833 "Error occurred while attempting to unalias '%s'.\n", 834 args[0].c_str()); 835 else 836 result.AppendErrorWithFormat("'%s' is not an existing alias.\n", 837 args[0].c_str()); 838 result.SetStatus(eReturnStatusFailed); 839 return false; 840 } 841 842 result.SetStatus(eReturnStatusSuccessFinishNoResult); 843 return result.Succeeded(); 844 } 845 }; 846 847 #pragma mark CommandObjectCommandsDelete 848 //------------------------------------------------------------------------- 849 // CommandObjectCommandsDelete 850 //------------------------------------------------------------------------- 851 852 class CommandObjectCommandsDelete : public CommandObjectParsed { 853 public: 854 CommandObjectCommandsDelete(CommandInterpreter &interpreter) 855 : CommandObjectParsed( 856 interpreter, "command delete", 857 "Delete one or more custom commands defined by 'command regex'.", 858 nullptr) { 859 CommandArgumentEntry arg; 860 CommandArgumentData alias_arg; 861 862 // Define the first (and only) variant of this arg. 863 alias_arg.arg_type = eArgTypeCommandName; 864 alias_arg.arg_repetition = eArgRepeatPlain; 865 866 // There is only one variant this argument could be; put it into the 867 // argument entry. 868 arg.push_back(alias_arg); 869 870 // Push the data for the first argument into the m_arguments vector. 871 m_arguments.push_back(arg); 872 } 873 874 ~CommandObjectCommandsDelete() override = default; 875 876 protected: 877 bool DoExecute(Args &args, CommandReturnObject &result) override { 878 CommandObject::CommandMap::iterator pos; 879 880 if (args.empty()) { 881 result.AppendErrorWithFormat("must call '%s' with one or more valid user " 882 "defined regular expression command names", 883 GetCommandName().str().c_str()); 884 result.SetStatus(eReturnStatusFailed); 885 } 886 887 auto command_name = args[0].ref; 888 if (!m_interpreter.CommandExists(command_name)) { 889 StreamString error_msg_stream; 890 const bool generate_apropos = true; 891 const bool generate_type_lookup = false; 892 CommandObjectHelp::GenerateAdditionalHelpAvenuesMessage( 893 &error_msg_stream, command_name, llvm::StringRef(), llvm::StringRef(), 894 generate_apropos, generate_type_lookup); 895 result.AppendError(error_msg_stream.GetString()); 896 result.SetStatus(eReturnStatusFailed); 897 return false; 898 } 899 900 if (!m_interpreter.RemoveCommand(command_name)) { 901 result.AppendErrorWithFormat( 902 "'%s' is a permanent debugger command and cannot be removed.\n", 903 args[0].c_str()); 904 result.SetStatus(eReturnStatusFailed); 905 return false; 906 } 907 908 result.SetStatus(eReturnStatusSuccessFinishNoResult); 909 return true; 910 } 911 }; 912 913 //------------------------------------------------------------------------- 914 // CommandObjectCommandsAddRegex 915 //------------------------------------------------------------------------- 916 917 static OptionDefinition g_regex_options[] = { 918 // clang-format off 919 { LLDB_OPT_SET_1, false, "help" , 'h', OptionParser::eRequiredArgument, nullptr, nullptr, 0, eArgTypeNone, "The help text to display for this command." }, 920 { LLDB_OPT_SET_1, false, "syntax", 's', OptionParser::eRequiredArgument, nullptr, nullptr, 0, eArgTypeNone, "A syntax string showing the typical usage syntax." }, 921 // clang-format on 922 }; 923 924 #pragma mark CommandObjectCommandsAddRegex 925 926 class CommandObjectCommandsAddRegex : public CommandObjectParsed, 927 public IOHandlerDelegateMultiline { 928 public: 929 CommandObjectCommandsAddRegex(CommandInterpreter &interpreter) 930 : CommandObjectParsed( 931 interpreter, "command regex", "Define a custom command in terms of " 932 "existing commands by matching " 933 "regular expressions.", 934 "command regex <cmd-name> [s/<regex>/<subst>/ ...]"), 935 IOHandlerDelegateMultiline("", 936 IOHandlerDelegate::Completion::LLDBCommand), 937 m_options() { 938 SetHelpLong( 939 R"( 940 )" 941 "This command allows the user to create powerful regular expression commands \ 942 with substitutions. The regular expressions and substitutions are specified \ 943 using the regular expression substitution format of:" 944 R"( 945 946 s/<regex>/<subst>/ 947 948 )" 949 "<regex> is a regular expression that can use parenthesis to capture regular \ 950 expression input and substitute the captured matches in the output using %1 \ 951 for the first match, %2 for the second, and so on." 952 R"( 953 954 )" 955 "The regular expressions can all be specified on the command line if more than \ 956 one argument is provided. If just the command name is provided on the command \ 957 line, then the regular expressions and substitutions can be entered on separate \ 958 lines, followed by an empty line to terminate the command definition." 959 R"( 960 961 EXAMPLES 962 963 )" 964 "The following example will define a regular expression command named 'f' that \ 965 will call 'finish' if there are no arguments, or 'frame select <frame-idx>' if \ 966 a number follows 'f':" 967 R"( 968 969 (lldb) command regex f s/^$/finish/ 's/([0-9]+)/frame select %1/')"); 970 } 971 972 ~CommandObjectCommandsAddRegex() override = default; 973 974 protected: 975 void IOHandlerActivated(IOHandler &io_handler) override { 976 StreamFileSP output_sp(io_handler.GetOutputStreamFile()); 977 if (output_sp) { 978 output_sp->PutCString("Enter one of more sed substitution commands in " 979 "the form: 's/<regex>/<subst>/'.\nTerminate the " 980 "substitution list with an empty line.\n"); 981 output_sp->Flush(); 982 } 983 } 984 985 void IOHandlerInputComplete(IOHandler &io_handler, 986 std::string &data) override { 987 io_handler.SetIsDone(true); 988 if (m_regex_cmd_ap) { 989 StringList lines; 990 if (lines.SplitIntoLines(data)) { 991 const size_t num_lines = lines.GetSize(); 992 bool check_only = false; 993 for (size_t i = 0; i < num_lines; ++i) { 994 llvm::StringRef bytes_strref(lines[i]); 995 Status error = AppendRegexSubstitution(bytes_strref, check_only); 996 if (error.Fail()) { 997 if (!m_interpreter.GetDebugger() 998 .GetCommandInterpreter() 999 .GetBatchCommandMode()) { 1000 StreamSP out_stream = 1001 m_interpreter.GetDebugger().GetAsyncOutputStream(); 1002 out_stream->Printf("error: %s\n", error.AsCString()); 1003 } 1004 } 1005 } 1006 } 1007 if (m_regex_cmd_ap->HasRegexEntries()) { 1008 CommandObjectSP cmd_sp(m_regex_cmd_ap.release()); 1009 m_interpreter.AddCommand(cmd_sp->GetCommandName(), cmd_sp, true); 1010 } 1011 } 1012 } 1013 1014 bool DoExecute(Args &command, CommandReturnObject &result) override { 1015 const size_t argc = command.GetArgumentCount(); 1016 if (argc == 0) { 1017 result.AppendError("usage: 'command regex <command-name> " 1018 "[s/<regex1>/<subst1>/ s/<regex2>/<subst2>/ ...]'\n"); 1019 result.SetStatus(eReturnStatusFailed); 1020 return false; 1021 } 1022 1023 Status error; 1024 auto name = command[0].ref; 1025 m_regex_cmd_ap = llvm::make_unique<CommandObjectRegexCommand>( 1026 m_interpreter, name, m_options.GetHelp(), m_options.GetSyntax(), 10, 0, 1027 true); 1028 1029 if (argc == 1) { 1030 Debugger &debugger = m_interpreter.GetDebugger(); 1031 bool color_prompt = debugger.GetUseColor(); 1032 const bool multiple_lines = true; // Get multiple lines 1033 IOHandlerSP io_handler_sp(new IOHandlerEditline( 1034 debugger, IOHandler::Type::Other, 1035 "lldb-regex", // Name of input reader for history 1036 llvm::StringRef("> "), // Prompt 1037 llvm::StringRef(), // Continuation prompt 1038 multiple_lines, color_prompt, 1039 0, // Don't show line numbers 1040 *this)); 1041 1042 if (io_handler_sp) { 1043 debugger.PushIOHandler(io_handler_sp); 1044 result.SetStatus(eReturnStatusSuccessFinishNoResult); 1045 } 1046 } else { 1047 for (auto &entry : command.entries().drop_front()) { 1048 bool check_only = false; 1049 error = AppendRegexSubstitution(entry.ref, check_only); 1050 if (error.Fail()) 1051 break; 1052 } 1053 1054 if (error.Success()) { 1055 AddRegexCommandToInterpreter(); 1056 } 1057 } 1058 if (error.Fail()) { 1059 result.AppendError(error.AsCString()); 1060 result.SetStatus(eReturnStatusFailed); 1061 } 1062 1063 return result.Succeeded(); 1064 } 1065 1066 Status AppendRegexSubstitution(const llvm::StringRef ®ex_sed, 1067 bool check_only) { 1068 Status error; 1069 1070 if (!m_regex_cmd_ap) { 1071 error.SetErrorStringWithFormat( 1072 "invalid regular expression command object for: '%.*s'", 1073 (int)regex_sed.size(), regex_sed.data()); 1074 return error; 1075 } 1076 1077 size_t regex_sed_size = regex_sed.size(); 1078 1079 if (regex_sed_size <= 1) { 1080 error.SetErrorStringWithFormat( 1081 "regular expression substitution string is too short: '%.*s'", 1082 (int)regex_sed.size(), regex_sed.data()); 1083 return error; 1084 } 1085 1086 if (regex_sed[0] != 's') { 1087 error.SetErrorStringWithFormat("regular expression substitution string " 1088 "doesn't start with 's': '%.*s'", 1089 (int)regex_sed.size(), regex_sed.data()); 1090 return error; 1091 } 1092 const size_t first_separator_char_pos = 1; 1093 // use the char that follows 's' as the regex separator character so we can 1094 // have "s/<regex>/<subst>/" or "s|<regex>|<subst>|" 1095 const char separator_char = regex_sed[first_separator_char_pos]; 1096 const size_t second_separator_char_pos = 1097 regex_sed.find(separator_char, first_separator_char_pos + 1); 1098 1099 if (second_separator_char_pos == std::string::npos) { 1100 error.SetErrorStringWithFormat( 1101 "missing second '%c' separator char after '%.*s' in '%.*s'", 1102 separator_char, 1103 (int)(regex_sed.size() - first_separator_char_pos - 1), 1104 regex_sed.data() + (first_separator_char_pos + 1), 1105 (int)regex_sed.size(), regex_sed.data()); 1106 return error; 1107 } 1108 1109 const size_t third_separator_char_pos = 1110 regex_sed.find(separator_char, second_separator_char_pos + 1); 1111 1112 if (third_separator_char_pos == std::string::npos) { 1113 error.SetErrorStringWithFormat( 1114 "missing third '%c' separator char after '%.*s' in '%.*s'", 1115 separator_char, 1116 (int)(regex_sed.size() - second_separator_char_pos - 1), 1117 regex_sed.data() + (second_separator_char_pos + 1), 1118 (int)regex_sed.size(), regex_sed.data()); 1119 return error; 1120 } 1121 1122 if (third_separator_char_pos != regex_sed_size - 1) { 1123 // Make sure that everything that follows the last regex separator char 1124 if (regex_sed.find_first_not_of("\t\n\v\f\r ", 1125 third_separator_char_pos + 1) != 1126 std::string::npos) { 1127 error.SetErrorStringWithFormat( 1128 "extra data found after the '%.*s' regular expression substitution " 1129 "string: '%.*s'", 1130 (int)third_separator_char_pos + 1, regex_sed.data(), 1131 (int)(regex_sed.size() - third_separator_char_pos - 1), 1132 regex_sed.data() + (third_separator_char_pos + 1)); 1133 return error; 1134 } 1135 } else if (first_separator_char_pos + 1 == second_separator_char_pos) { 1136 error.SetErrorStringWithFormat( 1137 "<regex> can't be empty in 's%c<regex>%c<subst>%c' string: '%.*s'", 1138 separator_char, separator_char, separator_char, (int)regex_sed.size(), 1139 regex_sed.data()); 1140 return error; 1141 } else if (second_separator_char_pos + 1 == third_separator_char_pos) { 1142 error.SetErrorStringWithFormat( 1143 "<subst> can't be empty in 's%c<regex>%c<subst>%c' string: '%.*s'", 1144 separator_char, separator_char, separator_char, (int)regex_sed.size(), 1145 regex_sed.data()); 1146 return error; 1147 } 1148 1149 if (!check_only) { 1150 std::string regex(regex_sed.substr(first_separator_char_pos + 1, 1151 second_separator_char_pos - 1152 first_separator_char_pos - 1)); 1153 std::string subst(regex_sed.substr(second_separator_char_pos + 1, 1154 third_separator_char_pos - 1155 second_separator_char_pos - 1)); 1156 m_regex_cmd_ap->AddRegexCommand(regex.c_str(), subst.c_str()); 1157 } 1158 return error; 1159 } 1160 1161 void AddRegexCommandToInterpreter() { 1162 if (m_regex_cmd_ap) { 1163 if (m_regex_cmd_ap->HasRegexEntries()) { 1164 CommandObjectSP cmd_sp(m_regex_cmd_ap.release()); 1165 m_interpreter.AddCommand(cmd_sp->GetCommandName(), cmd_sp, true); 1166 } 1167 } 1168 } 1169 1170 private: 1171 std::unique_ptr<CommandObjectRegexCommand> m_regex_cmd_ap; 1172 1173 class CommandOptions : public Options { 1174 public: 1175 CommandOptions() : Options() {} 1176 1177 ~CommandOptions() override = default; 1178 1179 Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, 1180 ExecutionContext *execution_context) override { 1181 Status error; 1182 const int short_option = m_getopt_table[option_idx].val; 1183 1184 switch (short_option) { 1185 case 'h': 1186 m_help.assign(option_arg); 1187 break; 1188 case 's': 1189 m_syntax.assign(option_arg); 1190 break; 1191 default: 1192 error.SetErrorStringWithFormat("unrecognized option '%c'", 1193 short_option); 1194 break; 1195 } 1196 1197 return error; 1198 } 1199 1200 void OptionParsingStarting(ExecutionContext *execution_context) override { 1201 m_help.clear(); 1202 m_syntax.clear(); 1203 } 1204 1205 llvm::ArrayRef<OptionDefinition> GetDefinitions() override { 1206 return llvm::makeArrayRef(g_regex_options); 1207 } 1208 1209 // TODO: Convert these functions to return StringRefs. 1210 const char *GetHelp() { 1211 return (m_help.empty() ? nullptr : m_help.c_str()); 1212 } 1213 1214 const char *GetSyntax() { 1215 return (m_syntax.empty() ? nullptr : m_syntax.c_str()); 1216 } 1217 1218 protected: 1219 // Instance variables to hold the values for command options. 1220 1221 std::string m_help; 1222 std::string m_syntax; 1223 }; 1224 1225 Options *GetOptions() override { return &m_options; } 1226 1227 CommandOptions m_options; 1228 }; 1229 1230 class CommandObjectPythonFunction : public CommandObjectRaw { 1231 public: 1232 CommandObjectPythonFunction(CommandInterpreter &interpreter, std::string name, 1233 std::string funct, std::string help, 1234 ScriptedCommandSynchronicity synch) 1235 : CommandObjectRaw(interpreter, name), 1236 m_function_name(funct), m_synchro(synch), m_fetched_help_long(false) { 1237 if (!help.empty()) 1238 SetHelp(help); 1239 else { 1240 StreamString stream; 1241 stream.Printf("For more information run 'help %s'", name.c_str()); 1242 SetHelp(stream.GetString()); 1243 } 1244 } 1245 1246 ~CommandObjectPythonFunction() override = default; 1247 1248 bool IsRemovable() const override { return true; } 1249 1250 const std::string &GetFunctionName() { return m_function_name; } 1251 1252 ScriptedCommandSynchronicity GetSynchronicity() { return m_synchro; } 1253 1254 llvm::StringRef GetHelpLong() override { 1255 if (m_fetched_help_long) 1256 return CommandObjectRaw::GetHelpLong(); 1257 1258 ScriptInterpreter *scripter = m_interpreter.GetScriptInterpreter(); 1259 if (!scripter) 1260 return CommandObjectRaw::GetHelpLong(); 1261 1262 std::string docstring; 1263 m_fetched_help_long = 1264 scripter->GetDocumentationForItem(m_function_name.c_str(), docstring); 1265 if (!docstring.empty()) 1266 SetHelpLong(docstring); 1267 return CommandObjectRaw::GetHelpLong(); 1268 } 1269 1270 protected: 1271 bool DoExecute(llvm::StringRef raw_command_line, 1272 CommandReturnObject &result) override { 1273 ScriptInterpreter *scripter = m_interpreter.GetScriptInterpreter(); 1274 1275 Status error; 1276 1277 result.SetStatus(eReturnStatusInvalid); 1278 1279 if (!scripter || 1280 !scripter->RunScriptBasedCommand(m_function_name.c_str(), 1281 raw_command_line, m_synchro, result, 1282 error, m_exe_ctx)) { 1283 result.AppendError(error.AsCString()); 1284 result.SetStatus(eReturnStatusFailed); 1285 } else { 1286 // Don't change the status if the command already set it... 1287 if (result.GetStatus() == eReturnStatusInvalid) { 1288 if (result.GetOutputData().empty()) 1289 result.SetStatus(eReturnStatusSuccessFinishNoResult); 1290 else 1291 result.SetStatus(eReturnStatusSuccessFinishResult); 1292 } 1293 } 1294 1295 return result.Succeeded(); 1296 } 1297 1298 private: 1299 std::string m_function_name; 1300 ScriptedCommandSynchronicity m_synchro; 1301 bool m_fetched_help_long; 1302 }; 1303 1304 class CommandObjectScriptingObject : public CommandObjectRaw { 1305 public: 1306 CommandObjectScriptingObject(CommandInterpreter &interpreter, 1307 std::string name, 1308 StructuredData::GenericSP cmd_obj_sp, 1309 ScriptedCommandSynchronicity synch) 1310 : CommandObjectRaw(interpreter, name), 1311 m_cmd_obj_sp(cmd_obj_sp), m_synchro(synch), m_fetched_help_short(false), 1312 m_fetched_help_long(false) { 1313 StreamString stream; 1314 stream.Printf("For more information run 'help %s'", name.c_str()); 1315 SetHelp(stream.GetString()); 1316 if (ScriptInterpreter *scripter = m_interpreter.GetScriptInterpreter()) 1317 GetFlags().Set(scripter->GetFlagsForCommandObject(cmd_obj_sp)); 1318 } 1319 1320 ~CommandObjectScriptingObject() override = default; 1321 1322 bool IsRemovable() const override { return true; } 1323 1324 StructuredData::GenericSP GetImplementingObject() { return m_cmd_obj_sp; } 1325 1326 ScriptedCommandSynchronicity GetSynchronicity() { return m_synchro; } 1327 1328 llvm::StringRef GetHelp() override { 1329 if (m_fetched_help_short) 1330 return CommandObjectRaw::GetHelp(); 1331 ScriptInterpreter *scripter = m_interpreter.GetScriptInterpreter(); 1332 if (!scripter) 1333 return CommandObjectRaw::GetHelp(); 1334 std::string docstring; 1335 m_fetched_help_short = 1336 scripter->GetShortHelpForCommandObject(m_cmd_obj_sp, docstring); 1337 if (!docstring.empty()) 1338 SetHelp(docstring); 1339 1340 return CommandObjectRaw::GetHelp(); 1341 } 1342 1343 llvm::StringRef GetHelpLong() override { 1344 if (m_fetched_help_long) 1345 return CommandObjectRaw::GetHelpLong(); 1346 1347 ScriptInterpreter *scripter = m_interpreter.GetScriptInterpreter(); 1348 if (!scripter) 1349 return CommandObjectRaw::GetHelpLong(); 1350 1351 std::string docstring; 1352 m_fetched_help_long = 1353 scripter->GetLongHelpForCommandObject(m_cmd_obj_sp, docstring); 1354 if (!docstring.empty()) 1355 SetHelpLong(docstring); 1356 return CommandObjectRaw::GetHelpLong(); 1357 } 1358 1359 protected: 1360 bool DoExecute(llvm::StringRef raw_command_line, 1361 CommandReturnObject &result) override { 1362 ScriptInterpreter *scripter = m_interpreter.GetScriptInterpreter(); 1363 1364 Status error; 1365 1366 result.SetStatus(eReturnStatusInvalid); 1367 1368 if (!scripter || 1369 !scripter->RunScriptBasedCommand(m_cmd_obj_sp, raw_command_line, 1370 m_synchro, result, error, m_exe_ctx)) { 1371 result.AppendError(error.AsCString()); 1372 result.SetStatus(eReturnStatusFailed); 1373 } else { 1374 // Don't change the status if the command already set it... 1375 if (result.GetStatus() == eReturnStatusInvalid) { 1376 if (result.GetOutputData().empty()) 1377 result.SetStatus(eReturnStatusSuccessFinishNoResult); 1378 else 1379 result.SetStatus(eReturnStatusSuccessFinishResult); 1380 } 1381 } 1382 1383 return result.Succeeded(); 1384 } 1385 1386 private: 1387 StructuredData::GenericSP m_cmd_obj_sp; 1388 ScriptedCommandSynchronicity m_synchro; 1389 bool m_fetched_help_short : 1; 1390 bool m_fetched_help_long : 1; 1391 }; 1392 1393 //------------------------------------------------------------------------- 1394 // CommandObjectCommandsScriptImport 1395 //------------------------------------------------------------------------- 1396 1397 OptionDefinition g_script_import_options[] = { 1398 // clang-format off 1399 { LLDB_OPT_SET_1, false, "allow-reload", 'r', OptionParser::eNoArgument, nullptr, nullptr, 0, eArgTypeNone, "Allow the script to be loaded even if it was already loaded before. This argument exists for backwards compatibility, but reloading is always allowed, whether you specify it or not." }, 1400 // clang-format on 1401 }; 1402 1403 class CommandObjectCommandsScriptImport : public CommandObjectParsed { 1404 public: 1405 CommandObjectCommandsScriptImport(CommandInterpreter &interpreter) 1406 : CommandObjectParsed(interpreter, "command script import", 1407 "Import a scripting module in LLDB.", nullptr), 1408 m_options() { 1409 CommandArgumentEntry arg1; 1410 CommandArgumentData cmd_arg; 1411 1412 // Define the first (and only) variant of this arg. 1413 cmd_arg.arg_type = eArgTypeFilename; 1414 cmd_arg.arg_repetition = eArgRepeatPlus; 1415 1416 // There is only one variant this argument could be; put it into the 1417 // argument entry. 1418 arg1.push_back(cmd_arg); 1419 1420 // Push the data for the first argument into the m_arguments vector. 1421 m_arguments.push_back(arg1); 1422 } 1423 1424 ~CommandObjectCommandsScriptImport() override = default; 1425 1426 int HandleArgumentCompletion( 1427 CompletionRequest &request, 1428 OptionElementVector &opt_element_vector) override { 1429 CommandCompletions::InvokeCommonCompletionCallbacks( 1430 GetCommandInterpreter(), CommandCompletions::eDiskFileCompletion, 1431 request, nullptr); 1432 return request.GetMatches().GetSize(); 1433 } 1434 1435 Options *GetOptions() override { return &m_options; } 1436 1437 protected: 1438 class CommandOptions : public Options { 1439 public: 1440 CommandOptions() : Options() {} 1441 1442 ~CommandOptions() override = default; 1443 1444 Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, 1445 ExecutionContext *execution_context) override { 1446 Status error; 1447 const int short_option = m_getopt_table[option_idx].val; 1448 1449 switch (short_option) { 1450 case 'r': 1451 m_allow_reload = true; 1452 break; 1453 default: 1454 error.SetErrorStringWithFormat("unrecognized option '%c'", 1455 short_option); 1456 break; 1457 } 1458 1459 return error; 1460 } 1461 1462 void OptionParsingStarting(ExecutionContext *execution_context) override { 1463 m_allow_reload = true; 1464 } 1465 1466 llvm::ArrayRef<OptionDefinition> GetDefinitions() override { 1467 return llvm::makeArrayRef(g_script_import_options); 1468 } 1469 1470 // Instance variables to hold the values for command options. 1471 1472 bool m_allow_reload; 1473 }; 1474 1475 bool DoExecute(Args &command, CommandReturnObject &result) override { 1476 if (m_interpreter.GetDebugger().GetScriptLanguage() != 1477 lldb::eScriptLanguagePython) { 1478 result.AppendError("only scripting language supported for module " 1479 "importing is currently Python"); 1480 result.SetStatus(eReturnStatusFailed); 1481 return false; 1482 } 1483 1484 if (command.empty()) { 1485 result.AppendError("command script import needs one or more arguments"); 1486 result.SetStatus(eReturnStatusFailed); 1487 return false; 1488 } 1489 1490 for (auto &entry : command.entries()) { 1491 Status error; 1492 1493 const bool init_session = true; 1494 // FIXME: this is necessary because CommandObject::CheckRequirements() 1495 // assumes that commands won't ever be recursively invoked, but it's 1496 // actually possible to craft a Python script that does other "command 1497 // script imports" in __lldb_init_module the real fix is to have 1498 // recursive commands possible with a CommandInvocation object separate 1499 // from the CommandObject itself, so that recursive command invocations 1500 // won't stomp on each other (wrt to execution contents, options, and 1501 // more) 1502 m_exe_ctx.Clear(); 1503 if (m_interpreter.GetScriptInterpreter()->LoadScriptingModule( 1504 entry.c_str(), m_options.m_allow_reload, init_session, error)) { 1505 result.SetStatus(eReturnStatusSuccessFinishNoResult); 1506 } else { 1507 result.AppendErrorWithFormat("module importing failed: %s", 1508 error.AsCString()); 1509 result.SetStatus(eReturnStatusFailed); 1510 } 1511 } 1512 1513 return result.Succeeded(); 1514 } 1515 1516 CommandOptions m_options; 1517 }; 1518 1519 //------------------------------------------------------------------------- 1520 // CommandObjectCommandsScriptAdd 1521 //------------------------------------------------------------------------- 1522 1523 static OptionEnumValueElement g_script_synchro_type[] = { 1524 {eScriptedCommandSynchronicitySynchronous, "synchronous", 1525 "Run synchronous"}, 1526 {eScriptedCommandSynchronicityAsynchronous, "asynchronous", 1527 "Run asynchronous"}, 1528 {eScriptedCommandSynchronicityCurrentValue, "current", 1529 "Do not alter current setting"}, 1530 {0, nullptr, nullptr}}; 1531 1532 static OptionDefinition g_script_add_options[] = { 1533 // clang-format off 1534 { LLDB_OPT_SET_1, false, "function", 'f', OptionParser::eRequiredArgument, nullptr, nullptr, 0, eArgTypePythonFunction, "Name of the Python function to bind to this command name." }, 1535 { LLDB_OPT_SET_2, false, "class", 'c', OptionParser::eRequiredArgument, nullptr, nullptr, 0, eArgTypePythonClass, "Name of the Python class to bind to this command name." }, 1536 { LLDB_OPT_SET_1, false, "help" , 'h', OptionParser::eRequiredArgument, nullptr, nullptr, 0, eArgTypeHelpText, "The help text to display for this command." }, 1537 { LLDB_OPT_SET_ALL, false, "synchronicity", 's', OptionParser::eRequiredArgument, nullptr, g_script_synchro_type, 0, eArgTypeScriptedCommandSynchronicity, "Set the synchronicity of this command's executions with regard to LLDB event system." }, 1538 // clang-format on 1539 }; 1540 1541 class CommandObjectCommandsScriptAdd : public CommandObjectParsed, 1542 public IOHandlerDelegateMultiline { 1543 public: 1544 CommandObjectCommandsScriptAdd(CommandInterpreter &interpreter) 1545 : CommandObjectParsed(interpreter, "command script add", 1546 "Add a scripted function as an LLDB command.", 1547 nullptr), 1548 IOHandlerDelegateMultiline("DONE"), m_options() { 1549 CommandArgumentEntry arg1; 1550 CommandArgumentData cmd_arg; 1551 1552 // Define the first (and only) variant of this arg. 1553 cmd_arg.arg_type = eArgTypeCommandName; 1554 cmd_arg.arg_repetition = eArgRepeatPlain; 1555 1556 // There is only one variant this argument could be; put it into the 1557 // argument entry. 1558 arg1.push_back(cmd_arg); 1559 1560 // Push the data for the first argument into the m_arguments vector. 1561 m_arguments.push_back(arg1); 1562 } 1563 1564 ~CommandObjectCommandsScriptAdd() override = default; 1565 1566 Options *GetOptions() override { return &m_options; } 1567 1568 protected: 1569 class CommandOptions : public Options { 1570 public: 1571 CommandOptions() 1572 : Options(), m_class_name(), m_funct_name(), m_short_help(), 1573 m_synchronicity(eScriptedCommandSynchronicitySynchronous) {} 1574 1575 ~CommandOptions() override = default; 1576 1577 Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, 1578 ExecutionContext *execution_context) override { 1579 Status error; 1580 const int short_option = m_getopt_table[option_idx].val; 1581 1582 switch (short_option) { 1583 case 'f': 1584 if (!option_arg.empty()) 1585 m_funct_name = option_arg; 1586 break; 1587 case 'c': 1588 if (!option_arg.empty()) 1589 m_class_name = option_arg; 1590 break; 1591 case 'h': 1592 if (!option_arg.empty()) 1593 m_short_help = option_arg; 1594 break; 1595 case 's': 1596 m_synchronicity = 1597 (ScriptedCommandSynchronicity)OptionArgParser::ToOptionEnum( 1598 option_arg, GetDefinitions()[option_idx].enum_values, 0, error); 1599 if (!error.Success()) 1600 error.SetErrorStringWithFormat( 1601 "unrecognized value for synchronicity '%s'", 1602 option_arg.str().c_str()); 1603 break; 1604 default: 1605 error.SetErrorStringWithFormat("unrecognized option '%c'", 1606 short_option); 1607 break; 1608 } 1609 1610 return error; 1611 } 1612 1613 void OptionParsingStarting(ExecutionContext *execution_context) override { 1614 m_class_name.clear(); 1615 m_funct_name.clear(); 1616 m_short_help.clear(); 1617 m_synchronicity = eScriptedCommandSynchronicitySynchronous; 1618 } 1619 1620 llvm::ArrayRef<OptionDefinition> GetDefinitions() override { 1621 return llvm::makeArrayRef(g_script_add_options); 1622 } 1623 1624 // Instance variables to hold the values for command options. 1625 1626 std::string m_class_name; 1627 std::string m_funct_name; 1628 std::string m_short_help; 1629 ScriptedCommandSynchronicity m_synchronicity; 1630 }; 1631 1632 void IOHandlerActivated(IOHandler &io_handler) override { 1633 StreamFileSP output_sp(io_handler.GetOutputStreamFile()); 1634 if (output_sp) { 1635 output_sp->PutCString(g_python_command_instructions); 1636 output_sp->Flush(); 1637 } 1638 } 1639 1640 void IOHandlerInputComplete(IOHandler &io_handler, 1641 std::string &data) override { 1642 StreamFileSP error_sp = io_handler.GetErrorStreamFile(); 1643 1644 ScriptInterpreter *interpreter = m_interpreter.GetScriptInterpreter(); 1645 if (interpreter) { 1646 1647 StringList lines; 1648 lines.SplitIntoLines(data); 1649 if (lines.GetSize() > 0) { 1650 std::string funct_name_str; 1651 if (interpreter->GenerateScriptAliasFunction(lines, funct_name_str)) { 1652 if (funct_name_str.empty()) { 1653 error_sp->Printf("error: unable to obtain a function name, didn't " 1654 "add python command.\n"); 1655 error_sp->Flush(); 1656 } else { 1657 // everything should be fine now, let's add this alias 1658 1659 CommandObjectSP command_obj_sp(new CommandObjectPythonFunction( 1660 m_interpreter, m_cmd_name, funct_name_str, m_short_help, 1661 m_synchronicity)); 1662 1663 if (!m_interpreter.AddUserCommand(m_cmd_name, command_obj_sp, 1664 true)) { 1665 error_sp->Printf("error: unable to add selected command, didn't " 1666 "add python command.\n"); 1667 error_sp->Flush(); 1668 } 1669 } 1670 } else { 1671 error_sp->Printf( 1672 "error: unable to create function, didn't add python command.\n"); 1673 error_sp->Flush(); 1674 } 1675 } else { 1676 error_sp->Printf("error: empty function, didn't add python command.\n"); 1677 error_sp->Flush(); 1678 } 1679 } else { 1680 error_sp->Printf( 1681 "error: script interpreter missing, didn't add python command.\n"); 1682 error_sp->Flush(); 1683 } 1684 1685 io_handler.SetIsDone(true); 1686 } 1687 1688 protected: 1689 bool DoExecute(Args &command, CommandReturnObject &result) override { 1690 if (m_interpreter.GetDebugger().GetScriptLanguage() != 1691 lldb::eScriptLanguagePython) { 1692 result.AppendError("only scripting language supported for scripted " 1693 "commands is currently Python"); 1694 result.SetStatus(eReturnStatusFailed); 1695 return false; 1696 } 1697 1698 if (command.GetArgumentCount() != 1) { 1699 result.AppendError("'command script add' requires one argument"); 1700 result.SetStatus(eReturnStatusFailed); 1701 return false; 1702 } 1703 1704 // Store the options in case we get multi-line input 1705 m_cmd_name = command[0].ref; 1706 m_short_help.assign(m_options.m_short_help); 1707 m_synchronicity = m_options.m_synchronicity; 1708 1709 if (m_options.m_class_name.empty()) { 1710 if (m_options.m_funct_name.empty()) { 1711 m_interpreter.GetPythonCommandsFromIOHandler( 1712 " ", // Prompt 1713 *this, // IOHandlerDelegate 1714 true, // Run IOHandler in async mode 1715 nullptr); // Baton for the "io_handler" that will be passed back 1716 // into our IOHandlerDelegate functions 1717 } else { 1718 CommandObjectSP new_cmd(new CommandObjectPythonFunction( 1719 m_interpreter, m_cmd_name, m_options.m_funct_name, 1720 m_options.m_short_help, m_synchronicity)); 1721 if (m_interpreter.AddUserCommand(m_cmd_name, new_cmd, true)) { 1722 result.SetStatus(eReturnStatusSuccessFinishNoResult); 1723 } else { 1724 result.AppendError("cannot add command"); 1725 result.SetStatus(eReturnStatusFailed); 1726 } 1727 } 1728 } else { 1729 ScriptInterpreter *interpreter = 1730 GetCommandInterpreter().GetScriptInterpreter(); 1731 if (!interpreter) { 1732 result.AppendError("cannot find ScriptInterpreter"); 1733 result.SetStatus(eReturnStatusFailed); 1734 return false; 1735 } 1736 1737 auto cmd_obj_sp = interpreter->CreateScriptCommandObject( 1738 m_options.m_class_name.c_str()); 1739 if (!cmd_obj_sp) { 1740 result.AppendError("cannot create helper object"); 1741 result.SetStatus(eReturnStatusFailed); 1742 return false; 1743 } 1744 1745 CommandObjectSP new_cmd(new CommandObjectScriptingObject( 1746 m_interpreter, m_cmd_name, cmd_obj_sp, m_synchronicity)); 1747 if (m_interpreter.AddUserCommand(m_cmd_name, new_cmd, true)) { 1748 result.SetStatus(eReturnStatusSuccessFinishNoResult); 1749 } else { 1750 result.AppendError("cannot add command"); 1751 result.SetStatus(eReturnStatusFailed); 1752 } 1753 } 1754 1755 return result.Succeeded(); 1756 } 1757 1758 CommandOptions m_options; 1759 std::string m_cmd_name; 1760 std::string m_short_help; 1761 ScriptedCommandSynchronicity m_synchronicity; 1762 }; 1763 1764 //------------------------------------------------------------------------- 1765 // CommandObjectCommandsScriptList 1766 //------------------------------------------------------------------------- 1767 1768 class CommandObjectCommandsScriptList : public CommandObjectParsed { 1769 public: 1770 CommandObjectCommandsScriptList(CommandInterpreter &interpreter) 1771 : CommandObjectParsed(interpreter, "command script list", 1772 "List defined scripted commands.", nullptr) {} 1773 1774 ~CommandObjectCommandsScriptList() override = default; 1775 1776 bool DoExecute(Args &command, CommandReturnObject &result) override { 1777 m_interpreter.GetHelp(result, CommandInterpreter::eCommandTypesUserDef); 1778 1779 result.SetStatus(eReturnStatusSuccessFinishResult); 1780 1781 return true; 1782 } 1783 }; 1784 1785 //------------------------------------------------------------------------- 1786 // CommandObjectCommandsScriptClear 1787 //------------------------------------------------------------------------- 1788 1789 class CommandObjectCommandsScriptClear : public CommandObjectParsed { 1790 public: 1791 CommandObjectCommandsScriptClear(CommandInterpreter &interpreter) 1792 : CommandObjectParsed(interpreter, "command script clear", 1793 "Delete all scripted commands.", nullptr) {} 1794 1795 ~CommandObjectCommandsScriptClear() override = default; 1796 1797 protected: 1798 bool DoExecute(Args &command, CommandReturnObject &result) override { 1799 m_interpreter.RemoveAllUser(); 1800 1801 result.SetStatus(eReturnStatusSuccessFinishResult); 1802 1803 return true; 1804 } 1805 }; 1806 1807 //------------------------------------------------------------------------- 1808 // CommandObjectCommandsScriptDelete 1809 //------------------------------------------------------------------------- 1810 1811 class CommandObjectCommandsScriptDelete : public CommandObjectParsed { 1812 public: 1813 CommandObjectCommandsScriptDelete(CommandInterpreter &interpreter) 1814 : CommandObjectParsed(interpreter, "command script delete", 1815 "Delete a scripted command.", nullptr) { 1816 CommandArgumentEntry arg1; 1817 CommandArgumentData cmd_arg; 1818 1819 // Define the first (and only) variant of this arg. 1820 cmd_arg.arg_type = eArgTypeCommandName; 1821 cmd_arg.arg_repetition = eArgRepeatPlain; 1822 1823 // There is only one variant this argument could be; put it into the 1824 // argument entry. 1825 arg1.push_back(cmd_arg); 1826 1827 // Push the data for the first argument into the m_arguments vector. 1828 m_arguments.push_back(arg1); 1829 } 1830 1831 ~CommandObjectCommandsScriptDelete() override = default; 1832 1833 protected: 1834 bool DoExecute(Args &command, CommandReturnObject &result) override { 1835 1836 if (command.GetArgumentCount() != 1) { 1837 result.AppendError("'command script delete' requires one argument"); 1838 result.SetStatus(eReturnStatusFailed); 1839 return false; 1840 } 1841 1842 auto cmd_name = command[0].ref; 1843 1844 if (cmd_name.empty() || !m_interpreter.HasUserCommands() || 1845 !m_interpreter.UserCommandExists(cmd_name)) { 1846 result.AppendErrorWithFormat("command %s not found", command[0].c_str()); 1847 result.SetStatus(eReturnStatusFailed); 1848 return false; 1849 } 1850 1851 m_interpreter.RemoveUser(cmd_name); 1852 result.SetStatus(eReturnStatusSuccessFinishResult); 1853 return true; 1854 } 1855 }; 1856 1857 #pragma mark CommandObjectMultiwordCommandsScript 1858 1859 //------------------------------------------------------------------------- 1860 // CommandObjectMultiwordCommandsScript 1861 //------------------------------------------------------------------------- 1862 1863 class CommandObjectMultiwordCommandsScript : public CommandObjectMultiword { 1864 public: 1865 CommandObjectMultiwordCommandsScript(CommandInterpreter &interpreter) 1866 : CommandObjectMultiword( 1867 interpreter, "command script", "Commands for managing custom " 1868 "commands implemented by " 1869 "interpreter scripts.", 1870 "command script <subcommand> [<subcommand-options>]") { 1871 LoadSubCommand("add", CommandObjectSP( 1872 new CommandObjectCommandsScriptAdd(interpreter))); 1873 LoadSubCommand( 1874 "delete", 1875 CommandObjectSP(new CommandObjectCommandsScriptDelete(interpreter))); 1876 LoadSubCommand( 1877 "clear", 1878 CommandObjectSP(new CommandObjectCommandsScriptClear(interpreter))); 1879 LoadSubCommand("list", CommandObjectSP(new CommandObjectCommandsScriptList( 1880 interpreter))); 1881 LoadSubCommand( 1882 "import", 1883 CommandObjectSP(new CommandObjectCommandsScriptImport(interpreter))); 1884 } 1885 1886 ~CommandObjectMultiwordCommandsScript() override = default; 1887 }; 1888 1889 #pragma mark CommandObjectMultiwordCommands 1890 1891 //------------------------------------------------------------------------- 1892 // CommandObjectMultiwordCommands 1893 //------------------------------------------------------------------------- 1894 1895 CommandObjectMultiwordCommands::CommandObjectMultiwordCommands( 1896 CommandInterpreter &interpreter) 1897 : CommandObjectMultiword(interpreter, "command", 1898 "Commands for managing custom LLDB commands.", 1899 "command <subcommand> [<subcommand-options>]") { 1900 LoadSubCommand("source", 1901 CommandObjectSP(new CommandObjectCommandsSource(interpreter))); 1902 LoadSubCommand("alias", 1903 CommandObjectSP(new CommandObjectCommandsAlias(interpreter))); 1904 LoadSubCommand("unalias", CommandObjectSP( 1905 new CommandObjectCommandsUnalias(interpreter))); 1906 LoadSubCommand("delete", 1907 CommandObjectSP(new CommandObjectCommandsDelete(interpreter))); 1908 LoadSubCommand( 1909 "regex", CommandObjectSP(new CommandObjectCommandsAddRegex(interpreter))); 1910 LoadSubCommand("history", CommandObjectSP( 1911 new CommandObjectCommandsHistory(interpreter))); 1912 LoadSubCommand( 1913 "script", 1914 CommandObjectSP(new CommandObjectMultiwordCommandsScript(interpreter))); 1915 } 1916 1917 CommandObjectMultiwordCommands::~CommandObjectMultiwordCommands() = default; 1918