break-cond-parse.c revision 1.1 1 1.1 christos /* Copyright (C) 2023 Free Software Foundation, Inc.
2 1.1 christos
3 1.1 christos This file is part of GDB.
4 1.1 christos
5 1.1 christos This program is free software; you can redistribute it and/or modify
6 1.1 christos it under the terms of the GNU General Public License as published by
7 1.1 christos the Free Software Foundation; either version 3 of the License, or
8 1.1 christos (at your option) any later version.
9 1.1 christos
10 1.1 christos This program is distributed in the hope that it will be useful,
11 1.1 christos but WITHOUT ANY WARRANTY; without even the implied warranty of
12 1.1 christos MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 1.1 christos GNU General Public License for more details.
14 1.1 christos
15 1.1 christos You should have received a copy of the GNU General Public License
16 1.1 christos along with this program. If not, see <http://www.gnu.org/licenses/>. */
17 1.1 christos
18 1.1 christos #include "defs.h"
19 1.1 christos #include "gdbsupport/gdb_assert.h"
20 1.1 christos #include "gdbsupport/selftest.h"
21 1.1 christos #include "test-target.h"
22 1.1 christos #include "scoped-mock-context.h"
23 1.1 christos #include "break-cond-parse.h"
24 1.1 christos #include "tid-parse.h"
25 1.1 christos #include "ada-lang.h"
26 1.1 christos #include "exceptions.h"
27 1.1 christos
28 1.1 christos /* When parsing tokens from a string, which direction are we parsing?
29 1.1 christos
30 1.1 christos Given the following string and pointer 'ptr':
31 1.1 christos
32 1.1 christos ABC DEF GHI JKL
33 1.1 christos ^
34 1.1 christos ptr
35 1.1 christos
36 1.1 christos Parsing 'forward' will return the token 'GHI' and update 'ptr' to point
37 1.1 christos between GHI and JKL. Parsing 'backward' will return the token 'DEF' and
38 1.1 christos update 'ptr' to point between ABC and DEF.
39 1.1 christos */
40 1.1 christos
41 1.1 christos enum class parse_direction
42 1.1 christos {
43 1.1 christos /* Parse the next token forwards. */
44 1.1 christos forward,
45 1.1 christos
46 1.1 christos /* Parse the previous token backwards. */
47 1.1 christos backward
48 1.1 christos };
49 1.1 christos
50 1.1 christos /* Find the next token in DIRECTION from *CURR. */
51 1.1 christos
52 1.1 christos static std::string_view
53 1.1 christos find_next_token (const char **curr, parse_direction direction)
54 1.1 christos {
55 1.1 christos const char *tok_start, *tok_end;
56 1.1 christos
57 1.1 christos gdb_assert (**curr != '\0');
58 1.1 christos
59 1.1 christos if (direction == parse_direction::forward)
60 1.1 christos {
61 1.1 christos *curr = skip_spaces (*curr);
62 1.1 christos tok_start = *curr;
63 1.1 christos *curr = skip_to_space (*curr);
64 1.1 christos tok_end = *curr - 1;
65 1.1 christos }
66 1.1 christos else
67 1.1 christos {
68 1.1 christos gdb_assert (direction == parse_direction::backward);
69 1.1 christos
70 1.1 christos while (isspace (**curr))
71 1.1 christos --(*curr);
72 1.1 christos
73 1.1 christos tok_end = *curr;
74 1.1 christos
75 1.1 christos while (!isspace (**curr))
76 1.1 christos --(*curr);
77 1.1 christos
78 1.1 christos tok_start = (*curr) + 1;
79 1.1 christos }
80 1.1 christos
81 1.1 christos return std::string_view (tok_start, tok_end - tok_start + 1);
82 1.1 christos }
83 1.1 christos
84 1.1 christos /* A class that represents a complete parsed token. Each token has a type
85 1.1 christos and a std::string_view into the original breakpoint condition string. */
86 1.1 christos
87 1.1 christos struct token
88 1.1 christos {
89 1.1 christos /* The types a token might take. */
90 1.1 christos enum class type
91 1.1 christos {
92 1.1 christos /* These are the token types for the 'if', 'thread', 'inferior', and
93 1.1 christos 'task' keywords. The m_content for these token types is the value
94 1.1 christos passed to the keyword, not the keyword itself. */
95 1.1 christos CONDITION,
96 1.1 christos THREAD,
97 1.1 christos INFERIOR,
98 1.1 christos TASK,
99 1.1 christos
100 1.1 christos /* This is the token used when we find unknown content, the m_content
101 1.1 christos for this token is the rest of the input string. */
102 1.1 christos REST,
103 1.1 christos
104 1.1 christos /* This is the token for the -force-condition token, the m_content for
105 1.1 christos this token contains the keyword itself. */
106 1.1 christos FORCE
107 1.1 christos };
108 1.1 christos
109 1.1 christos token (enum type type, std::string_view content)
110 1.1 christos : m_type (type),
111 1.1 christos m_content (std::move (content))
112 1.1 christos {
113 1.1 christos /* Nothing. */
114 1.1 christos }
115 1.1 christos
116 1.1 christos /* Return a string representing this token. Only used for debug. */
117 1.1 christos std::string to_string () const
118 1.1 christos {
119 1.1 christos switch (m_type)
120 1.1 christos {
121 1.1 christos case type::CONDITION:
122 1.1 christos return string_printf ("{ CONDITION: \"%s\" }",
123 1.1 christos std::string (m_content).c_str ());
124 1.1 christos case type::THREAD:
125 1.1 christos return string_printf ("{ THREAD: \"%s\" }",
126 1.1 christos std::string (m_content).c_str ());
127 1.1 christos case type::INFERIOR:
128 1.1 christos return string_printf ("{ INFERIOR: \"%s\" }",
129 1.1 christos std::string (m_content).c_str ());
130 1.1 christos case type::TASK:
131 1.1 christos return string_printf ("{ TASK: \"%s\" }",
132 1.1 christos std::string (m_content).c_str ());
133 1.1 christos case type::REST:
134 1.1 christos return string_printf ("{ REST: \"%s\" }",
135 1.1 christos std::string (m_content).c_str ());
136 1.1 christos case type::FORCE:
137 1.1 christos return string_printf ("{ FORCE }");
138 1.1 christos default:
139 1.1 christos return "** unknown **";
140 1.1 christos }
141 1.1 christos }
142 1.1 christos
143 1.1 christos /* The type of this token. */
144 1.1 christos const type &get_type () const
145 1.1 christos {
146 1.1 christos return m_type;
147 1.1 christos }
148 1.1 christos
149 1.1 christos /* Return the value of this token. */
150 1.1 christos const std::string_view &get_value () const
151 1.1 christos {
152 1.1 christos gdb_assert (m_content.size () > 0);
153 1.1 christos return m_content;
154 1.1 christos }
155 1.1 christos
156 1.1 christos /* Extend this token with the contents of OTHER. This only makes sense
157 1.1 christos if OTHER is the next token after this one in the original string,
158 1.1 christos however, enforcing that restriction is left to the caller of this
159 1.1 christos function.
160 1.1 christos
161 1.1 christos When OTHER is a keyword/value token, e.g. 'thread 1', the m_content
162 1.1 christos for OTHER will only point to the '1'. However, as the m_content is a
163 1.1 christos std::string_view, then when we merge the m_content of OTHER into this
164 1.1 christos token we automatically merge in the 'thread' part too, as it
165 1.1 christos naturally sits between this token and OTHER. */
166 1.1 christos
167 1.1 christos void
168 1.1 christos extend (const token &other)
169 1.1 christos {
170 1.1 christos m_content = std::string_view (this->m_content.data (),
171 1.1 christos (other.m_content.data ()
172 1.1 christos - this->m_content.data ()
173 1.1 christos + other.m_content.size ()));
174 1.1 christos }
175 1.1 christos
176 1.1 christos private:
177 1.1 christos /* The type of this token. */
178 1.1 christos type m_type;
179 1.1 christos
180 1.1 christos /* The important content part of this token. The extend member function
181 1.1 christos depends on this being a std::string_view. */
182 1.1 christos std::string_view m_content;
183 1.1 christos };
184 1.1 christos
185 1.1 christos /* Split STR, a breakpoint condition string, into a vector of tokens where
186 1.1 christos each token represents a component of the condition. Tokens are first
187 1.1 christos parsed from the front of STR until we encounter an 'if' token. At this
188 1.1 christos point tokens are parsed from the end of STR until we encounter an
189 1.1 christos unknown token, which we assume is the other end of the 'if' condition.
190 1.1 christos If when scanning forward we encounter an unknown token then the
191 1.1 christos remainder of STR is placed into a 'rest' token (the rest of the
192 1.1 christos string), and no backward scan is performed. */
193 1.1 christos
194 1.1 christos static std::vector<token>
195 1.1 christos parse_all_tokens (const char *str)
196 1.1 christos {
197 1.1 christos gdb_assert (str != nullptr);
198 1.1 christos
199 1.1 christos std::vector<token> forward_results;
200 1.1 christos std::vector<token> backward_results;
201 1.1 christos
202 1.1 christos const char *cond_start = nullptr;
203 1.1 christos const char *cond_end = nullptr;
204 1.1 christos parse_direction direction = parse_direction::forward;
205 1.1 christos std::vector<token> *curr_results = &forward_results;
206 1.1 christos while (*str != '\0')
207 1.1 christos {
208 1.1 christos /* Find the next token. If moving backward and this token starts at
209 1.1 christos the same location as the condition then we must have found the
210 1.1 christos other end of the condition string -- we're done. */
211 1.1 christos std::string_view t = find_next_token (&str, direction);
212 1.1 christos if (direction == parse_direction::backward && t.data () <= cond_start)
213 1.1 christos {
214 1.1 christos cond_end = &t.back ();
215 1.1 christos break;
216 1.1 christos }
217 1.1 christos
218 1.1 christos /* We only have a single flag option to check for. All the other
219 1.1 christos options take a value so require an additional token to be found.
220 1.1 christos Additionally, we require that this flag be at least '-f', we
221 1.1 christos don't allow it to be abbreviated to '-'. */
222 1.1 christos if (t.length () > 1 && startswith ("-force-condition", t))
223 1.1 christos {
224 1.1 christos curr_results->emplace_back (token::type::FORCE, t);
225 1.1 christos continue;
226 1.1 christos }
227 1.1 christos
228 1.1 christos /* Maybe the first token was the last token in the string. If this
229 1.1 christos is the case then we definitely can't try to extract a value
230 1.1 christos token. This also means that the token T is meaningless. Reset
231 1.1 christos TOK to point at the start of the unknown content and break out of
232 1.1 christos the loop. We'll record the unknown part of the string outside of
233 1.1 christos the scanning loop (below). */
234 1.1 christos if (direction == parse_direction::forward && *str == '\0')
235 1.1 christos {
236 1.1 christos str = t.data ();
237 1.1 christos break;
238 1.1 christos }
239 1.1 christos
240 1.1 christos /* As before, find the next token and, if we are scanning backwards,
241 1.1 christos check that we have not reached the start of the condition string. */
242 1.1 christos std::string_view v = find_next_token (&str, direction);
243 1.1 christos if (direction == parse_direction::backward && v.data () <= cond_start)
244 1.1 christos {
245 1.1 christos /* Use token T here as that must also be part of the condition
246 1.1 christos string. */
247 1.1 christos cond_end = &t.back ();
248 1.1 christos break;
249 1.1 christos }
250 1.1 christos
251 1.1 christos /* When moving backward we will first parse the value token then the
252 1.1 christos keyword token, so swap them now. */
253 1.1 christos if (direction == parse_direction::backward)
254 1.1 christos std::swap (t, v);
255 1.1 christos
256 1.1 christos /* Check for valid option in token T. If we find a valid option then
257 1.1 christos parse the value from the token V. Except for 'if', that's handled
258 1.1 christos differently.
259 1.1 christos
260 1.1 christos For the 'if' token we need to capture the entire condition
261 1.1 christos string, so record the start of the condition string and then
262 1.1 christos start scanning backwards looking for the end of the condition
263 1.1 christos string.
264 1.1 christos
265 1.1 christos The order of these checks is important, at least the check for
266 1.1 christos 'thread' must occur before the check for 'task'. We accept
267 1.1 christos abbreviations of these token names, and 't' should resolve to
268 1.1 christos 'thread', which will only happen if we check 'thread' first. */
269 1.1 christos if (direction == parse_direction::forward && startswith ("if", t))
270 1.1 christos {
271 1.1 christos cond_start = v.data ();
272 1.1 christos str = str + strlen (str);
273 1.1 christos gdb_assert (*str == '\0');
274 1.1 christos --str;
275 1.1 christos direction = parse_direction::backward;
276 1.1 christos curr_results = &backward_results;
277 1.1 christos continue;
278 1.1 christos }
279 1.1 christos else if (startswith ("thread", t))
280 1.1 christos curr_results->emplace_back (token::type::THREAD, v);
281 1.1 christos else if (startswith ("inferior", t))
282 1.1 christos curr_results->emplace_back (token::type::INFERIOR, v);
283 1.1 christos else if (startswith ("task", t))
284 1.1 christos curr_results->emplace_back (token::type::TASK, v);
285 1.1 christos else
286 1.1 christos {
287 1.1 christos /* An unknown token. If we are scanning forward then reset TOK
288 1.1 christos to point at the start of the unknown content, we record this
289 1.1 christos outside of the scanning loop (below).
290 1.1 christos
291 1.1 christos If we are scanning backward then unknown content is assumed to
292 1.1 christos be the other end of the condition string, obviously, this is
293 1.1 christos just a heuristic, we could be looking at a mistyped command
294 1.1 christos line, but this will be spotted when the condition is
295 1.1 christos eventually evaluated.
296 1.1 christos
297 1.1 christos Either way, no more scanning is required after this. */
298 1.1 christos if (direction == parse_direction::forward)
299 1.1 christos str = t.data ();
300 1.1 christos else
301 1.1 christos {
302 1.1 christos gdb_assert (direction == parse_direction::backward);
303 1.1 christos cond_end = &v.back ();
304 1.1 christos }
305 1.1 christos break;
306 1.1 christos }
307 1.1 christos }
308 1.1 christos
309 1.1 christos if (cond_start != nullptr)
310 1.1 christos {
311 1.1 christos /* If we found the start of a condition string then we should have
312 1.1 christos switched to backward scan mode, and found the end of the condition
313 1.1 christos string. Capture the whole condition string into COND_STRING
314 1.1 christos now. */
315 1.1 christos gdb_assert (direction == parse_direction::backward);
316 1.1 christos gdb_assert (cond_end != nullptr);
317 1.1 christos
318 1.1 christos std::string_view v (cond_start, cond_end - cond_start + 1);
319 1.1 christos
320 1.1 christos forward_results.emplace_back (token::type::CONDITION, v);
321 1.1 christos }
322 1.1 christos else if (*str != '\0')
323 1.1 christos {
324 1.1 christos /* If we didn't have a condition start pointer then we should still
325 1.1 christos be in forward scanning mode. If we didn't reach the end of the
326 1.1 christos input string (TOK is not at the null character) then the rest of
327 1.1 christos the input string is garbage that we didn't understand.
328 1.1 christos
329 1.1 christos Record the unknown content into REST. The caller of this function
330 1.1 christos will report this as an error later on. We could report the error
331 1.1 christos here, but we prefer to allow the caller to run other checks, and
332 1.1 christos prioritise other errors before reporting this problem. */
333 1.1 christos gdb_assert (direction == parse_direction::forward);
334 1.1 christos gdb_assert (cond_end == nullptr);
335 1.1 christos
336 1.1 christos std::string_view v (str, strlen (str));
337 1.1 christos
338 1.1 christos forward_results.emplace_back (token::type::REST, v);
339 1.1 christos }
340 1.1 christos
341 1.1 christos /* If we have tokens in the BACKWARD_RESULTS vector then this means that
342 1.1 christos we found an 'if' condition (which will be the last thing in the
343 1.1 christos FORWARD_RESULTS vector), and then we started a backward scan.
344 1.1 christos
345 1.1 christos The last tokens from the input string (those after the 'if' condition)
346 1.1 christos will be the first tokens added to the BACKWARD_RESULTS vector, so the
347 1.1 christos last items in the BACKWARD_RESULTS vector are those next to the 'if'
348 1.1 christos condition.
349 1.1 christos
350 1.1 christos Check the tokens in the BACKWARD_RESULTS vector from back to front.
351 1.1 christos If the tokens look invalid then we assume that they are actually part
352 1.1 christos of the 'if' condition, and merge the token with the 'if' condition.
353 1.1 christos If it turns out that this was incorrect and that instead the user just
354 1.1 christos messed up entering the token value, then this will show as an error
355 1.1 christos when parsing the 'if' condition.
356 1.1 christos
357 1.1 christos Doing this allows us to handle things like:
358 1.1 christos
359 1.1 christos break function if ( variable == thread )
360 1.1 christos
361 1.1 christos Where 'thread' is a local variable within 'function'. When parsing
362 1.1 christos this we will initially see 'thread )' as a thread token with ')' as
363 1.1 christos the value. However, the following code will spot that ')' is not a
364 1.1 christos valid thread-id, and so we merge 'thread )' into the 'if' condition
365 1.1 christos string.
366 1.1 christos
367 1.1 christos This code also handles the special treatment for '-force-condition',
368 1.1 christos which exists for backwards compatibility reasons. Traditionally this
369 1.1 christos flag, if it occurred immediately after the 'if' condition, would be
370 1.1 christos treated as part of the 'if' condition. When the breakpoint condition
371 1.1 christos parsing code was rewritten, this behavior was retained. */
372 1.1 christos gdb_assert (backward_results.empty ()
373 1.1 christos || (forward_results.back ().get_type ()
374 1.1 christos == token::type::CONDITION));
375 1.1 christos while (!backward_results.empty ())
376 1.1 christos {
377 1.1 christos token &t = backward_results.back ();
378 1.1 christos
379 1.1 christos if (t.get_type () == token::type::FORCE)
380 1.1 christos forward_results.back ().extend (std::move (t));
381 1.1 christos else if (t.get_type () == token::type::THREAD)
382 1.1 christos {
383 1.1 christos const char *end;
384 1.1 christos std::string v (t.get_value ());
385 1.1 christos if (is_thread_id (v.c_str (), &end) && *end == '\0')
386 1.1 christos break;
387 1.1 christos forward_results.back ().extend (std::move (t));
388 1.1 christos }
389 1.1 christos else if (t.get_type () == token::type::INFERIOR
390 1.1 christos || t.get_type () == token::type::TASK)
391 1.1 christos {
392 1.1 christos /* Place the token's value into a null-terminated string, parse
393 1.1 christos the string as a number and check that the entire string was
394 1.1 christos parsed. If this is true then this looks like a valid inferior
395 1.1 christos or task number, otherwise, assume an invalid id, and merge
396 1.1 christos this token with the 'if' token. */
397 1.1 christos char *end;
398 1.1 christos std::string v (t.get_value ());
399 1.1 christos (void) strtol (v.c_str (), &end, 0);
400 1.1 christos if (end > v.c_str () && *end == '\0')
401 1.1 christos break;
402 1.1 christos forward_results.back ().extend (std::move (t));
403 1.1 christos }
404 1.1 christos else
405 1.1 christos gdb_assert_not_reached ("unexpected token type");
406 1.1 christos
407 1.1 christos /* If we found an actual valid token above then we will have broken
408 1.1 christos out of the loop. We only get here if the token was merged with
409 1.1 christos the 'if' condition, in which case we can discard the last token
410 1.1 christos and then check the token before that. */
411 1.1 christos backward_results.pop_back ();
412 1.1 christos }
413 1.1 christos
414 1.1 christos /* If after the above checks we still have some tokens in the
415 1.1 christos BACKWARD_RESULTS vector, then these need to be appended to the
416 1.1 christos FORWARD_RESULTS vector. However, we first reverse the order so that
417 1.1 christos FORWARD_RESULTS retains the tokens in the order they appeared in the
418 1.1 christos input string. */
419 1.1 christos if (!backward_results.empty ())
420 1.1 christos forward_results.insert (forward_results.end (),
421 1.1 christos backward_results.rbegin (),
422 1.1 christos backward_results.rend ());
423 1.1 christos
424 1.1 christos return forward_results;
425 1.1 christos }
426 1.1 christos
427 1.1 christos /* Called when the global debug_breakpoint is true. Prints VEC to the
428 1.1 christos debug output stream. */
429 1.1 christos
430 1.1 christos static void
431 1.1 christos dump_condition_tokens (const std::vector<token> &vec)
432 1.1 christos {
433 1.1 christos gdb_assert (debug_breakpoint);
434 1.1 christos
435 1.1 christos bool first = true;
436 1.1 christos std::string str = "Tokens: ";
437 1.1 christos for (const token &t : vec)
438 1.1 christos {
439 1.1 christos if (!first)
440 1.1 christos str += " ";
441 1.1 christos first = false;
442 1.1 christos str += t.to_string ();
443 1.1 christos }
444 1.1 christos breakpoint_debug_printf ("%s", str.c_str ());
445 1.1 christos }
446 1.1 christos
447 1.1 christos /* See break-cond-parse.h. */
448 1.1 christos
449 1.1 christos void
450 1.1 christos create_breakpoint_parse_arg_string
451 1.1 christos (const char *str, gdb::unique_xmalloc_ptr<char> *cond_string_ptr,
452 1.1 christos int *thread_ptr, int *inferior_ptr, int *task_ptr,
453 1.1 christos gdb::unique_xmalloc_ptr<char> *rest_ptr, bool *force_ptr)
454 1.1 christos {
455 1.1 christos /* Set up the defaults. */
456 1.1 christos cond_string_ptr->reset ();
457 1.1 christos rest_ptr->reset ();
458 1.1 christos *thread_ptr = -1;
459 1.1 christos *inferior_ptr = -1;
460 1.1 christos *task_ptr = -1;
461 1.1 christos *force_ptr = false;
462 1.1 christos
463 1.1 christos if (str == nullptr)
464 1.1 christos return;
465 1.1 christos
466 1.1 christos /* Split STR into a series of tokens. */
467 1.1 christos std::vector<token> tokens = parse_all_tokens (str);
468 1.1 christos if (debug_breakpoint)
469 1.1 christos dump_condition_tokens (tokens);
470 1.1 christos
471 1.1 christos /* Temporary variables. Initialised to the default state, then updated
472 1.1 christos as we parse TOKENS. If all of TOKENS is parsed successfully then the
473 1.1 christos state from these variables is copied into the output arguments before
474 1.1 christos the function returns. */
475 1.1 christos int thread = -1, inferior = -1, task = -1;
476 1.1 christos bool force = false;
477 1.1 christos gdb::unique_xmalloc_ptr<char> cond_string, rest;
478 1.1 christos
479 1.1 christos for (const token &t : tokens)
480 1.1 christos {
481 1.1 christos std::string tok_value (t.get_value ());
482 1.1 christos switch (t.get_type ())
483 1.1 christos {
484 1.1 christos case token::type::FORCE:
485 1.1 christos force = true;
486 1.1 christos break;
487 1.1 christos case token::type::THREAD:
488 1.1 christos {
489 1.1 christos if (thread != -1)
490 1.1 christos error ("You can specify only one thread.");
491 1.1 christos if (task != -1 || inferior != -1)
492 1.1 christos error ("You can specify only one of thread, inferior, or task.");
493 1.1 christos const char *tmptok;
494 1.1 christos thread_info *thr = parse_thread_id (tok_value.c_str (), &tmptok);
495 1.1 christos gdb_assert (*tmptok == '\0');
496 1.1 christos thread = thr->global_num;
497 1.1 christos }
498 1.1 christos break;
499 1.1 christos case token::type::INFERIOR:
500 1.1 christos {
501 1.1 christos if (inferior != -1)
502 1.1 christos error ("You can specify only one inferior.");
503 1.1 christos if (task != -1 || thread != -1)
504 1.1 christos error ("You can specify only one of thread, inferior, or task.");
505 1.1 christos char *tmptok;
506 1.1 christos long inferior_id = strtol (tok_value.c_str (), &tmptok, 0);
507 1.1 christos if (*tmptok != '\0')
508 1.1 christos error (_("Junk '%s' after inferior keyword."), tmptok);
509 1.1 christos if (inferior_id > INT_MAX)
510 1.1 christos error (_("No inferior number '%ld'"), inferior_id);
511 1.1 christos inferior = static_cast<int> (inferior_id);
512 1.1 christos struct inferior *inf = find_inferior_id (inferior);
513 1.1 christos if (inf == nullptr)
514 1.1 christos error (_("No inferior number '%d'"), inferior);
515 1.1 christos }
516 1.1 christos break;
517 1.1 christos case token::type::TASK:
518 1.1 christos {
519 1.1 christos if (task != -1)
520 1.1 christos error ("You can specify only one task.");
521 1.1 christos if (inferior != -1 || thread != -1)
522 1.1 christos error ("You can specify only one of thread, inferior, or task.");
523 1.1 christos char *tmptok;
524 1.1 christos long task_id = strtol (tok_value.c_str (), &tmptok, 0);
525 1.1 christos if (*tmptok != '\0')
526 1.1 christos error (_("Junk '%s' after task keyword."), tmptok);
527 1.1 christos if (task_id > INT_MAX)
528 1.1 christos error (_("Unknown task %ld"), task_id);
529 1.1 christos task = static_cast<int> (task_id);
530 1.1 christos if (!valid_task_id (task))
531 1.1 christos error (_("Unknown task %d."), task);
532 1.1 christos }
533 1.1 christos break;
534 1.1 christos case token::type::CONDITION:
535 1.1 christos cond_string.reset (savestring (t.get_value ().data (),
536 1.1 christos t.get_value ().size ()));
537 1.1 christos break;
538 1.1 christos case token::type::REST:
539 1.1 christos rest.reset (savestring (t.get_value ().data (),
540 1.1 christos t.get_value ().size ()));
541 1.1 christos break;
542 1.1 christos }
543 1.1 christos }
544 1.1 christos
545 1.1 christos /* Move results into the output locations. */
546 1.1 christos *force_ptr = force;
547 1.1 christos *thread_ptr = thread;
548 1.1 christos *inferior_ptr = inferior;
549 1.1 christos *task_ptr = task;
550 1.1 christos rest_ptr->reset (rest.release ());
551 1.1 christos cond_string_ptr->reset (cond_string.release ());
552 1.1 christos }
553 1.1 christos
554 1.1 christos #if GDB_SELF_TEST
555 1.1 christos
556 1.1 christos namespace selftests {
557 1.1 christos
558 1.1 christos /* Run a single test of the create_breakpoint_parse_arg_string function.
559 1.1 christos INPUT is passed to create_breakpoint_parse_arg_string while all other
560 1.1 christos arguments are the expected output from
561 1.1 christos create_breakpoint_parse_arg_string. */
562 1.1 christos
563 1.1 christos static void
564 1.1 christos test (const char *input, const char *condition, int thread = -1,
565 1.1 christos int inferior = -1, int task = -1, bool force = false,
566 1.1 christos const char *rest = nullptr, const char *error_msg = nullptr)
567 1.1 christos {
568 1.1 christos gdb::unique_xmalloc_ptr<char> extracted_condition;
569 1.1 christos gdb::unique_xmalloc_ptr<char> extracted_rest;
570 1.1 christos int extracted_thread, extracted_inferior, extracted_task;
571 1.1 christos bool extracted_force_condition;
572 1.1 christos std::string exception_msg, error_str;
573 1.1 christos
574 1.1 christos if (error_msg != nullptr)
575 1.1 christos error_str = std::string (error_msg) + "\n";
576 1.1 christos
577 1.1 christos try
578 1.1 christos {
579 1.1 christos create_breakpoint_parse_arg_string (input, &extracted_condition,
580 1.1 christos &extracted_thread,
581 1.1 christos &extracted_inferior,
582 1.1 christos &extracted_task, &extracted_rest,
583 1.1 christos &extracted_force_condition);
584 1.1 christos }
585 1.1 christos catch (const gdb_exception_error &ex)
586 1.1 christos {
587 1.1 christos string_file buf;
588 1.1 christos
589 1.1 christos exception_print (&buf, ex);
590 1.1 christos exception_msg = buf.release ();
591 1.1 christos }
592 1.1 christos
593 1.1 christos if ((condition == nullptr) != (extracted_condition.get () == nullptr)
594 1.1 christos || (condition != nullptr
595 1.1 christos && strcmp (condition, extracted_condition.get ()) != 0)
596 1.1 christos || (rest == nullptr) != (extracted_rest.get () == nullptr)
597 1.1 christos || (rest != nullptr && strcmp (rest, extracted_rest.get ()) != 0)
598 1.1 christos || thread != extracted_thread
599 1.1 christos || inferior != extracted_inferior
600 1.1 christos || task != extracted_task
601 1.1 christos || force != extracted_force_condition
602 1.1 christos || exception_msg != error_str)
603 1.1 christos {
604 1.1 christos if (run_verbose ())
605 1.1 christos {
606 1.1 christos debug_printf ("input: '%s'\n", input);
607 1.1 christos debug_printf ("condition: '%s'\n", extracted_condition.get ());
608 1.1 christos debug_printf ("rest: '%s'\n", extracted_rest.get ());
609 1.1 christos debug_printf ("thread: %d\n", extracted_thread);
610 1.1 christos debug_printf ("inferior: %d\n", extracted_inferior);
611 1.1 christos debug_printf ("task: %d\n", extracted_task);
612 1.1 christos debug_printf ("forced: %s\n",
613 1.1 christos extracted_force_condition ? "true" : "false");
614 1.1 christos debug_printf ("exception: '%s'\n", exception_msg.c_str ());
615 1.1 christos }
616 1.1 christos
617 1.1 christos /* Report the failure. */
618 1.1 christos SELF_CHECK (false);
619 1.1 christos }
620 1.1 christos }
621 1.1 christos
622 1.1 christos /* Wrapper for test function. Pass through the default values for all
623 1.1 christos parameters, except the last parameter, which indicates that we expect
624 1.1 christos INPUT to trigger an error. */
625 1.1 christos
626 1.1 christos static void
627 1.1 christos test_error (const char *input, const char *error_msg)
628 1.1 christos {
629 1.1 christos test (input, nullptr, -1, -1, -1, false, nullptr, error_msg);
630 1.1 christos }
631 1.1 christos
632 1.1 christos /* Test the create_breakpoint_parse_arg_string function. Just wraps
633 1.1 christos multiple calls to the test function above. */
634 1.1 christos
635 1.1 christos static void
636 1.1 christos create_breakpoint_parse_arg_string_tests ()
637 1.1 christos {
638 1.1 christos gdbarch *arch = current_inferior ()->arch ();
639 1.1 christos scoped_restore_current_pspace_and_thread restore;
640 1.1 christos scoped_mock_context<test_target_ops> mock_target (arch);
641 1.1 christos
642 1.1 christos int global_thread_num = mock_target.mock_thread.global_num;
643 1.1 christos
644 1.1 christos /* Test parsing valid breakpoint condition strings. */
645 1.1 christos test (" if blah ", "blah");
646 1.1 christos test (" if blah thread 1", "blah", global_thread_num);
647 1.1 christos test (" if blah inferior 1", "blah", -1, 1);
648 1.1 christos test (" if blah thread 1 ", "blah", global_thread_num);
649 1.1 christos test ("thread 1 woof", nullptr, global_thread_num, -1, -1, false, "woof");
650 1.1 christos test ("thread 1 X", nullptr, global_thread_num, -1, -1, false, "X");
651 1.1 christos test (" if blah thread 1 -force-condition", "blah", global_thread_num,
652 1.1 christos -1, -1, true);
653 1.1 christos test (" -force-condition if blah thread 1", "blah", global_thread_num,
654 1.1 christos -1, -1, true);
655 1.1 christos test (" -force-condition if blah thread 1 ", "blah", global_thread_num,
656 1.1 christos -1, -1, true);
657 1.1 christos test ("thread 1 -force-condition if blah", "blah", global_thread_num,
658 1.1 christos -1, -1, true);
659 1.1 christos test ("if (A::outer::func ())", "(A::outer::func ())");
660 1.1 christos test ("if ( foo == thread )", "( foo == thread )");
661 1.1 christos test ("if ( foo == thread ) inferior 1", "( foo == thread )", -1, 1);
662 1.1 christos test ("if ( foo == thread ) thread 1", "( foo == thread )",
663 1.1 christos global_thread_num);
664 1.1 christos test ("if foo == thread", "foo == thread");
665 1.1 christos test ("if foo == thread 1", "foo ==", global_thread_num);
666 1.1 christos
667 1.1 christos /* Test parsing some invalid breakpoint condition strings. */
668 1.1 christos test_error ("thread 1 if foo == 123 thread 1",
669 1.1 christos "You can specify only one thread.");
670 1.1 christos test_error ("thread 1 if foo == 123 inferior 1",
671 1.1 christos "You can specify only one of thread, inferior, or task.");
672 1.1 christos test_error ("thread 1 if foo == 123 task 1",
673 1.1 christos "You can specify only one of thread, inferior, or task.");
674 1.1 christos test_error ("inferior 1 if foo == 123 inferior 1",
675 1.1 christos "You can specify only one inferior.");
676 1.1 christos test_error ("inferior 1 if foo == 123 thread 1",
677 1.1 christos "You can specify only one of thread, inferior, or task.");
678 1.1 christos test_error ("inferior 1 if foo == 123 task 1",
679 1.1 christos "You can specify only one of thread, inferior, or task.");
680 1.1 christos test_error ("thread 1.2.3", "Invalid thread ID: 1.2.3");
681 1.1 christos test_error ("thread 1/2", "Invalid thread ID: 1/2");
682 1.1 christos test_error ("thread 1xxx", "Invalid thread ID: 1xxx");
683 1.1 christos test_error ("inferior 1xxx", "Junk 'xxx' after inferior keyword.");
684 1.1 christos test_error ("task 1xxx", "Junk 'xxx' after task keyword.");
685 1.1 christos }
686 1.1 christos
687 1.1 christos } // namespace selftests
688 1.1 christos #endif /* GDB_SELF_TEST */
689 1.1 christos
690 1.1 christos void _initialize_break_cond_parse ();
691 1.1 christos void
692 1.1 christos _initialize_break_cond_parse ()
693 1.1 christos {
694 1.1 christos #if GDB_SELF_TEST
695 1.1 christos selftests::register_test
696 1.1 christos ("create_breakpoint_parse_arg_string",
697 1.1 christos selftests::create_breakpoint_parse_arg_string_tests);
698 1.1 christos #endif
699 1.1 christos }
700