gdb: fix printf of wchar_t early in a gdb session

Given this test program:

  #include <wchar.h>

  const wchar_t wide_str[] = L"wide string";

  int
  main (void)
  {
    return 0;
  }

I observed this GDB behaviour:

  $ gdb -q /tmp/printf-wchar_t
  Reading symbols from /tmp/printf-wchar_t...
  (gdb) start
  Temporary breakpoint 1 at 0x40110a: file /tmp/printf-wchar_t.c, line 8.
  Starting program: /tmp/printf-wchar_t

  Temporary breakpoint 1, main () at /tmp/printf-wchar_t.c:8
  25	  return 0;
  (gdb) printf "%ls\n", wide_str

  (gdb)

Notice that the printf results in a blank line rather than the
expected 'wide string' output.

I tracked the problem down to printf_wide_c_string (in printcmd.c), in
this function we do this:

  struct type *wctype = lookup_typename (current_language,
					 "wchar_t", NULL, 0);
  int wcwidth = wctype->length ();

the problem here is that 'wchar_t' is a typedef.  If we look at the
comment on type::length() we see this:

  /* Note that if thistype is a TYPEDEF type, you have to call check_typedef.
     But check_typedef does set the TYPE_LENGTH of the TYPEDEF type,
     so you only have to call check_typedef once.  Since value::allocate
     calls check_typedef, X->type ()->length () is safe.  */

What this means is that after calling lookup_typename we should call
check_typedef in order to ensure that the length of the typedef has
been setup correctly.  We are not doing this in printf_wide_c_string,
and so wcwidth is incorrectly calculated as 0.  This is what leads GDB
to print an empty string.

We can see in c_string_operation::evaluate (in c-lang.c) an example of
calling check_typedef specifically to fix this exact issue.

Initially I did fix this problem by adding a check_typedef call into
printf_wide_c_string, but then I figured why not move the
check_typedef call up into lookup_typename itself, that feels like it
should be harmless when looking up a non-typedef type, but will avoid
bugs like this when looking up a typedef.  So that's what I did.

I can then remove the extra check_typedef call from c-lang.c, I don't
see any other places where we had extra check_typedef calls.  This
doesn't mean we definitely had bugs -- so long as we never checked the
length, or, if we knew that check_typedef had already been called,
then we would be fine.

I don't see any test regressions after this change, and my new test
case is now passing.

Reviewed-By: Tom Tromey <tom@tromey.com>
This commit is contained in:
Andrew Burgess
2023-05-31 16:14:47 +01:00
parent eae2847fbf
commit bde240e7f8
5 changed files with 71 additions and 9 deletions

View File

@@ -615,9 +615,6 @@ c_string_operation::evaluate (struct type *expect_type,
internal_error (_("unhandled c_string_type")); internal_error (_("unhandled c_string_type"));
} }
/* Ensure TYPE_LENGTH is valid for TYPE. */
check_typedef (type);
/* If the caller expects an array of some integral type, /* If the caller expects an array of some integral type,
satisfy them. If something odder is expected, rely on the satisfy them. If something odder is expected, rely on the
caller to cast. */ caller to cast. */

View File

@@ -1648,9 +1648,7 @@ type_name_or_error (struct type *type)
objfile ? objfile_name (objfile) : "<arch>"); objfile ? objfile_name (objfile) : "<arch>");
} }
/* Lookup a typedef or primitive type named NAME, visible in lexical /* See gdbtypes.h. */
block BLOCK. If NOERR is nonzero, return zero if NAME is not
suitably defined. */
struct type * struct type *
lookup_typename (const struct language_defn *language, lookup_typename (const struct language_defn *language,
@@ -1662,7 +1660,12 @@ lookup_typename (const struct language_defn *language,
sym = lookup_symbol_in_language (name, block, VAR_DOMAIN, sym = lookup_symbol_in_language (name, block, VAR_DOMAIN,
language->la_language, NULL).symbol; language->la_language, NULL).symbol;
if (sym != NULL && sym->aclass () == LOC_TYPEDEF) if (sym != NULL && sym->aclass () == LOC_TYPEDEF)
return sym->type (); {
struct type *type = sym->type ();
/* Ensure the length of TYPE is valid. */
check_typedef (type);
return type;
}
if (noerr) if (noerr)
return NULL; return NULL;

View File

@@ -2586,8 +2586,18 @@ extern void check_stub_method_group (struct type *, int);
extern char *gdb_mangle_name (struct type *, int, int); extern char *gdb_mangle_name (struct type *, int, int);
extern struct type *lookup_typename (const struct language_defn *, /* Lookup a typedef or primitive type named NAME, visible in lexical block
const char *, const struct block *, int); BLOCK. If NOERR is nonzero, return zero if NAME is not suitably
defined.
If this function finds a suitable type then check_typedef is called on
the type, this ensures that if the type being returned is a typedef
then the length of the type will be correct. The original typedef will
still be returned, not the result of calling check_typedef. */
extern struct type *lookup_typename (const struct language_defn *language,
const char *name,
const struct block *block, int noerr);
extern struct type *lookup_template_type (const char *, struct type *, extern struct type *lookup_template_type (const char *, struct type *,
const struct block *); const struct block *);

View File

@@ -0,0 +1,26 @@
/* This testcase is part of GDB, the GNU debugger.
Copyright 2023 Free Software Foundation, Inc.
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 <wchar.h>
const wchar_t wide_str[] = L"wide string";
int
main (void)
{
return 0;
}

View File

@@ -0,0 +1,26 @@
# Copyright 2023 Free Software Foundation, Inc.
# 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/>.
standard_testfile
if {[prepare_for_testing "failed to prepare" $testfile $srcfile]} {
return -1
}
if {![runto_main]} {
return -1
}
gdb_test {printf "%ls\n", wide_str} "^wide string"