Files
binutils-gdb/gdb/tid-parse.c
Tom Tromey 3719472095 Use gnulib c-ctype module in gdb
PR ada/33217 points out that gdb incorrectly calls the <ctype.h>
functions.  In particular, gdb feels free to pass a 'char' like:

    char *str = ...;
    ... isdigit (*str)

This is incorrect as isdigit only accepts EOF and values that can be
represented as 'unsigned char' -- that is, a cast is needed here to
avoid undefined behavior when 'char' is signed and a character in the
string might be sign-extended.  (As an aside, I think this API seems
obviously bad, but unfortunately this is what the standard says, and
some systems check this.)

Rather than adding casts everywhere, this changes all the code in gdb
that uses any <ctype.h> API to instead call the corresponding c-ctype
function.

Now, c-ctype has some limitations compared to <ctype.h>.  It works as
if the C locale is in effect, so in theory some non-ASCII characters
may be misclassified.  This would only affect a subset of character
sets, though, and in most places I think ASCII is sufficient -- for
example the many places in gdb that check for whitespace.
Furthermore, in practice most users are using UTF-8-based locales,
where these functions aren't really informative for non-ASCII
characters anyway; see the existing workarounds in gdb/c-support.h.

Note that safe-ctype.h cannot be used because it causes conflicts with
readline.h.  And, we canot poison the <ctype.h> identifiers as this
provokes errors from some libstdc++ headers.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33217
Approved-By: Simon Marchi <simon.marchi@efficios.com>
2025-09-09 11:59:04 -06:00

380 lines
8.7 KiB
C

