1 //===-- CommandObjectWatchpointCommand.cpp ----------------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include <vector>
10 
11 #include "CommandObjectWatchpoint.h"
12 #include "CommandObjectWatchpointCommand.h"
13 #include "lldb/Breakpoint/StoppointCallbackContext.h"
14 #include "lldb/Breakpoint/Watchpoint.h"
15 #include "lldb/Core/IOHandler.h"
16 #include "lldb/Host/OptionParser.h"
17 #include "lldb/Interpreter/CommandInterpreter.h"
18 #include "lldb/Interpreter/CommandReturnObject.h"
19 #include "lldb/Interpreter/OptionArgParser.h"
20 #include "lldb/Target/Target.h"
21 
22 using namespace lldb;
23 using namespace lldb_private;
24 
25 // FIXME: "script-type" needs to have its contents determined dynamically, so
26 // somebody can add a new scripting language to lldb and have it pickable here
27 // without having to change this enumeration by hand and rebuild lldb proper.
28 static constexpr OptionEnumValueElement g_script_option_enumeration[] = {
29     {
30         eScriptLanguageNone,
31         "command",
32         "Commands are in the lldb command interpreter language",
33     },
34     {
35         eScriptLanguagePython,
36         "python",
37         "Commands are in the Python language.",
38     },
39     {
40         eScriptLanguageLua,
41         "lua",
42         "Commands are in the Python language.",
43     },
44     {
45         eSortOrderByName,
46         "default-script",
47         "Commands are in the default scripting language.",
48     },
49 };
50 
51 static constexpr OptionEnumValues ScriptOptionEnum() {
52   return OptionEnumValues(g_script_option_enumeration);
53 }
54 
55 #define LLDB_OPTIONS_watchpoint_command_add
56 #include "CommandOptions.inc"
57 
58 class CommandObjectWatchpointCommandAdd : public CommandObjectParsed,
59                                           public IOHandlerDelegateMultiline {
60 public:
61   CommandObjectWatchpointCommandAdd(CommandInterpreter &interpreter)
62       : CommandObjectParsed(interpreter, "add",
63                             "Add a set of LLDB commands to a watchpoint, to be "
64                             "executed whenever the watchpoint is hit.",
65                             nullptr, eCommandRequiresTarget),
66         IOHandlerDelegateMultiline("DONE",
67                                    IOHandlerDelegate::Completion::LLDBCommand),
68         m_options() {
69     SetHelpLong(
70         R"(
71 General information about entering watchpoint commands
72 ------------------------------------------------------
73 
74 )"
75         "This command will prompt for commands to be executed when the specified \
76 watchpoint is hit.  Each command is typed on its own line following the '> ' \
77 prompt until 'DONE' is entered."
78         R"(
79 
80 )"
81         "Syntactic errors may not be detected when initially entered, and many \
82 malformed commands can silently fail when executed.  If your watchpoint commands \
83 do not appear to be executing, double-check the command syntax."
84         R"(
85 
86 )"
87         "Note: You may enter any debugger command exactly as you would at the debugger \
88 prompt.  There is no limit to the number of commands supplied, but do NOT enter \
89 more than one command per line."
90         R"(
91 
92 Special information about PYTHON watchpoint commands
93 ----------------------------------------------------
94 
95 )"
96         "You may enter either one or more lines of Python, including function \
97 definitions or calls to functions that will have been imported by the time \
98 the code executes.  Single line watchpoint commands will be interpreted 'as is' \
99 when the watchpoint is hit.  Multiple lines of Python will be wrapped in a \
100 generated function, and a call to the function will be attached to the watchpoint."
101         R"(
102 
103 This auto-generated function is passed in three arguments:
104 
105     frame:  an lldb.SBFrame object for the frame which hit the watchpoint.
106 
107     wp:     the watchpoint that was hit.
108 
109 )"
110         "When specifying a python function with the --python-function option, you need \
111 to supply the function name prepended by the module name:"
112         R"(
113 
114     --python-function myutils.watchpoint_callback
115 
116 The function itself must have the following prototype:
117 
118 def watchpoint_callback(frame, wp):
119   # Your code goes here
120 
121 )"
122         "The arguments are the same as the arguments passed to generated functions as \
123 described above.  Note that the global variable 'lldb.frame' will NOT be updated when \
124 this function is called, so be sure to use the 'frame' argument. The 'frame' argument \
125 can get you to the thread via frame.GetThread(), the thread can get you to the \
126 process via thread.GetProcess(), and the process can get you back to the target \
127 via process.GetTarget()."
128         R"(
129 
130 )"
131         "Important Note: As Python code gets collected into functions, access to global \
132 variables requires explicit scoping using the 'global' keyword.  Be sure to use correct \
133 Python syntax, including indentation, when entering Python watchpoint commands."
134         R"(
135 
136 Example Python one-line watchpoint command:
137 
138 (lldb) watchpoint command add -s python 1
139 Enter your Python command(s). Type 'DONE' to end.
140 > print "Hit this watchpoint!"
141 > DONE
142 
143 As a convenience, this also works for a short Python one-liner:
144 
145 (lldb) watchpoint command add -s python 1 -o 'import time; print time.asctime()'
146 (lldb) run
147 Launching '.../a.out'  (x86_64)
148 (lldb) Fri Sep 10 12:17:45 2010
149 Process 21778 Stopped
150 * thread #1: tid = 0x2e03, 0x0000000100000de8 a.out`c + 7 at main.c:39, stop reason = watchpoint 1.1, queue = com.apple.main-thread
151   36
152   37   	int c(int val)
153   38   	{
154   39 ->	    return val + 3;
155   40   	}
156   41
157   42   	int main (int argc, char const *argv[])
158 
159 Example multiple line Python watchpoint command, using function definition:
160 
161 (lldb) watchpoint command add -s python 1
162 Enter your Python command(s). Type 'DONE' to end.
163 > def watchpoint_output (wp_no):
164 >     out_string = "Hit watchpoint number " + repr (wp_no)
165 >     print out_string
166 >     return True
167 > watchpoint_output (1)
168 > DONE
169 
170 Example multiple line Python watchpoint command, using 'loose' Python:
171 
172 (lldb) watchpoint command add -s p 1
173 Enter your Python command(s). Type 'DONE' to end.
174 > global wp_count
175 > wp_count = wp_count + 1
176 > print "Hit this watchpoint " + repr(wp_count) + " times!"
177 > DONE
178 
179 )"
180         "In this case, since there is a reference to a global variable, \
181 'wp_count', you will also need to make sure 'wp_count' exists and is \
182 initialized:"
183         R"(
184 
185 (lldb) script
186 >>> wp_count = 0
187 >>> quit()
188 
189 )"
190         "Final Note: A warning that no watchpoint command was generated when there \
191 are no syntax errors may indicate that a function was declared but never called.");
192 
193     CommandArgumentEntry arg;
194     CommandArgumentData wp_id_arg;
195 
196     // Define the first (and only) variant of this arg.
197     wp_id_arg.arg_type = eArgTypeWatchpointID;
198     wp_id_arg.arg_repetition = eArgRepeatPlain;
199 
200     // There is only one variant this argument could be; put it into the
201     // argument entry.
202     arg.push_back(wp_id_arg);
203 
204     // Push the data for the first argument into the m_arguments vector.
205     m_arguments.push_back(arg);
206   }
207 
208   ~CommandObjectWatchpointCommandAdd() override = default;
209 
210   Options *GetOptions() override { return &m_options; }
211 
212   void IOHandlerActivated(IOHandler &io_handler, bool interactive) override {
213     StreamFileSP output_sp(io_handler.GetOutputStreamFileSP());
214     if (output_sp && interactive) {
215       output_sp->PutCString(
216           "Enter your debugger command(s).  Type 'DONE' to end.\n");
217       output_sp->Flush();
218     }
219   }
220 
221   void IOHandlerInputComplete(IOHandler &io_handler,
222                               std::string &line) override {
223     io_handler.SetIsDone(true);
224 
225     // The WatchpointOptions object is owned by the watchpoint or watchpoint
226     // location
227     WatchpointOptions *wp_options =
228         (WatchpointOptions *)io_handler.GetUserData();
229     if (wp_options) {
230       std::unique_ptr<WatchpointOptions::CommandData> data_up(
231           new WatchpointOptions::CommandData());
232       if (data_up) {
233         data_up->user_source.SplitIntoLines(line);
234         auto baton_sp = std::make_shared<WatchpointOptions::CommandBaton>(
235             std::move(data_up));
236         wp_options->SetCallback(WatchpointOptionsCallbackFunction, baton_sp);
237       }
238     }
239   }
240 
241   void CollectDataForWatchpointCommandCallback(WatchpointOptions *wp_options,
242                                                CommandReturnObject &result) {
243     m_interpreter.GetLLDBCommandsFromIOHandler(
244         "> ",        // Prompt
245         *this,       // IOHandlerDelegate
246         true,        // Run IOHandler in async mode
247         wp_options); // Baton for the "io_handler" that will be passed back into
248                      // our IOHandlerDelegate functions
249   }
250 
251   /// Set a one-liner as the callback for the watchpoint.
252   void SetWatchpointCommandCallback(WatchpointOptions *wp_options,
253                                     const char *oneliner) {
254     std::unique_ptr<WatchpointOptions::CommandData> data_up(
255         new WatchpointOptions::CommandData());
256 
257     // It's necessary to set both user_source and script_source to the
258     // oneliner. The former is used to generate callback description (as in
259     // watchpoint command list) while the latter is used for Python to
260     // interpret during the actual callback.
261     data_up->user_source.AppendString(oneliner);
262     data_up->script_source.assign(oneliner);
263     data_up->stop_on_error = m_options.m_stop_on_error;
264 
265     auto baton_sp =
266         std::make_shared<WatchpointOptions::CommandBaton>(std::move(data_up));
267     wp_options->SetCallback(WatchpointOptionsCallbackFunction, baton_sp);
268   }
269 
270   static bool
271   WatchpointOptionsCallbackFunction(void *baton,
272                                     StoppointCallbackContext *context,
273                                     lldb::user_id_t watch_id) {
274     bool ret_value = true;
275     if (baton == nullptr)
276       return true;
277 
278     WatchpointOptions::CommandData *data =
279         (WatchpointOptions::CommandData *)baton;
280     StringList &commands = data->user_source;
281 
282     if (commands.GetSize() > 0) {
283       ExecutionContext exe_ctx(context->exe_ctx_ref);
284       Target *target = exe_ctx.GetTargetPtr();
285       if (target) {
286         CommandReturnObject result;
287         Debugger &debugger = target->GetDebugger();
288         // Rig up the results secondary output stream to the debugger's, so the
289         // output will come out synchronously if the debugger is set up that
290         // way.
291 
292         StreamSP output_stream(debugger.GetAsyncOutputStream());
293         StreamSP error_stream(debugger.GetAsyncErrorStream());
294         result.SetImmediateOutputStream(output_stream);
295         result.SetImmediateErrorStream(error_stream);
296 
297         CommandInterpreterRunOptions options;
298         options.SetStopOnContinue(true);
299         options.SetStopOnError(data->stop_on_error);
300         options.SetEchoCommands(false);
301         options.SetPrintResults(true);
302         options.SetPrintErrors(true);
303         options.SetAddToHistory(false);
304 
305         debugger.GetCommandInterpreter().HandleCommands(commands, &exe_ctx,
306                                                         options, result);
307         result.GetImmediateOutputStream()->Flush();
308         result.GetImmediateErrorStream()->Flush();
309       }
310     }
311     return ret_value;
312   }
313 
314   class CommandOptions : public Options {
315   public:
316     CommandOptions()
317         : Options(), m_use_commands(false), m_use_script_language(false),
318           m_script_language(eScriptLanguageNone), m_use_one_liner(false),
319           m_one_liner(), m_function_name() {}
320 
321     ~CommandOptions() override = default;
322 
323     Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
324                           ExecutionContext *execution_context) override {
325       Status error;
326       const int short_option = m_getopt_table[option_idx].val;
327 
328       switch (short_option) {
329       case 'o':
330         m_use_one_liner = true;
331         m_one_liner = option_arg;
332         break;
333 
334       case 's':
335         m_script_language = (lldb::ScriptLanguage)OptionArgParser::ToOptionEnum(
336             option_arg, GetDefinitions()[option_idx].enum_values,
337             eScriptLanguageNone, error);
338 
339         switch (m_script_language) {
340         case eScriptLanguagePython:
341         case eScriptLanguageLua:
342           m_use_script_language = true;
343           break;
344         case eScriptLanguageNone:
345         case eScriptLanguageUnknown:
346           m_use_script_language = false;
347           break;
348         }
349         break;
350 
351       case 'e': {
352         bool success = false;
353         m_stop_on_error =
354             OptionArgParser::ToBoolean(option_arg, false, &success);
355         if (!success)
356           error.SetErrorStringWithFormat(
357               "invalid value for stop-on-error: \"%s\"",
358               option_arg.str().c_str());
359       } break;
360 
361       case 'F':
362         m_use_one_liner = false;
363         m_function_name.assign(option_arg);
364         break;
365 
366       default:
367         llvm_unreachable("Unimplemented option");
368       }
369       return error;
370     }
371 
372     void OptionParsingStarting(ExecutionContext *execution_context) override {
373       m_use_commands = true;
374       m_use_script_language = false;
375       m_script_language = eScriptLanguageNone;
376 
377       m_use_one_liner = false;
378       m_stop_on_error = true;
379       m_one_liner.clear();
380       m_function_name.clear();
381     }
382 
383     llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
384       return llvm::makeArrayRef(g_watchpoint_command_add_options);
385     }
386 
387     // Instance variables to hold the values for command options.
388 
389     bool m_use_commands;
390     bool m_use_script_language;
391     lldb::ScriptLanguage m_script_language;
392 
393     // Instance variables to hold the values for one_liner options.
394     bool m_use_one_liner;
395     std::string m_one_liner;
396     bool m_stop_on_error;
397     std::string m_function_name;
398   };
399 
400 protected:
401   bool DoExecute(Args &command, CommandReturnObject &result) override {
402     Target *target = &GetSelectedTarget();
403 
404     const WatchpointList &watchpoints = target->GetWatchpointList();
405     size_t num_watchpoints = watchpoints.GetSize();
406 
407     if (num_watchpoints == 0) {
408       result.AppendError("No watchpoints exist to have commands added");
409       result.SetStatus(eReturnStatusFailed);
410       return false;
411     }
412 
413     if (!m_options.m_function_name.empty()) {
414       if (!m_options.m_use_script_language) {
415         m_options.m_script_language = GetDebugger().GetScriptLanguage();
416         m_options.m_use_script_language = true;
417       }
418     }
419 
420     std::vector<uint32_t> valid_wp_ids;
421     if (!CommandObjectMultiwordWatchpoint::VerifyWatchpointIDs(target, command,
422                                                                valid_wp_ids)) {
423       result.AppendError("Invalid watchpoints specification.");
424       result.SetStatus(eReturnStatusFailed);
425       return false;
426     }
427 
428     result.SetStatus(eReturnStatusSuccessFinishNoResult);
429     const size_t count = valid_wp_ids.size();
430     for (size_t i = 0; i < count; ++i) {
431       uint32_t cur_wp_id = valid_wp_ids.at(i);
432       if (cur_wp_id != LLDB_INVALID_WATCH_ID) {
433         Watchpoint *wp = target->GetWatchpointList().FindByID(cur_wp_id).get();
434         // Sanity check wp first.
435         if (wp == nullptr)
436           continue;
437 
438         WatchpointOptions *wp_options = wp->GetOptions();
439         // Skip this watchpoint if wp_options is not good.
440         if (wp_options == nullptr)
441           continue;
442 
443         // If we are using script language, get the script interpreter in order
444         // to set or collect command callback.  Otherwise, call the methods
445         // associated with this object.
446         if (m_options.m_use_script_language) {
447           ScriptInterpreter *script_interp = GetDebugger().GetScriptInterpreter(
448               /*can_create=*/true, m_options.m_script_language);
449           // Special handling for one-liner specified inline.
450           if (m_options.m_use_one_liner) {
451             script_interp->SetWatchpointCommandCallback(
452                 wp_options, m_options.m_one_liner.c_str());
453           }
454           // Special handling for using a Python function by name instead of
455           // extending the watchpoint callback data structures, we just
456           // automatize what the user would do manually: make their watchpoint
457           // command be a function call
458           else if (!m_options.m_function_name.empty()) {
459             std::string oneliner(m_options.m_function_name);
460             oneliner += "(frame, wp, internal_dict)";
461             script_interp->SetWatchpointCommandCallback(
462                 wp_options, oneliner.c_str());
463           } else {
464             script_interp->CollectDataForWatchpointCommandCallback(wp_options,
465                                                                    result);
466           }
467         } else {
468           // Special handling for one-liner specified inline.
469           if (m_options.m_use_one_liner)
470             SetWatchpointCommandCallback(wp_options,
471                                          m_options.m_one_liner.c_str());
472           else
473             CollectDataForWatchpointCommandCallback(wp_options, result);
474         }
475       }
476     }
477 
478     return result.Succeeded();
479   }
480 
481 private:
482   CommandOptions m_options;
483 };
484 
485 // CommandObjectWatchpointCommandDelete
486 
487 class CommandObjectWatchpointCommandDelete : public CommandObjectParsed {
488 public:
489   CommandObjectWatchpointCommandDelete(CommandInterpreter &interpreter)
490       : CommandObjectParsed(interpreter, "delete",
491                             "Delete the set of commands from a watchpoint.",
492                             nullptr, eCommandRequiresTarget) {
493     CommandArgumentEntry arg;
494     CommandArgumentData wp_id_arg;
495 
496     // Define the first (and only) variant of this arg.
497     wp_id_arg.arg_type = eArgTypeWatchpointID;
498     wp_id_arg.arg_repetition = eArgRepeatPlain;
499 
500     // There is only one variant this argument could be; put it into the
501     // argument entry.
502     arg.push_back(wp_id_arg);
503 
504     // Push the data for the first argument into the m_arguments vector.
505     m_arguments.push_back(arg);
506   }
507 
508   ~CommandObjectWatchpointCommandDelete() override = default;
509 
510 protected:
511   bool DoExecute(Args &command, CommandReturnObject &result) override {
512     Target *target = &GetSelectedTarget();
513 
514     const WatchpointList &watchpoints = target->GetWatchpointList();
515     size_t num_watchpoints = watchpoints.GetSize();
516 
517     if (num_watchpoints == 0) {
518       result.AppendError("No watchpoints exist to have commands deleted");
519       result.SetStatus(eReturnStatusFailed);
520       return false;
521     }
522 
523     if (command.GetArgumentCount() == 0) {
524       result.AppendError(
525           "No watchpoint specified from which to delete the commands");
526       result.SetStatus(eReturnStatusFailed);
527       return false;
528     }
529 
530     std::vector<uint32_t> valid_wp_ids;
531     if (!CommandObjectMultiwordWatchpoint::VerifyWatchpointIDs(target, command,
532                                                                valid_wp_ids)) {
533       result.AppendError("Invalid watchpoints specification.");
534       result.SetStatus(eReturnStatusFailed);
535       return false;
536     }
537 
538     result.SetStatus(eReturnStatusSuccessFinishNoResult);
539     const size_t count = valid_wp_ids.size();
540     for (size_t i = 0; i < count; ++i) {
541       uint32_t cur_wp_id = valid_wp_ids.at(i);
542       if (cur_wp_id != LLDB_INVALID_WATCH_ID) {
543         Watchpoint *wp = target->GetWatchpointList().FindByID(cur_wp_id).get();
544         if (wp)
545           wp->ClearCallback();
546       } else {
547         result.AppendErrorWithFormat("Invalid watchpoint ID: %u.\n", cur_wp_id);
548         result.SetStatus(eReturnStatusFailed);
549         return false;
550       }
551     }
552     return result.Succeeded();
553   }
554 };
555 
556 // CommandObjectWatchpointCommandList
557 
558 class CommandObjectWatchpointCommandList : public CommandObjectParsed {
559 public:
560   CommandObjectWatchpointCommandList(CommandInterpreter &interpreter)
561       : CommandObjectParsed(interpreter, "list",
562                             "List the script or set of commands to be executed "
563                             "when the watchpoint is hit.",
564                             nullptr, eCommandRequiresTarget) {
565     CommandArgumentEntry arg;
566     CommandArgumentData wp_id_arg;
567 
568     // Define the first (and only) variant of this arg.
569     wp_id_arg.arg_type = eArgTypeWatchpointID;
570     wp_id_arg.arg_repetition = eArgRepeatPlain;
571 
572     // There is only one variant this argument could be; put it into the
573     // argument entry.
574     arg.push_back(wp_id_arg);
575 
576     // Push the data for the first argument into the m_arguments vector.
577     m_arguments.push_back(arg);
578   }
579 
580   ~CommandObjectWatchpointCommandList() override = default;
581 
582 protected:
583   bool DoExecute(Args &command, CommandReturnObject &result) override {
584     Target *target = &GetSelectedTarget();
585 
586     const WatchpointList &watchpoints = target->GetWatchpointList();
587     size_t num_watchpoints = watchpoints.GetSize();
588 
589     if (num_watchpoints == 0) {
590       result.AppendError("No watchpoints exist for which to list commands");
591       result.SetStatus(eReturnStatusFailed);
592       return false;
593     }
594 
595     if (command.GetArgumentCount() == 0) {
596       result.AppendError(
597           "No watchpoint specified for which to list the commands");
598       result.SetStatus(eReturnStatusFailed);
599       return false;
600     }
601 
602     std::vector<uint32_t> valid_wp_ids;
603     if (!CommandObjectMultiwordWatchpoint::VerifyWatchpointIDs(target, command,
604                                                                valid_wp_ids)) {
605       result.AppendError("Invalid watchpoints specification.");
606       result.SetStatus(eReturnStatusFailed);
607       return false;
608     }
609 
610     result.SetStatus(eReturnStatusSuccessFinishNoResult);
611     const size_t count = valid_wp_ids.size();
612     for (size_t i = 0; i < count; ++i) {
613       uint32_t cur_wp_id = valid_wp_ids.at(i);
614       if (cur_wp_id != LLDB_INVALID_WATCH_ID) {
615         Watchpoint *wp = target->GetWatchpointList().FindByID(cur_wp_id).get();
616 
617         if (wp) {
618           const WatchpointOptions *wp_options = wp->GetOptions();
619           if (wp_options) {
620             // Get the callback baton associated with the current watchpoint.
621             const Baton *baton = wp_options->GetBaton();
622             if (baton) {
623               result.GetOutputStream().Printf("Watchpoint %u:\n", cur_wp_id);
624               baton->GetDescription(result.GetOutputStream().AsRawOstream(),
625                                     eDescriptionLevelFull,
626                                     result.GetOutputStream().GetIndentLevel() +
627                                         2);
628             } else {
629               result.AppendMessageWithFormat(
630                   "Watchpoint %u does not have an associated command.\n",
631                   cur_wp_id);
632             }
633           }
634           result.SetStatus(eReturnStatusSuccessFinishResult);
635         } else {
636           result.AppendErrorWithFormat("Invalid watchpoint ID: %u.\n",
637                                        cur_wp_id);
638           result.SetStatus(eReturnStatusFailed);
639         }
640       }
641     }
642 
643     return result.Succeeded();
644   }
645 };
646 
647 // CommandObjectWatchpointCommand
648 
649 CommandObjectWatchpointCommand::CommandObjectWatchpointCommand(
650     CommandInterpreter &interpreter)
651     : CommandObjectMultiword(
652           interpreter, "command",
653           "Commands for adding, removing and examining LLDB commands "
654           "executed when the watchpoint is hit (watchpoint 'commands').",
655           "command <sub-command> [<sub-command-options>] <watchpoint-id>") {
656   CommandObjectSP add_command_object(
657       new CommandObjectWatchpointCommandAdd(interpreter));
658   CommandObjectSP delete_command_object(
659       new CommandObjectWatchpointCommandDelete(interpreter));
660   CommandObjectSP list_command_object(
661       new CommandObjectWatchpointCommandList(interpreter));
662 
663   add_command_object->SetCommandName("watchpoint command add");
664   delete_command_object->SetCommandName("watchpoint command delete");
665   list_command_object->SetCommandName("watchpoint command list");
666 
667   LoadSubCommand("add", add_command_object);
668   LoadSubCommand("delete", delete_command_object);
669   LoadSubCommand("list", list_command_object);
670 }
671 
672 CommandObjectWatchpointCommand::~CommandObjectWatchpointCommand() = default;
673