| /* Copyright (C) 2023 Free Software Foundation, Inc. | |
| This file is part of GDB. | |
| This program is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 3 of the License, or | |
| (at your option) any later version. | |
| This program is distributed in the hope that it will be useful, | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| GNU General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with this program. If not, see <http://www.gnu.org/licenses/>. */ | |
| #include "defs.h" | |
| #include "gdbsupport/gdb_assert.h" | |
| #include "gdbsupport/selftest.h" | |
| #include "test-target.h" | |
| #include "scoped-mock-context.h" | |
| #include "break-cond-parse.h" | |
| #include "tid-parse.h" | |
| #include "ada-lang.h" | |
| #include "exceptions.h" | |
| /* When parsing tokens from a string, which direction are we parsing? | |
| Given the following string and pointer 'ptr': | |
| ABC DEF GHI JKL | |
| ^ | |
| ptr | |
| Parsing 'forward' will return the token 'GHI' and update 'ptr' to point | |
| between GHI and JKL. Parsing 'backward' will return the token 'DEF' and | |
| update 'ptr' to point between ABC and DEF. | |
| */ | |
| enum class parse_direction | |
| { | |
| /* Parse the next token forwards. */ | |
| forward, | |
| /* Parse the previous token backwards. */ | |
| backward | |
| }; | |
| /* Find the next token in DIRECTION from *CURR. */ | |
| static std::string_view | |
| find_next_token (const char **curr, parse_direction direction) | |
| { | |
| const char *tok_start, *tok_end; | |
| gdb_assert (**curr != '\0'); | |
| if (direction == parse_direction::forward) | |
| { | |
| *curr = skip_spaces (*curr); | |
| tok_start = *curr; | |
| *curr = skip_to_space (*curr); | |
| tok_end = *curr - 1; | |
| } | |
| else | |
| { | |
| gdb_assert (direction == parse_direction::backward); | |
| while (isspace (**curr)) | |
| --(*curr); | |
| tok_end = *curr; | |
| while (!isspace (**curr)) | |
| --(*curr); | |
| tok_start = (*curr) + 1; | |
| } | |
| return std::string_view (tok_start, tok_end - tok_start + 1); | |
| } | |
| /* A class that represents a complete parsed token. Each token has a type | |
| and a std::string_view into the original breakpoint condition string. */ | |
| struct token | |
| { | |
| /* The types a token might take. */ | |
| enum class type | |
| { | |
| /* These are the token types for the 'if', 'thread', 'inferior', and | |
| 'task' keywords. The m_content for these token types is the value | |
| passed to the keyword, not the keyword itself. */ | |
| CONDITION, | |
| THREAD, | |
| INFERIOR, | |
| TASK, | |
| /* This is the token used when we find unknown content, the m_content | |
| for this token is the rest of the input string. */ | |
| REST, | |
| /* This is the token for the -force-condition token, the m_content for | |
| this token contains the keyword itself. */ | |
| FORCE | |
| }; | |
| token (enum type type, std::string_view content) | |
| : m_type (type), | |
| m_content (std::move (content)) | |
| { | |
| /* Nothing. */ | |
| } | |
| /* Return a string representing this token. Only used for debug. */ | |
| std::string to_string () const | |
| { | |
| switch (m_type) | |
| { | |
| case type::CONDITION: | |
| return string_printf ("{ CONDITION: \"%s\" }", | |
| std::string (m_content).c_str ()); | |
| case type::THREAD: | |
| return string_printf ("{ THREAD: \"%s\" }", | |
| std::string (m_content).c_str ()); | |
| case type::INFERIOR: | |
| return string_printf ("{ INFERIOR: \"%s\" }", | |
| std::string (m_content).c_str ()); | |
| case type::TASK: | |
| return string_printf ("{ TASK: \"%s\" }", | |
| std::string (m_content).c_str ()); | |
| case type::REST: | |
| return string_printf ("{ REST: \"%s\" }", | |
| std::string (m_content).c_str ()); | |
| case type::FORCE: | |
| return string_printf ("{ FORCE }"); | |
| default: | |
| return "** unknown **"; | |
| } | |
| } | |
| /* The type of this token. */ | |
| const type &get_type () const | |
| { | |
| return m_type; | |
| } | |
| /* Return the value of this token. */ | |
| const std::string_view &get_value () const | |
| { | |
| gdb_assert (m_content.size () > 0); | |
| return m_content; | |
| } | |
| /* Extend this token with the contents of OTHER. This only makes sense | |
| if OTHER is the next token after this one in the original string, | |
| however, enforcing that restriction is left to the caller of this | |
| function. | |
| When OTHER is a keyword/value token, e.g. 'thread 1', the m_content | |
| for OTHER will only point to the '1'. However, as the m_content is a | |
| std::string_view, then when we merge the m_content of OTHER into this | |
| token we automatically merge in the 'thread' part too, as it | |
| naturally sits between this token and OTHER. */ | |
| void | |
| extend (const token &other) | |
| { | |
| m_content = std::string_view (this->m_content.data (), | |
| (other.m_content.data () | |
| - this->m_content.data () | |
| + other.m_content.size ())); | |
| } | |
| private: | |
| /* The type of this token. */ | |
| type m_type; | |
| /* The important content part of this token. The extend member function | |
| depends on this being a std::string_view. */ | |
| std::string_view m_content; | |
| }; | |
| /* Split STR, a breakpoint condition string, into a vector of tokens where | |
| each token represents a component of the condition. Tokens are first | |
| parsed from the front of STR until we encounter an 'if' token. At this | |
| point tokens are parsed from the end of STR until we encounter an | |
| unknown token, which we assume is the other end of the 'if' condition. | |
| If when scanning forward we encounter an unknown token then the | |
| remainder of STR is placed into a 'rest' token (the rest of the | |
| string), and no backward scan is performed. */ | |
| static std::vector<token> | |
| parse_all_tokens (const char *str) | |
| { | |
| gdb_assert (str != nullptr); | |
| std::vector<token> forward_results; | |
| std::vector<token> backward_results; | |
| const char *cond_start = nullptr; | |
| const char *cond_end = nullptr; | |
| parse_direction direction = parse_direction::forward; | |
| std::vector<token> *curr_results = &forward_results; | |
| while (*str != '\0') | |
| { | |
| /* Find the next token. If moving backward and this token starts at | |
| the same location as the condition then we must have found the | |
| other end of the condition string -- we're done. */ | |
| std::string_view t = find_next_token (&str, direction); | |
| if (direction == parse_direction::backward && t.data () <= cond_start) | |
| { | |
| cond_end = &t.back (); | |
| break; | |
| } | |
| /* We only have a single flag option to check for. All the other | |
| options take a value so require an additional token to be found. | |
| Additionally, we require that this flag be at least '-f', we | |
| don't allow it to be abbreviated to '-'. */ | |
| if (t.length () > 1 && startswith ("-force-condition", t)) | |
| { | |
| curr_results->emplace_back (token::type::FORCE, t); | |
| continue; | |
| } | |
| /* Maybe the first token was the last token in the string. If this | |
| is the case then we definitely can't try to extract a value | |
| token. This also means that the token T is meaningless. Reset | |
| TOK to point at the start of the unknown content and break out of | |
| the loop. We'll record the unknown part of the string outside of | |
| the scanning loop (below). */ | |
| if (direction == parse_direction::forward && *str == '\0') | |
| { | |
| str = t.data (); | |
| break; | |
| } | |
| /* As before, find the next token and, if we are scanning backwards, | |
| check that we have not reached the start of the condition string. */ | |
| std::string_view v = find_next_token (&str, direction); | |
| if (direction == parse_direction::backward && v.data () <= cond_start) | |
| { | |
| /* Use token T here as that must also be part of the condition | |
| string. */ | |
| cond_end = &t.back (); | |
| break; | |
| } | |
| /* When moving backward we will first parse the value token then the | |
| keyword token, so swap them now. */ | |
| if (direction == parse_direction::backward) | |
| std::swap (t, v); | |
| /* Check for valid option in token T. If we find a valid option then | |
| parse the value from the token V. Except for 'if', that's handled | |
| differently. | |
| For the 'if' token we need to capture the entire condition | |
| string, so record the start of the condition string and then | |
| start scanning backwards looking for the end of the condition | |
| string. | |
| The order of these checks is important, at least the check for | |
| 'thread' must occur before the check for 'task'. We accept | |
| abbreviations of these token names, and 't' should resolve to | |
| 'thread', which will only happen if we check 'thread' first. */ | |
| if (direction == parse_direction::forward && startswith ("if", t)) | |
| { | |
| cond_start = v.data (); | |
| str = str + strlen (str); | |
| gdb_assert (*str == '\0'); | |
| --str; | |
| direction = parse_direction::backward; | |
| curr_results = &backward_results; | |
| continue; | |
| } | |
| else if (startswith ("thread", t)) | |
| curr_results->emplace_back (token::type::THREAD, v); | |
| else if (startswith ("inferior", t)) | |
| curr_results->emplace_back (token::type::INFERIOR, v); | |
| else if (startswith ("task", t)) | |
| curr_results->emplace_back (token::type::TASK, v); | |
| else | |
| { | |
| /* An unknown token. If we are scanning forward then reset TOK | |
| to point at the start of the unknown content, we record this | |
| outside of the scanning loop (below). | |
| If we are scanning backward then unknown content is assumed to | |
| be the other end of the condition string, obviously, this is | |
| just a heuristic, we could be looking at a mistyped command | |
| line, but this will be spotted when the condition is | |
| eventually evaluated. | |
| Either way, no more scanning is required after this. */ | |
| if (direction == parse_direction::forward) | |
| str = t.data (); | |
| else | |
| { | |
| gdb_assert (direction == parse_direction::backward); | |
| cond_end = &v.back (); | |
| } | |
| break; | |
| } | |
| } | |
| if (cond_start != nullptr) | |
| { | |
| /* If we found the start of a condition string then we should have | |
| switched to backward scan mode, and found the end of the condition | |
| string. Capture the whole condition string into COND_STRING | |
| now. */ | |
| gdb_assert (direction == parse_direction::backward); | |
| gdb_assert (cond_end != nullptr); | |
| std::string_view v (cond_start, cond_end - cond_start + 1); | |
| forward_results.emplace_back (token::type::CONDITION, v); | |
| } | |
| else if (*str != '\0') | |
| { | |
| /* If we didn't have a condition start pointer then we should still | |
| be in forward scanning mode. If we didn't reach the end of the | |
| input string (TOK is not at the null character) then the rest of | |
| the input string is garbage that we didn't understand. | |
| Record the unknown content into REST. The caller of this function | |
| will report this as an error later on. We could report the error | |
| here, but we prefer to allow the caller to run other checks, and | |
| prioritise other errors before reporting this problem. */ | |
| gdb_assert (direction == parse_direction::forward); | |
| gdb_assert (cond_end == nullptr); | |
| std::string_view v (str, strlen (str)); | |
| forward_results.emplace_back (token::type::REST, v); | |
| } | |
| /* If we have tokens in the BACKWARD_RESULTS vector then this means that | |
| we found an 'if' condition (which will be the last thing in the | |
| FORWARD_RESULTS vector), and then we started a backward scan. | |
| The last tokens from the input string (those after the 'if' condition) | |
| will be the first tokens added to the BACKWARD_RESULTS vector, so the | |
| last items in the BACKWARD_RESULTS vector are those next to the 'if' | |
| condition. | |
| Check the tokens in the BACKWARD_RESULTS vector from back to front. | |
| If the tokens look invalid then we assume that they are actually part | |
| of the 'if' condition, and merge the token with the 'if' condition. | |
| If it turns out that this was incorrect and that instead the user just | |
| messed up entering the token value, then this will show as an error | |
| when parsing the 'if' condition. | |
| Doing this allows us to handle things like: | |
| break function if ( variable == thread ) | |
| Where 'thread' is a local variable within 'function'. When parsing | |
| this we will initially see 'thread )' as a thread token with ')' as | |
| the value. However, the following code will spot that ')' is not a | |
| valid thread-id, and so we merge 'thread )' into the 'if' condition | |
| string. | |
| This code also handles the special treatment for '-force-condition', | |
| which exists for backwards compatibility reasons. Traditionally this | |
| flag, if it occurred immediately after the 'if' condition, would be | |
| treated as part of the 'if' condition. When the breakpoint condition | |
| parsing code was rewritten, this behaviour was retained. */ | |
| gdb_assert (backward_results.empty () | |
| || (forward_results.back ().get_type () | |
| == token::type::CONDITION)); | |
| while (!backward_results.empty ()) | |
| { | |
| token &t = backward_results.back (); | |
| if (t.get_type () == token::type::FORCE) | |
| forward_results.back ().extend (std::move (t)); | |
| else if (t.get_type () == token::type::THREAD) | |
| { | |
| const char *end; | |
| std::string v (t.get_value ()); | |
| if (is_thread_id (v.c_str (), &end) && *end == '\0') | |
| break; | |
| forward_results.back ().extend (std::move (t)); | |
| } | |
| else if (t.get_type () == token::type::INFERIOR | |
| || t.get_type () == token::type::TASK) | |
| { | |
| /* Place the token's value into a null-terminated string, parse | |
| the string as a number and check that the entire string was | |
| parsed. If this is true then this looks like a valid inferior | |
| or task number, otherwise, assume an invalid id, and merge | |
| this token with the 'if' token. */ | |
| char *end; | |
| std::string v (t.get_value ()); | |
| (void) strtol (v.c_str (), &end, 0); | |
| if (end > v.c_str () && *end == '\0') | |
| break; | |
| forward_results.back ().extend (std::move (t)); | |
| } | |
| else | |
| gdb_assert_not_reached ("unexpected token type"); | |
| /* If we found an actual valid token above then we will have broken | |
| out of the loop. We only get here if the token was merged with | |
| the 'if' condition, in which case we can discard the last token | |
| and then check the token before that. */ | |
| backward_results.pop_back (); | |
| } | |
| /* If after the above checks we still have some tokens in the | |
| BACKWARD_RESULTS vector, then these need to be appended to the | |
| FORWARD_RESULTS vector. However, we first reverse the order so that | |
| FORWARD_RESULTS retains the tokens in the order they appeared in the | |
| input string. */ | |
| if (!backward_results.empty ()) | |
| forward_results.insert (forward_results.end (), | |
| backward_results.rbegin (), | |
| backward_results.rend ()); | |
| return forward_results; | |
| } | |
| /* Called when the global debug_breakpoint is true. Prints VEC to the | |
| debug output stream. */ | |
| static void | |
| dump_condition_tokens (const std::vector<token> &vec) | |
| { | |
| gdb_assert (debug_breakpoint); | |
| bool first = true; | |
| std::string str = "Tokens: "; | |
| for (const token &t : vec) | |
| { | |
| if (!first) | |
| str += " "; | |
| first = false; | |
| str += t.to_string (); | |
| } | |
| breakpoint_debug_printf ("%s", str.c_str ()); | |
| } | |
| /* See break-cond-parse.h. */ | |
| void | |
| create_breakpoint_parse_arg_string | |
| (const char *str, gdb::unique_xmalloc_ptr<char> *cond_string_ptr, | |
| int *thread_ptr, int *inferior_ptr, int *task_ptr, | |
| gdb::unique_xmalloc_ptr<char> *rest_ptr, bool *force_ptr) | |
| { | |
| /* Set up the defaults. */ | |
| cond_string_ptr->reset (); | |
| rest_ptr->reset (); | |
| *thread_ptr = -1; | |
| *inferior_ptr = -1; | |
| *task_ptr = -1; | |
| *force_ptr = false; | |
| if (str == nullptr) | |
| return; | |
| /* Split STR into a series of tokens. */ | |
| std::vector<token> tokens = parse_all_tokens (str); | |
| if (debug_breakpoint) | |
| dump_condition_tokens (tokens); | |
| /* Temporary variables. Initialised to the default state, then updated | |
| as we parse TOKENS. If all of TOKENS is parsed successfully then the | |
| state from these variables is copied into the output arguments before | |
| the function returns. */ | |
| int thread = -1, inferior = -1, task = -1; | |
| bool force = false; | |
| gdb::unique_xmalloc_ptr<char> cond_string, rest; | |
| for (const token &t : tokens) | |
| { | |
| std::string tok_value (t.get_value ()); | |
| switch (t.get_type ()) | |
| { | |
| case token::type::FORCE: | |
| force = true; | |
| break; | |
| case token::type::THREAD: | |
| { | |
| if (thread != -1) | |
| error ("You can specify only one thread."); | |
| if (task != -1 || inferior != -1) | |
| error ("You can specify only one of thread, inferior, or task."); | |
| const char *tmptok; | |
| thread_info *thr = parse_thread_id (tok_value.c_str (), &tmptok); | |
| gdb_assert (*tmptok == '\0'); | |
| thread = thr->global_num; | |
| } | |
| break; | |
| case token::type::INFERIOR: | |
| { | |
| if (inferior != -1) | |
| error ("You can specify only one inferior."); | |
| if (task != -1 || thread != -1) | |
| error ("You can specify only one of thread, inferior, or task."); | |
| char *tmptok; | |
| long inferior_id = strtol (tok_value.c_str (), &tmptok, 0); | |
| if (*tmptok != '\0') | |
| error (_("Junk '%s' after inferior keyword."), tmptok); | |
| if (inferior_id > INT_MAX) | |
| error (_("No inferior number '%ld'"), inferior_id); | |
| inferior = static_cast<int> (inferior_id); | |
| struct inferior *inf = find_inferior_id (inferior); | |
| if (inf == nullptr) | |
| error (_("No inferior number '%d'"), inferior); | |
| } | |
| break; | |
| case token::type::TASK: | |
| { | |
| if (task != -1) | |
| error ("You can specify only one task."); | |
| if (inferior != -1 || thread != -1) | |
| error ("You can specify only one of thread, inferior, or task."); | |
| char *tmptok; | |
| long task_id = strtol (tok_value.c_str (), &tmptok, 0); | |
| if (*tmptok != '\0') | |
| error (_("Junk '%s' after task keyword."), tmptok); | |
| if (task_id > INT_MAX) | |
| error (_("Unknown task %ld"), task_id); | |
| task = static_cast<int> (task_id); | |
| if (!valid_task_id (task)) | |
| error (_("Unknown task %d."), task); | |
| } | |
| break; | |
| case token::type::CONDITION: | |
| cond_string.reset (savestring (t.get_value ().data (), | |
| t.get_value ().size ())); | |
| break; | |
| case token::type::REST: | |
| rest.reset (savestring (t.get_value ().data (), | |
| t.get_value ().size ())); | |
| break; | |
| } | |
| } | |
| /* Move results into the output locations. */ | |
| *force_ptr = force; | |
| *thread_ptr = thread; | |
| *inferior_ptr = inferior; | |
| *task_ptr = task; | |
| rest_ptr->reset (rest.release ()); | |
| cond_string_ptr->reset (cond_string.release ()); | |
| } | |
| #if GDB_SELF_TEST | |
| namespace selftests { | |
| /* Run a single test of the create_breakpoint_parse_arg_string function. | |
| INPUT is passed to create_breakpoint_parse_arg_string while all other | |
| arguments are the expected output from | |
| create_breakpoint_parse_arg_string. */ | |
| static void | |
| test (const char *input, const char *condition, int thread = -1, | |
| int inferior = -1, int task = -1, bool force = false, | |
| const char *rest = nullptr, const char *error_msg = nullptr) | |
| { | |
| gdb::unique_xmalloc_ptr<char> extracted_condition; | |
| gdb::unique_xmalloc_ptr<char> extracted_rest; | |
| int extracted_thread, extracted_inferior, extracted_task; | |
| bool extracted_force_condition; | |
| std::string exception_msg, error_str; | |
| if (error_msg != nullptr) | |
| error_str = std::string (error_msg) + "\n"; | |
| try | |
| { | |
| create_breakpoint_parse_arg_string (input, &extracted_condition, | |
| &extracted_thread, | |
| &extracted_inferior, | |
| &extracted_task, &extracted_rest, | |
| &extracted_force_condition); | |
| } | |
| catch (const gdb_exception_error &ex) | |
| { | |
| string_file buf; | |
| exception_print (&buf, ex); | |
| exception_msg = buf.release (); | |
| } | |
| if ((condition == nullptr) != (extracted_condition.get () == nullptr) | |
| || (condition != nullptr | |
| && strcmp (condition, extracted_condition.get ()) != 0) | |
| || (rest == nullptr) != (extracted_rest.get () == nullptr) | |
| || (rest != nullptr && strcmp (rest, extracted_rest.get ()) != 0) | |
| || thread != extracted_thread | |
| || inferior != extracted_inferior | |
| || task != extracted_task | |
| || force != extracted_force_condition | |
| || exception_msg != error_str) | |
| { | |
| if (run_verbose ()) | |
| { | |
| debug_printf ("input: '%s'\n", input); | |
| debug_printf ("condition: '%s'\n", extracted_condition.get ()); | |
| debug_printf ("rest: '%s'\n", extracted_rest.get ()); | |
| debug_printf ("thread: %d\n", extracted_thread); | |
| debug_printf ("inferior: %d\n", extracted_inferior); | |
| debug_printf ("task: %d\n", extracted_task); | |
| debug_printf ("forced: %s\n", | |
| extracted_force_condition ? "true" : "false"); | |
| debug_printf ("exception: '%s'\n", exception_msg.c_str ()); | |
| } | |
| /* Report the failure. */ | |
| SELF_CHECK (false); | |
| } | |
| } | |
| /* Wrapper for test function. Pass through the default values for all | |
| parameters, except the last parameter, which indicates that we expect | |
| INPUT to trigger an error. */ | |
| static void | |
| test_error (const char *input, const char *error_msg) | |
| { | |
| test (input, nullptr, -1, -1, -1, false, nullptr, error_msg); | |
| } | |
| /* Test the create_breakpoint_parse_arg_string function. Just wraps | |
| multiple calls to the test function above. */ | |
| static void | |
| create_breakpoint_parse_arg_string_tests () | |
| { | |
| gdbarch *arch = current_inferior ()->arch (); | |
| scoped_restore_current_pspace_and_thread restore; | |
| scoped_mock_context<test_target_ops> mock_target (arch); | |
| int global_thread_num = mock_target.mock_thread.global_num; | |
| /* Test parsing valid breakpoint condition strings. */ | |
| test (" if blah ", "blah"); | |
| test (" if blah thread 1", "blah", global_thread_num); | |
| test (" if blah inferior 1", "blah", -1, 1); | |
| test (" if blah thread 1 ", "blah", global_thread_num); | |
| test ("thread 1 woof", nullptr, global_thread_num, -1, -1, false, "woof"); | |
| test ("thread 1 X", nullptr, global_thread_num, -1, -1, false, "X"); | |
| test (" if blah thread 1 -force-condition", "blah", global_thread_num, | |
| -1, -1, true); | |
| test (" -force-condition if blah thread 1", "blah", global_thread_num, | |
| -1, -1, true); | |
| test (" -force-condition if blah thread 1 ", "blah", global_thread_num, | |
| -1, -1, true); | |
| test ("thread 1 -force-condition if blah", "blah", global_thread_num, | |
| -1, -1, true); | |
| test ("if (A::outer::func ())", "(A::outer::func ())"); | |
| test ("if ( foo == thread )", "( foo == thread )"); | |
| test ("if ( foo == thread ) inferior 1", "( foo == thread )", -1, 1); | |
| test ("if ( foo == thread ) thread 1", "( foo == thread )", | |
| global_thread_num); | |
| test ("if foo == thread", "foo == thread"); | |
| test ("if foo == thread 1", "foo ==", global_thread_num); | |
| /* Test parsing some invalid breakpoint condition strings. */ | |
| test_error ("thread 1 if foo == 123 thread 1", | |
| "You can specify only one thread."); | |
| test_error ("thread 1 if foo == 123 inferior 1", | |
| "You can specify only one of thread, inferior, or task."); | |
| test_error ("thread 1 if foo == 123 task 1", | |
| "You can specify only one of thread, inferior, or task."); | |
| test_error ("inferior 1 if foo == 123 inferior 1", | |
| "You can specify only one inferior."); | |
| test_error ("inferior 1 if foo == 123 thread 1", | |
| "You can specify only one of thread, inferior, or task."); | |
| test_error ("inferior 1 if foo == 123 task 1", | |
| "You can specify only one of thread, inferior, or task."); | |
| test_error ("thread 1.2.3", "Invalid thread ID: 1.2.3"); | |
| test_error ("thread 1/2", "Invalid thread ID: 1/2"); | |
| test_error ("thread 1xxx", "Invalid thread ID: 1xxx"); | |
| test_error ("inferior 1xxx", "Junk 'xxx' after inferior keyword."); | |
| test_error ("task 1xxx", "Junk 'xxx' after task keyword."); | |
| } | |
| } // namespace selftests | |
| #endif /* GDB_SELF_TEST */ | |
| void _initialize_break_cond_parse (); | |
| void | |
| _initialize_break_cond_parse () | |
| { | |
| #if GDB_SELF_TEST | |
| selftests::register_test | |
| ("create_breakpoint_parse_arg_string", | |
| selftests::create_breakpoint_parse_arg_string_tests); | |
| #endif | |
| } |