/* TID parsing for GDB, the GNU debugger.
Copyright (C) 2015-2025 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 "tid-parse.h"
#include "inferior.h"
#include "gdbthread.h"
/* See tid-parse.h. */
[[noreturn]] void
invalid_thread_id_error (const char *string)
{
error (_("Invalid thread ID: %s"), string);
}
/* Wrapper for get_number_trailer that throws an error if we get back
a negative number. We'll see a negative value if the number is
stored in a negative convenience variable (e.g., $minus_one = -1).
STRING is the parser string to be used in the error message if we
do get back a negative number. */
static int
get_positive_number_trailer (const char **pp, int trailer, const char *string)
{
int num;
num = get_number_trailer (pp, trailer);
if (num < 0)
error (_("negative value: %s"), string);
return num;
}
/* Parse TIDSTR as a per-inferior thread ID, in either INF_NUM.THR_NUM
or THR_NUM form, and return a pair, the first item of the pair is
INF_NUM and the second item is THR_NUM.
If TIDSTR does not include an INF_NUM component, then the first item in
the pair will be 0 (which is an invalid inferior number), this indicates
that TIDSTR references the current inferior.
This function does not validate the INF_NUM and THR_NUM are actually
valid numbers, that is, they might reference inferiors or threads that
don't actually exist; this function just splits the string into its
component parts.
If there is an error parsing TIDSTR then this function will raise an
exception. */
static std::pair<int, int>
parse_thread_id_1 (const char *tidstr, const char **end)
{
const char *number = tidstr;
const char *dot, *p1;
int thr_num, inf_num;
dot = strchr (number, '.');
if (dot != NULL)
{
/* Parse number to the left of the dot. */
p1 = number;
inf_num = get_positive_number_trailer (&p1, '.', number);
if (inf_num == 0)
invalid_thread_id_error (number);
p1 = dot + 1;
}
else
{
inf_num = 0;
p1 = number;
}
thr_num = get_positive_number_trailer (&p1, 0, number);
if (thr_num == 0)
invalid_thread_id_error (number);
if (end != nullptr)
*end = p1;
return { inf_num, thr_num };
}
/* See tid-parse.h. */
struct thread_info *
parse_thread_id (const char *tidstr, const char **end)
{
const auto [inf_num, thr_num] = parse_thread_id_1 (tidstr, end);
inferior *inf;
bool explicit_inf_id = false;
if (inf_num != 0)
{
inf = find_inferior_id (inf_num);
if (inf == nullptr)
error (_("No inferior number '%d'"), inf_num);
explicit_inf_id = true;
}
else
inf = current_inferior ();
thread_info *tp = nullptr;
for (thread_info *it : inf->threads ())
if (it->per_inf_num == thr_num)
{
tp = it;
break;
}
if (tp == nullptr)
{
if (show_inferior_qualified_tids () || explicit_inf_id)
error (_("Unknown thread %d.%d."), inf->num, thr_num);
else
error (_("Unknown thread %d."), thr_num);
}
return tp;
}
/* See tid-parse.h. */
bool
is_thread_id (const char *tidstr, const char **end)
{
try
{
(void) parse_thread_id_1 (tidstr, end);
return true;
}
catch (const gdb_exception_error &)
{
return false;
}
}
/* See tid-parse.h. */
tid_range_parser::tid_range_parser (const char *tidlist,
int default_inferior)
{
init (tidlist, default_inferior);
}
/* See tid-parse.h. */
void
tid_range_parser::init (const char *tidlist, int default_inferior)
{
m_state = STATE_INFERIOR;
m_cur_tok = tidlist;
m_inf_num = 0;
m_qualified = false;
m_default_inferior = default_inferior;
}
/* See tid-parse.h. */
bool
tid_range_parser::finished () const
{
switch (m_state)
{
case STATE_INFERIOR:
/* Parsing is finished when at end of string or null string,
or we are not in a range and not in front of an integer, negative
integer, convenience var or negative convenience var. */
return (*m_cur_tok == '\0'
|| !(c_isdigit (*m_cur_tok)
|| *m_cur_tok == '$'
|| *m_cur_tok == '*'));
case STATE_THREAD_RANGE:
case STATE_STAR_RANGE:
return m_range_parser.finished ();
}
gdb_assert_not_reached ("unhandled state");
}
/* See tid-parse.h. */
const char *
tid_range_parser::cur_tok () const
{
switch (m_state)
{
case STATE_INFERIOR:
return m_cur_tok;
case STATE_THREAD_RANGE:
case STATE_STAR_RANGE:
return m_range_parser.cur_tok ();
}
gdb_assert_not_reached ("unhandled state");
}
void
tid_range_parser::skip_range ()
{
gdb_assert (m_state == STATE_THREAD_RANGE
|| m_state == STATE_STAR_RANGE);
m_range_parser.skip_range ();
init (m_range_parser.cur_tok (), m_default_inferior);
}
/* See tid-parse.h. */
bool
tid_range_parser::tid_is_qualified () const
{
return m_qualified;
}
/* Helper for tid_range_parser::get_tid and
tid_range_parser::get_tid_range. Return the next range if THR_END
is non-NULL, return a single thread ID otherwise. */
bool
tid_range_parser::get_tid_or_range (int *inf_num,
int *thr_start, int *thr_end)
{
if (m_state == STATE_INFERIOR)
{
const char *p;
const char *space;
space = skip_to_space (m_cur_tok);
p = m_cur_tok;
while (p < space && *p != '.')
p++;
if (p < space)
{
const char *dot = p;
/* Parse number to the left of the dot. */
p = m_cur_tok;
m_inf_num = get_positive_number_trailer (&p, '.', m_cur_tok);
if (m_inf_num == 0)
return 0;
m_qualified = true;
p = dot + 1;
if (c_isspace (*p))
return false;
}
else
{
m_inf_num = m_default_inferior;
m_qualified = false;
p = m_cur_tok;
}
m_range_parser.init (p);
if (p[0] == '*' && (p[1] == '\0' || c_isspace (p[1])))
{
/* Setup the number range parser to return numbers in the
whole [1,INT_MAX] range. */
m_range_parser.setup_range (1, INT_MAX, skip_spaces (p + 1));
m_state = STATE_STAR_RANGE;
}
else
m_state = STATE_THREAD_RANGE;
}
*inf_num = m_inf_num;
*thr_start = m_range_parser.get_number ();
if (*thr_start < 0)
error (_("negative value: %s"), m_cur_tok);
if (*thr_start == 0)
{
m_state = STATE_INFERIOR;
return false;
}
/* If we successfully parsed a thread number or finished parsing a
thread range, switch back to assuming the next TID is
inferior-qualified. */
if (!m_range_parser.in_range ())
{
m_state = STATE_INFERIOR;
m_cur_tok = m_range_parser.cur_tok ();
if (thr_end != NULL)
*thr_end = *thr_start;
}
/* If we're midway through a range, and the caller wants the end
value, return it and skip to the end of the range. */
if (thr_end != NULL
&& (m_state == STATE_THREAD_RANGE
|| m_state == STATE_STAR_RANGE))
{
*thr_end = m_range_parser.end_value ();
skip_range ();
}
return (*inf_num != 0 && *thr_start != 0);
}
/* See tid-parse.h. */
bool
tid_range_parser::get_tid_range (int *inf_num,
int *thr_start, int *thr_end)
{
gdb_assert (inf_num != NULL && thr_start != NULL && thr_end != NULL);
return get_tid_or_range (inf_num, thr_start, thr_end);
}
/* See tid-parse.h. */
bool
tid_range_parser::get_tid (int *inf_num, int *thr_num)
{
gdb_assert (inf_num != NULL && thr_num != NULL);
return get_tid_or_range (inf_num, thr_num, NULL);
}
/* See tid-parse.h. */
bool
tid_range_parser::in_star_range () const
{
return m_state == STATE_STAR_RANGE;
}
bool
tid_range_parser::in_thread_range () const
{
return m_state == STATE_THREAD_RANGE;
}
/* See tid-parse.h. */
int
tid_is_in_list (const char *list, int default_inferior,
int inf_num, int thr_num)
{
if (list == NULL || *list == '\0')
return 1;
tid_range_parser parser (list, default_inferior);
if (parser.finished ())
invalid_thread_id_error (parser.cur_tok ());
while (!parser.finished ())
{
int tmp_inf, tmp_thr_start, tmp_thr_end;
if (!parser.get_tid_range (&tmp_inf, &tmp_thr_start, &tmp_thr_end))
invalid_thread_id_error (parser.cur_tok ());
if (tmp_inf == inf_num
&& tmp_thr_start <= thr_num && thr_num <= tmp_thr_end)
return 1;
}
return 0;
}