1/* CLI options framework, for GDB.
2
3   Copyright (C) 2017-2023 Free Software Foundation, Inc.
4
5   This file is part of GDB.
6
7   This program is free software; you can redistribute it and/or modify
8   it under the terms of the GNU General Public License as published by
9   the Free Software Foundation; either version 3 of the License, or
10   (at your option) any later version.
11
12   This program is distributed in the hope that it will be useful,
13   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   GNU General Public License for more details.
16
17   You should have received a copy of the GNU General Public License
18   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
19
20#include "defs.h"
21#include "cli/cli-option.h"
22#include "cli/cli-decode.h"
23#include "cli/cli-utils.h"
24#include "cli/cli-setshow.h"
25#include "command.h"
26#include <vector>
27
28namespace gdb {
29namespace option {
30
31/* An option's value.  Which field is active depends on the option's
32   type.  */
33union option_value
34{
35  /* For var_boolean options.  */
36  bool boolean;
37
38  /* For var_uinteger options.  */
39  unsigned int uinteger;
40
41  /* For var_zuinteger_unlimited options.  */
42  int integer;
43
44  /* For var_enum options.  */
45  const char *enumeration;
46
47  /* For var_string options.  This is malloc-allocated.  */
48  std::string *string;
49};
50
51/* Holds an options definition and its value.  */
52struct option_def_and_value
53{
54  /* The option definition.  */
55  const option_def &option;
56
57  /* A context.  */
58  void *ctx;
59
60  /* The option's value, if any.  */
61  gdb::optional<option_value> value;
62
63  /* Constructor.  */
64  option_def_and_value (const option_def &option_, void *ctx_,
65			gdb::optional<option_value> &&value_ = {})
66    : option (option_),
67      ctx (ctx_),
68      value (std::move (value_))
69  {
70    clear_value (option_, value_);
71  }
72
73  /* Move constructor.  Need this because for some types the values
74     are allocated on the heap.  */
75  option_def_and_value (option_def_and_value &&rval)
76    : option (rval.option),
77      ctx (rval.ctx),
78      value (std::move (rval.value))
79  {
80    clear_value (rval.option, rval.value);
81  }
82
83  DISABLE_COPY_AND_ASSIGN (option_def_and_value);
84
85  ~option_def_and_value ()
86  {
87    if (value.has_value ())
88      {
89	if (option.type == var_string)
90	  delete value->string;
91      }
92  }
93
94private:
95
96  /* Clear the option_value, without releasing it.  This is used after
97     the value has been moved to some other option_def_and_value
98     instance.  This is needed because for some types the value is
99     allocated on the heap, so we must clear the pointer in the
100     source, to avoid a double free.  */
101  static void clear_value (const option_def &option,
102			   gdb::optional<option_value> &value)
103  {
104    if (value.has_value ())
105      {
106	if (option.type == var_string)
107	  value->string = nullptr;
108      }
109  }
110};
111
112static void save_option_value_in_ctx (gdb::optional<option_def_and_value> &ov);
113
114/* Info passed around when handling completion.  */
115struct parse_option_completion_info
116{
117  /* The completion word.  */
118  const char *word;
119
120  /* The tracker.  */
121  completion_tracker &tracker;
122};
123
124/* If ARGS starts with "-", look for a "--" delimiter.  If one is
125   found, then interpret everything up until the "--" as command line
126   options.  Otherwise, interpret unknown input as the beginning of
127   the command's operands.  */
128
129static const char *
130find_end_options_delimiter (const char *args)
131{
132  if (args[0] == '-')
133    {
134      const char *p = args;
135
136      p = skip_spaces (p);
137      while (*p)
138	{
139	  if (check_for_argument (&p, "--"))
140	    return p;
141	  else
142	    p = skip_to_space (p);
143	  p = skip_spaces (p);
144	}
145    }
146
147  return nullptr;
148}
149
150/* Complete TEXT/WORD on all options in OPTIONS_GROUP.  */
151
152static void
153complete_on_options (gdb::array_view<const option_def_group> options_group,
154		     completion_tracker &tracker,
155		     const char *text, const char *word)
156{
157  size_t textlen = strlen (text);
158  for (const auto &grp : options_group)
159    for (const auto &opt : grp.options)
160      if (strncmp (opt.name, text, textlen) == 0)
161	{
162	  tracker.add_completion
163	    (make_completion_match_str (opt.name, text, word));
164	}
165}
166
167/* See cli-option.h.  */
168
169void
170complete_on_all_options (completion_tracker &tracker,
171			 gdb::array_view<const option_def_group> options_group)
172{
173  static const char opt[] = "-";
174  complete_on_options (options_group, tracker, opt + 1, opt);
175}
176
177/* Parse ARGS, guided by OPTIONS_GROUP.  HAVE_DELIMITER is true if the
178   whole ARGS line included the "--" options-terminator delimiter.  */
179
180static gdb::optional<option_def_and_value>
181parse_option (gdb::array_view<const option_def_group> options_group,
182	      process_options_mode mode,
183	      bool have_delimiter,
184	      const char **args,
185	      parse_option_completion_info *completion = nullptr)
186{
187  if (*args == nullptr)
188    return {};
189  else if (**args != '-')
190    {
191      if (have_delimiter)
192	error (_("Unrecognized option at: %s"), *args);
193      return {};
194    }
195  else if (check_for_argument (args, "--"))
196    return {};
197
198  /* Skip the initial '-'.  */
199  const char *arg = *args + 1;
200
201  const char *after = skip_to_space (arg);
202  size_t len = after - arg;
203  const option_def *match = nullptr;
204  void *match_ctx = nullptr;
205
206  for (const auto &grp : options_group)
207    {
208      for (const auto &o : grp.options)
209	{
210	  if (strncmp (o.name, arg, len) == 0)
211	    {
212	      if (match != nullptr)
213		{
214		  if (completion != nullptr && arg[len] == '\0')
215		    {
216		      complete_on_options (options_group,
217					   completion->tracker,
218					   arg, completion->word);
219		      return {};
220		    }
221
222		  error (_("Ambiguous option at: -%s"), arg);
223		}
224
225	      match = &o;
226	      match_ctx = grp.ctx;
227
228	      if ((isspace (arg[len]) || arg[len] == '\0')
229		  && strlen (o.name) == len)
230		break; /* Exact match.  */
231	    }
232	}
233    }
234
235  if (match == nullptr)
236    {
237      if (have_delimiter || mode != PROCESS_OPTIONS_UNKNOWN_IS_OPERAND)
238	error (_("Unrecognized option at: %s"), *args);
239
240      return {};
241    }
242
243  if (completion != nullptr && arg[len] == '\0')
244    {
245      complete_on_options (options_group, completion->tracker,
246			   arg, completion->word);
247      return {};
248    }
249
250  *args += 1 + len;
251  *args = skip_spaces (*args);
252  if (completion != nullptr)
253    completion->word = *args;
254
255  switch (match->type)
256    {
257    case var_boolean:
258      {
259	if (!match->have_argument)
260	  {
261	    option_value val;
262	    val.boolean = true;
263	    return option_def_and_value {*match, match_ctx, val};
264	  }
265
266	const char *val_str = *args;
267	int res;
268
269	if (**args == '\0' && completion != nullptr)
270	  {
271	    /* Complete on both "on/off" and more options.  */
272
273	    if (mode == PROCESS_OPTIONS_REQUIRE_DELIMITER)
274	      {
275		complete_on_enum (completion->tracker,
276				  boolean_enums, val_str, val_str);
277		complete_on_all_options (completion->tracker, options_group);
278	      }
279	    return option_def_and_value {*match, match_ctx};
280	  }
281	else if (**args == '-')
282	  {
283	    /* Treat:
284		 "cmd -boolean-option -another-opt..."
285	       as:
286		 "cmd -boolean-option on -another-opt..."
287	     */
288	    res = 1;
289	  }
290	else if (**args == '\0')
291	  {
292	    /* Treat:
293		 (1) "cmd -boolean-option "
294	       as:
295		 (1) "cmd -boolean-option on"
296	     */
297	    res = 1;
298	  }
299	else
300	  {
301	    res = parse_cli_boolean_value (args);
302	    if (res < 0)
303	      {
304		const char *end = skip_to_space (*args);
305		if (completion != nullptr)
306		  {
307		    if (*end == '\0')
308		      {
309			complete_on_enum (completion->tracker,
310					  boolean_enums, val_str, val_str);
311			return option_def_and_value {*match, match_ctx};
312		      }
313		  }
314
315		if (have_delimiter)
316		  error (_("Value given for `-%s' is not a boolean: %.*s"),
317			 match->name, (int) (end - val_str), val_str);
318		/* The user didn't separate options from operands
319		   using "--", so treat this unrecognized value as the
320		   start of the operands.  This makes "frame apply all
321		   -past-main CMD" work.  */
322		return option_def_and_value {*match, match_ctx};
323	      }
324	    else if (completion != nullptr && **args == '\0')
325	      {
326		/* While "cmd -boolean [TAB]" only offers "on" and
327		   "off", the boolean option actually accepts "1",
328		   "yes", etc. as boolean values.  We complete on all
329		   of those instead of BOOLEAN_ENUMS here to make
330		   these work:
331
332		    "p -object 1[TAB]" -> "p -object 1 "
333		    "p -object ye[TAB]" -> "p -object yes "
334
335		   Etc.  Note that it's important that the space is
336		   auto-appended.  Otherwise, if we only completed on
337		   on/off here, then it might look to the user like
338		   "1" isn't valid, like:
339		   "p -object 1[TAB]" -> "p -object 1" (i.e., nothing happens).
340		*/
341		static const char *const all_boolean_enums[] = {
342		  "on", "off",
343		  "yes", "no",
344		  "enable", "disable",
345		  "0", "1",
346		  nullptr,
347		};
348		complete_on_enum (completion->tracker, all_boolean_enums,
349				  val_str, val_str);
350		return {};
351	      }
352	  }
353
354	option_value val;
355	val.boolean = res;
356	return option_def_and_value {*match, match_ctx, val};
357      }
358    case var_uinteger:
359    case var_zuinteger_unlimited:
360      {
361	if (completion != nullptr)
362	  {
363	    if (**args == '\0')
364	      {
365		/* Convenience to let the user know what the option
366		   can accept.  Note there's no common prefix between
367		   the strings on purpose, so that readline doesn't do
368		   a partial match.  */
369		completion->tracker.add_completion
370		  (make_unique_xstrdup ("NUMBER"));
371		completion->tracker.add_completion
372		  (make_unique_xstrdup ("unlimited"));
373		return {};
374	      }
375	    else if (startswith ("unlimited", *args))
376	      {
377		completion->tracker.add_completion
378		  (make_unique_xstrdup ("unlimited"));
379		return {};
380	      }
381	  }
382
383	if (match->type == var_zuinteger_unlimited)
384	  {
385	    option_value val;
386	    val.integer = parse_cli_var_zuinteger_unlimited (args, false);
387	    return option_def_and_value {*match, match_ctx, val};
388	  }
389	else
390	  {
391	    option_value val;
392	    val.uinteger = parse_cli_var_uinteger (match->type, args, false);
393	    return option_def_and_value {*match, match_ctx, val};
394	  }
395      }
396    case var_enum:
397      {
398	if (completion != nullptr)
399	  {
400	    const char *after_arg = skip_to_space (*args);
401	    if (*after_arg == '\0')
402	      {
403		complete_on_enum (completion->tracker,
404				  match->enums, *args, *args);
405		if (completion->tracker.have_completions ())
406		  return {};
407
408		/* If we don't have completions, let the
409		   non-completion path throw on invalid enum value
410		   below, so that completion processing stops.  */
411	      }
412	  }
413
414	if (check_for_argument (args, "--"))
415	  {
416	    /* Treat e.g., "backtrace -entry-values --" as if there
417	       was no argument after "-entry-values".  This makes
418	       parse_cli_var_enum throw an error with a suggestion of
419	       what are the valid options.  */
420	    args = nullptr;
421	  }
422
423	option_value val;
424	val.enumeration = parse_cli_var_enum (args, match->enums);
425	return option_def_and_value {*match, match_ctx, val};
426      }
427    case var_string:
428      {
429	if (check_for_argument (args, "--"))
430	  {
431	    /* Treat e.g., "maint test-options -string --" as if there
432	       was no argument after "-string".  */
433	    error (_("-%s requires an argument"), match->name);
434	  }
435
436	const char *arg_start = *args;
437	std::string str = extract_string_maybe_quoted (args);
438	if (*args == arg_start)
439	  error (_("-%s requires an argument"), match->name);
440
441	option_value val;
442	val.string = new std::string (std::move (str));
443	return option_def_and_value {*match, match_ctx, val};
444      }
445
446    default:
447      /* Not yet.  */
448      gdb_assert_not_reached ("option type not supported");
449    }
450
451  return {};
452}
453
454/* See cli-option.h.  */
455
456bool
457complete_options (completion_tracker &tracker,
458		  const char **args,
459		  process_options_mode mode,
460		  gdb::array_view<const option_def_group> options_group)
461{
462  const char *text = *args;
463
464  tracker.set_use_custom_word_point (true);
465
466  const char *delimiter = find_end_options_delimiter (text);
467  bool have_delimiter = delimiter != nullptr;
468
469  if (text[0] == '-' && (!have_delimiter || *delimiter == '\0'))
470    {
471      parse_option_completion_info completion_info {nullptr, tracker};
472
473      while (1)
474	{
475	  *args = skip_spaces (*args);
476	  completion_info.word = *args;
477
478	  if (strcmp (*args, "-") == 0)
479	    {
480	      complete_on_options (options_group, tracker, *args + 1,
481				   completion_info.word);
482	    }
483	  else if (strcmp (*args, "--") == 0)
484	    {
485	      tracker.add_completion (make_unique_xstrdup (*args));
486	    }
487	  else if (**args == '-')
488	    {
489	      gdb::optional<option_def_and_value> ov
490		= parse_option (options_group, mode, have_delimiter,
491				args, &completion_info);
492	      if (!ov && !tracker.have_completions ())
493		{
494		  tracker.advance_custom_word_point_by (*args - text);
495		  return mode == PROCESS_OPTIONS_REQUIRE_DELIMITER;
496		}
497
498	      if (ov
499		  && ov->option.type == var_boolean
500		  && !ov->value.has_value ())
501		{
502		  /* Looked like a boolean option, but we failed to
503		     parse the value.  If this command requires a
504		     delimiter, this value can't be the start of the
505		     operands, so return true.  Otherwise, if the
506		     command doesn't require a delimiter return false
507		     so that the caller tries to complete on the
508		     operand.  */
509		  tracker.advance_custom_word_point_by (*args - text);
510		  return mode == PROCESS_OPTIONS_REQUIRE_DELIMITER;
511		}
512
513	      /* If we parsed an option with an argument, and reached
514		 the end of the input string with no trailing space,
515		 return true, so that our callers don't try to
516		 complete anything by themselves.  E.g., this makes it
517		 so that with:
518
519		  (gdb) frame apply all -limit 10[TAB]
520
521		 we don't try to complete on command names.  */
522	      if (ov
523		  && !tracker.have_completions ()
524		  && **args == '\0'
525		  && *args > text && !isspace ((*args)[-1]))
526		{
527		  tracker.advance_custom_word_point_by
528		    (*args - text);
529		  return true;
530		}
531
532	      /* If the caller passed in a context, then it is
533		 interested in the option argument values.  */
534	      if (ov && ov->ctx != nullptr)
535		save_option_value_in_ctx (ov);
536	    }
537	  else
538	    {
539	      tracker.advance_custom_word_point_by
540		(completion_info.word - text);
541
542	      /* If the command requires a delimiter, but we haven't
543		 seen one, then return true, so that the caller
544		 doesn't try to complete on whatever follows options,
545		 which for these commands should only be done if
546		 there's a delimiter.  */
547	      if (mode == PROCESS_OPTIONS_REQUIRE_DELIMITER
548		  && !have_delimiter)
549		{
550		  /* If we reached the end of the input string, then
551		     offer all options, since that's all the user can
552		     type (plus "--").  */
553		  if (completion_info.word[0] == '\0')
554		    complete_on_all_options (tracker, options_group);
555		  return true;
556		}
557	      else
558		return false;
559	    }
560
561	  if (tracker.have_completions ())
562	    {
563	      tracker.advance_custom_word_point_by
564		(completion_info.word - text);
565	      return true;
566	    }
567	}
568    }
569  else if (delimiter != nullptr)
570    {
571      tracker.advance_custom_word_point_by (delimiter - text);
572      *args = delimiter;
573      return false;
574    }
575
576  return false;
577}
578
579/* Save the parsed value in the option's context.  */
580
581static void
582save_option_value_in_ctx (gdb::optional<option_def_and_value> &ov)
583{
584  switch (ov->option.type)
585    {
586    case var_boolean:
587      {
588	bool value = ov->value.has_value () ? ov->value->boolean : true;
589	*ov->option.var_address.boolean (ov->option, ov->ctx) = value;
590      }
591      break;
592    case var_uinteger:
593      *ov->option.var_address.uinteger (ov->option, ov->ctx)
594	= ov->value->uinteger;
595      break;
596    case var_zuinteger_unlimited:
597      *ov->option.var_address.integer (ov->option, ov->ctx)
598	= ov->value->integer;
599      break;
600    case var_enum:
601      *ov->option.var_address.enumeration (ov->option, ov->ctx)
602	= ov->value->enumeration;
603      break;
604    case var_string:
605      *ov->option.var_address.string (ov->option, ov->ctx)
606	= std::move (*ov->value->string);
607      break;
608    default:
609      gdb_assert_not_reached ("unhandled option type");
610    }
611}
612
613/* See cli-option.h.  */
614
615bool
616process_options (const char **args,
617		 process_options_mode mode,
618		 gdb::array_view<const option_def_group> options_group)
619{
620  if (*args == nullptr)
621    return false;
622
623  /* If ARGS starts with "-", look for a "--" sequence.  If one is
624     found, then interpret everything up until the "--" as
625     'gdb::option'-style command line options.  Otherwise, interpret
626     ARGS as possibly the command's operands.  */
627  bool have_delimiter = find_end_options_delimiter (*args) != nullptr;
628
629  if (mode == PROCESS_OPTIONS_REQUIRE_DELIMITER && !have_delimiter)
630    return false;
631
632  bool processed_any = false;
633
634  while (1)
635    {
636      *args = skip_spaces (*args);
637
638      auto ov = parse_option (options_group, mode, have_delimiter, args);
639      if (!ov)
640	{
641	  if (processed_any)
642	    return true;
643	  return false;
644	}
645
646      processed_any = true;
647
648      save_option_value_in_ctx (ov);
649    }
650}
651
652/* Helper for build_help.  Return a fragment of a help string showing
653   OPT's possible values.  Returns NULL if OPT doesn't take an
654   argument.  */
655
656static const char *
657get_val_type_str (const option_def &opt, std::string &buffer)
658{
659  if (!opt.have_argument)
660    return nullptr;
661
662  switch (opt.type)
663    {
664    case var_boolean:
665      return "[on|off]";
666    case var_uinteger:
667    case var_zuinteger_unlimited:
668      return "NUMBER|unlimited";
669    case var_enum:
670      {
671	buffer = "";
672	for (size_t i = 0; opt.enums[i] != nullptr; i++)
673	  {
674	    if (i != 0)
675	      buffer += "|";
676	    buffer += opt.enums[i];
677	  }
678	return buffer.c_str ();
679      }
680    case var_string:
681      return "STRING";
682    default:
683      return nullptr;
684    }
685}
686
687/* Helper for build_help.  Appends an indented version of DOC into
688   HELP.  */
689
690static void
691append_indented_doc (const char *doc, std::string &help)
692{
693  const char *p = doc;
694  const char *n = strchr (p, '\n');
695
696  while (n != nullptr)
697    {
698      help += "    ";
699      help.append (p, n - p + 1);
700      p = n + 1;
701      n = strchr (p, '\n');
702    }
703  help += "    ";
704  help += p;
705}
706
707/* Fill HELP with an auto-generated "help" string fragment for
708   OPTIONS.  */
709
710static void
711build_help_option (gdb::array_view<const option_def> options,
712		   std::string &help)
713{
714  std::string buffer;
715
716  for (const auto &o : options)
717    {
718      if (o.set_doc == nullptr)
719	continue;
720
721      help += "  -";
722      help += o.name;
723
724      const char *val_type_str = get_val_type_str (o, buffer);
725      if (val_type_str != nullptr)
726	{
727	  help += ' ';
728	  help += val_type_str;
729	}
730      help += "\n";
731      append_indented_doc (o.set_doc, help);
732      if (o.help_doc != nullptr)
733	{
734	  help += "\n";
735	  append_indented_doc (o.help_doc, help);
736	}
737    }
738}
739
740/* See cli-option.h.  */
741
742std::string
743build_help (const char *help_tmpl,
744	    gdb::array_view<const option_def_group> options_group)
745{
746  bool need_newlines = false;
747  std::string help_str;
748
749  const char *p = strstr (help_tmpl, "%OPTIONS%");
750  help_str.assign (help_tmpl, p);
751
752  for (const auto &grp : options_group)
753    for (const auto &opt : grp.options)
754      {
755	if (need_newlines)
756	  help_str += "\n\n";
757	else
758	  need_newlines = true;
759	build_help_option (opt, help_str);
760      }
761
762  p += strlen ("%OPTIONS%");
763  help_str.append (p);
764
765  return help_str;
766}
767
768/* See cli-option.h.  */
769
770void
771add_setshow_cmds_for_options (command_class cmd_class,
772			      void *data,
773			      gdb::array_view<const option_def> options,
774			      struct cmd_list_element **set_list,
775			      struct cmd_list_element **show_list)
776{
777  for (const auto &option : options)
778    {
779      if (option.type == var_boolean)
780	{
781	  add_setshow_boolean_cmd (option.name, cmd_class,
782				   option.var_address.boolean (option, data),
783				   option.set_doc, option.show_doc,
784				   option.help_doc,
785				   nullptr, option.show_cmd_cb,
786				   set_list, show_list);
787	}
788      else if (option.type == var_uinteger)
789	{
790	  add_setshow_uinteger_cmd (option.name, cmd_class,
791				    option.var_address.uinteger (option, data),
792				    option.set_doc, option.show_doc,
793				    option.help_doc,
794				    nullptr, option.show_cmd_cb,
795				    set_list, show_list);
796	}
797      else if (option.type == var_zuinteger_unlimited)
798	{
799	  add_setshow_zuinteger_unlimited_cmd
800	    (option.name, cmd_class,
801	     option.var_address.integer (option, data),
802	     option.set_doc, option.show_doc,
803	     option.help_doc,
804	     nullptr, option.show_cmd_cb,
805	     set_list, show_list);
806	}
807      else if (option.type == var_enum)
808	{
809	  add_setshow_enum_cmd (option.name, cmd_class,
810				option.enums,
811				option.var_address.enumeration (option, data),
812				option.set_doc, option.show_doc,
813				option.help_doc,
814				nullptr, option.show_cmd_cb,
815				set_list, show_list);
816	}
817      else if (option.type == var_string)
818	{
819	  add_setshow_string_cmd (option.name, cmd_class,
820				  option.var_address.string (option, data),
821				  option.set_doc, option.show_doc,
822				  option.help_doc,
823				  nullptr, option.show_cmd_cb,
824				  set_list, show_list);
825	}
826      else
827	gdb_assert_not_reached ("option type not handled");
828    }
829}
830
831} /* namespace option */
832} /* namespace gdb */
833