1 /* MI Command Set - MI parser.
2 
3    Copyright (C) 2000-2024 Free Software Foundation, Inc.
4 
5    Contributed by Cygnus Solutions (a Red Hat company).
6 
7    This file is part of GDB.
8 
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 3 of the License, or
12    (at your option) any later version.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
21 
22 #include "mi-cmds.h"
23 #include "mi-parse.h"
24 #include "charset.h"
25 
26 #include <ctype.h>
27 #include "cli/cli-utils.h"
28 #include "language.h"
29 
30 static const char mi_no_values[] = "--no-values";
31 static const char mi_simple_values[] = "--simple-values";
32 static const char mi_all_values[] = "--all-values";
33 
34 /* Like parse_escape, but leave the results as a host char, not a
35    target char.  */
36 
37 static int
mi_parse_escape(const char ** string_ptr)38 mi_parse_escape (const char **string_ptr)
39 {
40   int c = *(*string_ptr)++;
41 
42   switch (c)
43     {
44       case '\n':
45           return -2;
46       case 0:
47           (*string_ptr)--;
48           return 0;
49 
50       case '0':
51       case '1':
52       case '2':
53       case '3':
54       case '4':
55       case '5':
56       case '6':
57       case '7':
58           {
59             int i = fromhex (c);
60             int count = 0;
61 
62             while (++count < 3)
63               {
64                 c = (**string_ptr);
65                 if (isdigit (c) && c != '8' && c != '9')
66                     {
67                       (*string_ptr)++;
68                       i *= 8;
69                       i += fromhex (c);
70                     }
71                 else
72                     {
73                       break;
74                     }
75               }
76             return i;
77           }
78 
79     case 'a':
80       c = '\a';
81       break;
82     case 'b':
83       c = '\b';
84       break;
85     case 'f':
86       c = '\f';
87       break;
88     case 'n':
89       c = '\n';
90       break;
91     case 'r':
92       c = '\r';
93       break;
94     case 't':
95       c = '\t';
96       break;
97     case 'v':
98       c = '\v';
99       break;
100 
101     default:
102       break;
103     }
104 
105   return c;
106 }
107 
108 void
parse_argv()109 mi_parse::parse_argv ()
110 {
111   /* If arguments were already computed (or were supplied at
112      construction), then there's no need to re-compute them.  */
113   if (argv != nullptr)
114     return;
115 
116   const char *chp = m_args.c_str ();
117   int argc = 0;
118   char **argv = XNEWVEC (char *, argc + 1);
119 
120   argv[argc] = NULL;
121   while (1)
122     {
123       char *arg;
124 
125       /* Skip leading white space.  */
126       chp = skip_spaces (chp);
127       /* Three possibilities: EOF, quoted string, or other text. */
128       switch (*chp)
129           {
130           case '\0':
131             this->argv = argv;
132             this->argc = argc;
133             return;
134           case '"':
135             {
136               /* A quoted string.  */
137               int len;
138               const char *start = chp + 1;
139 
140               /* Determine the buffer size.  */
141               chp = start;
142               len = 0;
143               while (*chp != '\0' && *chp != '"')
144                 {
145                     if (*chp == '\\')
146                       {
147                         chp++;
148                         if (mi_parse_escape (&chp) <= 0)
149                           {
150                               /* Do not allow split lines or "\000".  */
151                               freeargv (argv);
152                               return;
153                           }
154                       }
155                     else
156                       chp++;
157                     len++;
158                 }
159               /* Insist on a closing quote.  */
160               if (*chp != '"')
161                 {
162                     freeargv (argv);
163                     return;
164                 }
165               /* Insist on trailing white space.  */
166               if (chp[1] != '\0' && !isspace (chp[1]))
167                 {
168                     freeargv (argv);
169                     return;
170                 }
171               /* Create the buffer and copy characters in.  */
172               arg = XNEWVEC (char, len + 1);
173               chp = start;
174               len = 0;
175               while (*chp != '\0' && *chp != '"')
176                 {
177                     if (*chp == '\\')
178                       {
179                         chp++;
180                         arg[len] = mi_parse_escape (&chp);
181                       }
182                     else
183                       arg[len] = *chp++;
184                     len++;
185                 }
186               arg[len] = '\0';
187               chp++;                    /* That closing quote.  */
188               break;
189             }
190           default:
191             {
192               /* An unquoted string.  Accumulate all non-blank
193                  characters into a buffer.  */
194               int len;
195               const char *start = chp;
196 
197               while (*chp != '\0' && !isspace (*chp))
198                 {
199                     chp++;
200                 }
201               len = chp - start;
202               arg = XNEWVEC (char, len + 1);
203               strncpy (arg, start, len);
204               arg[len] = '\0';
205               break;
206             }
207           }
208       /* Append arg to argv.  */
209       argv = XRESIZEVEC (char *, argv, argc + 2);
210       argv[argc++] = arg;
211       argv[argc] = NULL;
212     }
213 }
214 
~mi_parse()215 mi_parse::~mi_parse ()
216 {
217   freeargv (argv);
218 }
219 
220 /* See mi-parse.h.  */
221 
222 const char *
args()223 mi_parse::args ()
224 {
225   /* If args were already computed, or if there is no pre-computed
226      argv, just return the args.  */
227   if (!m_args.empty () || argv == nullptr)
228     return  m_args.c_str ();
229 
230   /* Compute args from argv.  */
231   for (int i = 0; i < argc; ++i)
232     {
233       if (!m_args.empty ())
234           m_args += " ";
235       m_args += argv[i];
236     }
237 
238   return m_args.c_str ();
239 }
240 
241 /* See mi-parse.h.  */
242 
243 void
set_thread_group(const char * arg,char ** endp)244 mi_parse::set_thread_group (const char *arg, char **endp)
245 {
246   if (thread_group != -1)
247     error (_("Duplicate '--thread-group' option"));
248   if (*arg != 'i')
249     error (_("Invalid thread group id"));
250   arg += 1;
251   thread_group = strtol (arg, endp, 10);
252 }
253 
254 /* See mi-parse.h.  */
255 
256 void
set_thread(const char * arg,char ** endp)257 mi_parse::set_thread (const char *arg, char **endp)
258 {
259   if (thread != -1)
260     error (_("Duplicate '--thread' option"));
261   thread = strtol (arg, endp, 10);
262 }
263 
264 /* See mi-parse.h.  */
265 
266 void
set_frame(const char * arg,char ** endp)267 mi_parse::set_frame (const char *arg, char **endp)
268 {
269   if (frame != -1)
270     error (_("Duplicate '--frame' option"));
271   frame = strtol (arg, endp, 10);
272 }
273 
274 /* See mi-parse.h.  */
275 
276 void
set_language(const char * arg,const char ** endp)277 mi_parse::set_language (const char *arg, const char **endp)
278 {
279   std::string lang_name = extract_arg (&arg);
280 
281   language = language_enum (lang_name.c_str ());
282   if (language == language_unknown)
283     error (_("Invalid --language argument: %s"), lang_name.c_str ());
284 
285   if (endp != nullptr)
286     *endp = arg;
287 }
288 
289 /* See mi-parse.h.  */
290 
mi_parse(const char * cmd,std::string * token)291 mi_parse::mi_parse (const char *cmd, std::string *token)
292 {
293   const char *chp;
294 
295   /* Before starting, skip leading white space.  */
296   cmd = skip_spaces (cmd);
297 
298   /* Find/skip any token and then extract it.  */
299   for (chp = cmd; *chp >= '0' && *chp <= '9'; chp++)
300     ;
301   *token = std::string (cmd, chp - cmd);
302 
303   /* This wasn't a real MI command.  Return it as a CLI_COMMAND.  */
304   if (*chp != '-')
305     {
306       chp = skip_spaces (chp);
307       this->command = make_unique_xstrdup (chp);
308       this->op = CLI_COMMAND;
309 
310       return;
311     }
312 
313   /* Extract the command.  */
314   {
315     const char *tmp = chp + 1;          /* discard ``-'' */
316 
317     for (; *chp && !isspace (*chp); chp++)
318       ;
319     this->command = make_unique_xstrndup (tmp, chp - tmp);
320   }
321 
322   /* Find the command in the MI table.  */
323   this->cmd = mi_cmd_lookup (this->command.get ());
324   if (this->cmd == NULL)
325     throw_error (UNDEFINED_COMMAND_ERROR,
326                      _("Undefined MI command: %s"), this->command.get ());
327 
328   /* Skip white space following the command.  */
329   chp = skip_spaces (chp);
330 
331   /* Parse the --thread and --frame options, if present.  At present,
332      some important commands, like '-break-*' are implemented by
333      forwarding to the CLI layer directly.  We want to parse --thread
334      and --frame here, so as not to leave those option in the string
335      that will be passed to CLI.
336 
337      Same for the --language option.  */
338 
339   for (;;)
340     {
341       const char *option;
342       size_t as = sizeof ("--all ") - 1;
343       size_t tgs = sizeof ("--thread-group ") - 1;
344       size_t ts = sizeof ("--thread ") - 1;
345       size_t fs = sizeof ("--frame ") - 1;
346       size_t ls = sizeof ("--language ") - 1;
347 
348       if (strncmp (chp, "--all ", as) == 0)
349           {
350             this->all = 1;
351             chp += as;
352           }
353       /* See if --all is the last token in the input.  */
354       if (strcmp (chp, "--all") == 0)
355           {
356             this->all = 1;
357             chp += strlen (chp);
358           }
359       if (strncmp (chp, "--thread-group ", tgs) == 0)
360           {
361             char *endp;
362 
363             option = "--thread-group";
364             chp += tgs;
365             this->set_thread_group (chp, &endp);
366             chp = endp;
367           }
368       else if (strncmp (chp, "--thread ", ts) == 0)
369           {
370             char *endp;
371 
372             option = "--thread";
373             chp += ts;
374             this->set_thread (chp, &endp);
375             chp = endp;
376           }
377       else if (strncmp (chp, "--frame ", fs) == 0)
378           {
379             char *endp;
380 
381             option = "--frame";
382             chp += fs;
383             this->set_frame (chp, &endp);
384             chp = endp;
385           }
386       else if (strncmp (chp, "--language ", ls) == 0)
387           {
388             option = "--language";
389             chp += ls;
390             this->set_language (chp, &chp);
391           }
392       else
393           break;
394 
395       if (*chp != '\0' && !isspace (*chp))
396           error (_("Invalid value for the '%s' option"), option);
397       chp = skip_spaces (chp);
398     }
399 
400   /* Save the rest of the arguments for the command.  */
401   this->m_args = chp;
402 
403   /* Fully parsed, flag as an MI command.  */
404   this->op = MI_COMMAND;
405 }
406 
407 /* See mi-parse.h.  */
408 
mi_parse(gdb::unique_xmalloc_ptr<char> command,std::vector<gdb::unique_xmalloc_ptr<char>> args)409 mi_parse::mi_parse (gdb::unique_xmalloc_ptr<char> command,
410                         std::vector<gdb::unique_xmalloc_ptr<char>> args)
411 {
412   this->command = std::move (command);
413   this->token = "";
414 
415   if (this->command.get ()[0] != '-')
416     throw_error (UNDEFINED_COMMAND_ERROR,
417                      _("MI command '%s' does not start with '-'"),
418                      this->command.get ());
419 
420   /* Find the command in the MI table.  */
421   this->cmd = mi_cmd_lookup (this->command.get () + 1);
422   if (this->cmd == NULL)
423     throw_error (UNDEFINED_COMMAND_ERROR,
424                      _("Undefined MI command: %s"), this->command.get ());
425 
426   /* This over-allocates slightly, but it seems unimportant.  */
427   this->argv = XCNEWVEC (char *, args.size () + 1);
428 
429   for (size_t i = 0; i < args.size (); ++i)
430     {
431       const char *chp = args[i].get ();
432 
433       /* See if --all is the last token in the input.  */
434       if (strcmp (chp, "--all") == 0)
435           {
436             this->all = 1;
437           }
438       else if (strcmp (chp, "--thread-group") == 0)
439           {
440             ++i;
441             if (i == args.size ())
442               error ("No argument to '--thread-group'");
443             this->set_thread_group (args[i].get (), nullptr);
444           }
445       else if (strcmp (chp, "--thread") == 0)
446           {
447             ++i;
448             if (i == args.size ())
449               error ("No argument to '--thread'");
450             this->set_thread (args[i].get (), nullptr);
451           }
452       else if (strcmp (chp, "--frame") == 0)
453           {
454             ++i;
455             if (i == args.size ())
456               error ("No argument to '--frame'");
457             this->set_frame (args[i].get (), nullptr);
458           }
459       else if (strcmp (chp, "--language") == 0)
460           {
461             ++i;
462             if (i == args.size ())
463               error ("No argument to '--language'");
464             this->set_language (args[i].get (), nullptr);
465           }
466       else
467           this->argv[this->argc++] = args[i].release ();
468     }
469 
470   /* Fully parsed, flag as an MI command.  */
471   this->op = MI_COMMAND;
472 }
473 
474 enum print_values
mi_parse_print_values(const char * name)475 mi_parse_print_values (const char *name)
476 {
477    if (strcmp (name, "0") == 0
478        || strcmp (name, mi_no_values) == 0)
479      return PRINT_NO_VALUES;
480    else if (strcmp (name, "1") == 0
481               || strcmp (name, mi_all_values) == 0)
482      return PRINT_ALL_VALUES;
483    else if (strcmp (name, "2") == 0
484               || strcmp (name, mi_simple_values) == 0)
485      return PRINT_SIMPLE_VALUES;
486    else
487      error (_("Unknown value for PRINT_VALUES: must be: \
488 0 or \"%s\", 1 or \"%s\", 2 or \"%s\""),
489               mi_no_values, mi_all_values, mi_simple_values);
490 }
491