From patchwork Fri Sep 27 21:25:15 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Tromey X-Patchwork-Id: 34703 Received: (qmail 106958 invoked by alias); 27 Sep 2019 21:25:36 -0000 Mailing-List: contact gdb-patches-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Subscribe: List-Archive: List-Post: List-Help: , Sender: gdb-patches-owner@sourceware.org Delivered-To: mailing list gdb-patches@sourceware.org Received: (qmail 106709 invoked by uid 89); 27 Sep 2019 21:25:35 -0000 Authentication-Results: sourceware.org; auth=none X-Spam-SWARE-Status: No, score=-18.5 required=5.0 tests=AWL, BAYES_00, FORGED_SPF_HELO, GIT_PATCH_0, GIT_PATCH_1, GIT_PATCH_2, GIT_PATCH_3, SPF_HELO_PASS autolearn=ham version=3.3.1 spammy=abuse, ordinary, STYLE X-HELO: gateway24.websitewelcome.com Received: from justpeacetheory.com (HELO gateway24.websitewelcome.com) (192.185.51.122) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Fri, 27 Sep 2019 21:25:25 +0000 Received: from cm14.websitewelcome.com (cm14.websitewelcome.com [100.42.49.7]) by gateway24.websitewelcome.com (Postfix) with ESMTP id 3737D7F397 for ; Fri, 27 Sep 2019 16:25:24 -0500 (CDT) Received: from box5379.bluehost.com ([162.241.216.53]) by cmsmtp with SMTP id Dxjwihcx8BnGaDxjwisxlF; Fri, 27 Sep 2019 16:25:24 -0500 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=tromey.com; s=default; h=References:In-Reply-To:Message-Id:Date:Subject:Cc:To:From: Sender:Reply-To:MIME-Version:Content-Type:Content-Transfer-Encoding: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=cGxw8uLkhpKZNxuOcNC6VAFVfsoJjJ02b1w7KLye86g=; b=xYJLAa0HVuDC7qQwM9P2LIGLo5 a/9DER5xmEIhm94v3WQLTVkz43l4VTOzzSupvNL5p9zjv7MHR/S/7MNoYnDQTKFGYqsv9CJJ4SKMQ EFzHg72xfhVAKLwLmOCWlExLi; Received: from 71-218-73-27.hlrn.qwest.net ([71.218.73.27]:34758 helo=bapiya.Home) by box5379.bluehost.com with esmtpsa (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.92) (envelope-from ) id 1iDxjv-002PvY-Tj; Fri, 27 Sep 2019 16:25:24 -0500 From: Tom Tromey To: gdb-patches@sourceware.org Cc: Pedro Alves Subject: [PATCH 3/8] Introduce gdb-specific %p format suffixes Date: Fri, 27 Sep 2019 15:25:15 -0600 Message-Id: <20190927212520.20073-4-tom@tromey.com> In-Reply-To: <20190927212520.20073-1-tom@tromey.com> References: <20190927212520.20073-1-tom@tromey.com> From: Pedro Alves This introduces a few gdb-specific %p format suffixes. This is useful for emitting gdb-specific output in an ergonomic way. It also yields code that is more i18n-friendly. The comment before ui_out::message explains the details. Note that the tests had to change a little. When using one of the gdb printf functions with styling, there can be spurious style changes emitted to the output. This did not seem worthwhile to fix, as the low-level output functions are rather spaghetti-ish already, and I didn't want to make them even worse. This change also necessitated adding support for "*" as precision and width in format_pieces. These are used in various spots in gdb, and it seemed better to me to implement them than to remove the uses. gdb/ChangeLog 2019-09-27 Pedro Alves Tom Tromey * unittests/format_pieces-selftests.c: Add gdb_format parameter. (test_gdb_formats): New function. (run_tests): Call it. (test_format_specifier): Update. * utils.h (fputs_filtered): Update comment. (vfprintf_styled, vfprintf_styled_no_gdbfmt) (fputs_styled_unfiltered): Declare. * utils.c (fputs_styled_unfiltered): New function. (vfprintf_maybe_filtered): Add gdbfmt parameter. (vfprintf_filtered): Update. (vfprintf_unfiltered, vprintf_filtered): Update. (vfprintf_styled, vfprintf_styled_no_gdbfmt): New functions. * ui-out.h (enum ui_out_flag) : New constants. (enum class field_kind): New. (struct base_field_s, struct signed_field_s): New. (signed_field): New function. (struct string_field_s): New. (string_field): New function. (struct styled_string_s): New. (styled_string): New function. (class ui_out) : Add comment. : New methods. : Add style parameter. * ui-out.c (ui_out::call_do_message, ui_out::vmessage): New methods. (ui_out::message): Rewrite. * mi/mi-out.h (class mi_ui_out) : Add style parameter. * mi/mi-out.c (mi_ui_out::do_message): Add style parameter. * gdbsupport/format.h (class format_pieces) : Add gdb_extensions parameter. (class format_piece): Add parameter to constructor. (n_int_args): New field. * gdbsupport/format.c (format_pieces::format_pieces): Add gdb_extensions parameter. Handle '*'. * cli-out.h (class cli_ui_out) : Add style parameter. * cli-out.c (cli_ui_out::do_message): Add style parameter. Call vfprintf_styled_no_gdbfmt. (cli_ui_out::do_field_string, cli_ui_out::do_spaces) (cli_ui_out::do_text, cli_ui_out::field_separator): Allow unfiltered output. * ui-style.h (struct ui_file_style) : New method. gdb/testsuite/ChangeLog 2019-09-27 Tom Tromey * gdb.base/style.exp: Update tests. --- gdb/ChangeLog | 47 ++++++ gdb/cli-out.c | 31 +++- gdb/cli-out.h | 5 +- gdb/gdbsupport/format.c | 166 +++++++++++++-------- gdb/gdbsupport/format.h | 11 +- gdb/mi/mi-out.c | 3 +- gdb/mi/mi-out.h | 5 +- gdb/testsuite/ChangeLog | 4 + gdb/testsuite/gdb.base/style.exp | 10 +- gdb/ui-out.c | 182 +++++++++++++++++++++++- gdb/ui-out.h | 142 +++++++++++++++++- gdb/ui-style.h | 6 + gdb/unittests/format_pieces-selftests.c | 35 +++-- gdb/utils.c | 81 +++++++++-- gdb/utils.h | 25 +++- 15 files changed, 646 insertions(+), 107 deletions(-) diff --git a/gdb/cli-out.c b/gdb/cli-out.c index fa72a1d344f..c713607e068 100644 --- a/gdb/cli-out.c +++ b/gdb/cli-out.c @@ -170,7 +170,12 @@ cli_ui_out::do_field_string (int fldno, int width, ui_align align, spaces (before); if (string) - fputs_styled (string, style, m_streams.back ()); + { + if (test_flags (unfiltered_output)) + fputs_styled_unfiltered (string, style, m_streams.back ()); + else + fputs_styled (string, style, m_streams.back ()); + } if (after) spaces (after); @@ -201,7 +206,10 @@ cli_ui_out::do_spaces (int numspaces) if (m_suppress_output) return; - print_spaces_filtered (numspaces, m_streams.back ()); + if (test_flags (unfiltered_output)) + print_spaces (numspaces, m_streams.back ()); + else + print_spaces_filtered (numspaces, m_streams.back ()); } void @@ -210,16 +218,24 @@ cli_ui_out::do_text (const char *string) if (m_suppress_output) return; - fputs_filtered (string, m_streams.back ()); + if (test_flags (unfiltered_output)) + fputs_unfiltered (string, m_streams.back ()); + else + fputs_filtered (string, m_streams.back ()); } void -cli_ui_out::do_message (const char *format, va_list args) +cli_ui_out::do_message (const ui_file_style &style, + const char *format, va_list args) { if (m_suppress_output) return; - vfprintf_unfiltered (m_streams.back (), format, args); + /* Use the "no_gdbfmt" variant here to avoid recursion. + vfprintf_styled calls into cli_ui_out::message to handle the + gdb-specific printf formats. */ + vfprintf_styled_no_gdbfmt (m_streams.back (), style, + !test_flags (unfiltered_output), format, args); } void @@ -255,7 +271,10 @@ cli_ui_out::do_redirect (ui_file *outstream) void cli_ui_out::field_separator () { - fputc_filtered (' ', m_streams.back ()); + if (test_flags (unfiltered_output)) + fputc_unfiltered (' ', m_streams.back ()); + else + fputc_filtered (' ', m_streams.back ()); } /* Constructor for cli_ui_out. */ diff --git a/gdb/cli-out.h b/gdb/cli-out.h index bc8b781d605..d7bd23b0ef2 100644 --- a/gdb/cli-out.h +++ b/gdb/cli-out.h @@ -64,8 +64,9 @@ protected: override ATTRIBUTE_PRINTF (6,0); virtual void do_spaces (int numspaces) override; virtual void do_text (const char *string) override; - virtual void do_message (const char *format, va_list args) override - ATTRIBUTE_PRINTF (2,0); + virtual void do_message (const ui_file_style &style, + const char *format, va_list args) override + ATTRIBUTE_PRINTF (3,0); virtual void do_wrap_hint (const char *identstring) override; virtual void do_flush () override; virtual void do_redirect (struct ui_file *outstream) override; diff --git a/gdb/gdbsupport/format.c b/gdb/gdbsupport/format.c index a5a367015f1..1e803501ae6 100644 --- a/gdb/gdbsupport/format.c +++ b/gdb/gdbsupport/format.c @@ -20,10 +20,10 @@ #include "common-defs.h" #include "format.h" -format_pieces::format_pieces (const char **arg) +format_pieces::format_pieces (const char **arg, bool gdb_extensions) { const char *s; - char *f, *string; + const char *string; const char *prev_start; const char *percent_loc; char *sub_start, *current_substring; @@ -31,70 +31,79 @@ format_pieces::format_pieces (const char **arg) s = *arg; - /* Parse the format-control string and copy it into the string STRING, - processing some kinds of escape sequence. */ + if (gdb_extensions) + { + string = *arg; + *arg += strlen (*arg); + } + else + { + /* Parse the format-control string and copy it into the string STRING, + processing some kinds of escape sequence. */ - f = string = (char *) alloca (strlen (s) + 1); + char *f = (char *) alloca (strlen (s) + 1); + string = f; - while (*s != '"' && *s != '\0') - { - int c = *s++; - switch (c) + while ((gdb_extensions || *s != '"') && *s != '\0') { - case '\0': - continue; - - case '\\': - switch (c = *s++) + int c = *s++; + switch (c) { + case '\0': + continue; + case '\\': - *f++ = '\\'; - break; - case 'a': - *f++ = '\a'; - break; - case 'b': - *f++ = '\b'; - break; - case 'e': - *f++ = '\e'; - break; - case 'f': - *f++ = '\f'; - break; - case 'n': - *f++ = '\n'; - break; - case 'r': - *f++ = '\r'; - break; - case 't': - *f++ = '\t'; - break; - case 'v': - *f++ = '\v'; - break; - case '"': - *f++ = '"'; + switch (c = *s++) + { + case '\\': + *f++ = '\\'; + break; + case 'a': + *f++ = '\a'; + break; + case 'b': + *f++ = '\b'; + break; + case 'e': + *f++ = '\e'; + break; + case 'f': + *f++ = '\f'; + break; + case 'n': + *f++ = '\n'; + break; + case 'r': + *f++ = '\r'; + break; + case 't': + *f++ = '\t'; + break; + case 'v': + *f++ = '\v'; + break; + case '"': + *f++ = '"'; + break; + default: + /* ??? TODO: handle other escape sequences. */ + error (_("Unrecognized escape character \\%c in format string."), + c); + } break; + default: - /* ??? TODO: handle other escape sequences. */ - error (_("Unrecognized escape character \\%c in format string."), - c); + *f++ = c; } - break; - - default: - *f++ = c; } - } - /* Terminate our escape-processed copy. */ - *f++ = '\0'; + /* Terminate our escape-processed copy. */ + *f++ = '\0'; - /* Whether the format string ended with double-quote or zero, we're - done with it; it's up to callers to complain about syntax. */ - *arg = s; + /* Whether the format string ended with double-quote or zero, we're + done with it; it's up to callers to complain about syntax. */ + *arg = s; + } /* Need extra space for the '\0's. Doubling the size is sufficient. */ @@ -105,7 +114,7 @@ format_pieces::format_pieces (const char **arg) argclass classifies the %-specs so we can give printf-type functions something of the right size. */ - f = string; + const char *f = string; prev_start = string; while (*f) if (*f++ == '%') @@ -115,6 +124,7 @@ format_pieces::format_pieces (const char **arg) int seen_big_l = 0, seen_h = 0, seen_big_h = 0; int seen_big_d = 0, seen_double_big_d = 0; int bad = 0; + int n_int_args = 0; /* Skip over "%%", it will become part of a literal piece. */ if (*f == '%') @@ -130,7 +140,7 @@ format_pieces::format_pieces (const char **arg) *current_substring++ = '\0'; if (*sub_start != '\0') - m_pieces.emplace_back (sub_start, literal_piece); + m_pieces.emplace_back (sub_start, literal_piece, 0); percent_loc = f - 1; @@ -155,16 +165,32 @@ format_pieces::format_pieces (const char **arg) } /* The next part of a format specifier is a width. */ - while (*f != '\0' && strchr ("0123456789", *f)) - f++; + if (gdb_extensions && *f == '*') + { + ++f; + ++n_int_args; + } + else + { + while (*f != '\0' && strchr ("0123456789", *f)) + f++; + } /* The next part of a format specifier is a precision. */ if (*f == '.') { seen_prec = 1; f++; - while (*f != '\0' && strchr ("0123456789", *f)) - f++; + if (gdb_extensions && *f == '*') + { + ++f; + ++n_int_args; + } + else + { + while (*f != '\0' && strchr ("0123456789", *f)) + f++; + } } /* The next part of a format specifier is a length modifier. */ @@ -252,6 +278,20 @@ format_pieces::format_pieces (const char **arg) bad = 1; if (seen_hash || seen_zero || seen_space || seen_plus) bad = 1; + + if (gdb_extensions) + { + switch (f[1]) + { + case 's': + case 'F': + case '[': + case ']': + f++; + break; + } + } + break; case 's': @@ -336,7 +376,7 @@ format_pieces::format_pieces (const char **arg) prev_start = f; - m_pieces.emplace_back (sub_start, this_argclass); + m_pieces.emplace_back (sub_start, this_argclass, n_int_args); } /* Record the remainder of the string. */ @@ -349,6 +389,6 @@ format_pieces::format_pieces (const char **arg) current_substring += f - prev_start; *current_substring++ = '\0'; - m_pieces.emplace_back (sub_start, literal_piece); + m_pieces.emplace_back (sub_start, literal_piece, 0); } } diff --git a/gdb/gdbsupport/format.h b/gdb/gdbsupport/format.h index 08ef66a7602..e2a47ba5187 100644 --- a/gdb/gdbsupport/format.h +++ b/gdb/gdbsupport/format.h @@ -50,9 +50,10 @@ enum argclass struct format_piece { - format_piece (const char *str, enum argclass argc) + format_piece (const char *str, enum argclass argc, int n) : string (str), - argclass (argc) + argclass (argc), + n_int_args (n) { } @@ -64,13 +65,17 @@ struct format_piece const char *string; enum argclass argclass; + /* Count the number of preceding 'int' arguments that must be passed + along. This is used for a width or precision of '*'. Note that + this feature is only available in "gdb_extensions" mode. */ + int n_int_args; }; class format_pieces { public: - format_pieces (const char **arg); + format_pieces (const char **arg, bool gdb_extensions = false); ~format_pieces () = default; DISABLE_COPY_AND_ASSIGN (format_pieces); diff --git a/gdb/mi/mi-out.c b/gdb/mi/mi-out.c index 0b930738f1d..71af4865e97 100644 --- a/gdb/mi/mi-out.c +++ b/gdb/mi/mi-out.c @@ -166,7 +166,8 @@ mi_ui_out::do_text (const char *string) } void -mi_ui_out::do_message (const char *format, va_list args) +mi_ui_out::do_message (const ui_file_style &style, + const char *format, va_list args) { } diff --git a/gdb/mi/mi-out.h b/gdb/mi/mi-out.h index 90528fd4e84..9393809b5f2 100644 --- a/gdb/mi/mi-out.h +++ b/gdb/mi/mi-out.h @@ -72,8 +72,9 @@ protected: override ATTRIBUTE_PRINTF (6,0); virtual void do_spaces (int numspaces) override; virtual void do_text (const char *string) override; - virtual void do_message (const char *format, va_list args) override - ATTRIBUTE_PRINTF (2,0); + virtual void do_message (const ui_file_style &style, + const char *format, va_list args) override + ATTRIBUTE_PRINTF (3,0); virtual void do_wrap_hint (const char *identstring) override; virtual void do_flush () override; virtual void do_redirect (struct ui_file *outstream) override; diff --git a/gdb/testsuite/gdb.base/style.exp b/gdb/testsuite/gdb.base/style.exp index 41c43dc8f7c..d2c3105bb9f 100644 --- a/gdb/testsuite/gdb.base/style.exp +++ b/gdb/testsuite/gdb.base/style.exp @@ -94,13 +94,13 @@ save_vars { env(TERM) } { gdb_test "" "${vers}.*" \ "version is styled" - set address_style_expr [style "\"address\" style" address] + set address_style_expr [style ".*\".*address.*\".*style.*" address] gdb_test "show style address foreground" \ "The ${address_style_expr} foreground color is: blue" \ "style name and style word styled using its own style in show style" - set aliases_expr [style "aliases" title] - set breakpoints_expr [style "breakpoints" title] + set aliases_expr [style ".*aliases.*" title] + set breakpoints_expr [style ".*breakpoints.*" title] gdb_test "help" \ [multi_line \ "List of classes of commands:" \ @@ -111,8 +111,8 @@ save_vars { env(TERM) } { ] \ "help classes of commands styled with title" - set taas_expr [style "taas" title] - set tfaas_expr [style "tfaas" title] + set taas_expr [style ".*taas.*" title] + set tfaas_expr [style ".*tfaas.*" title] set cut_for_thre_expr [style "cut for 'thre" highlight] gdb_test "apropos -v cut for 'thre" \ [multi_line \ diff --git a/gdb/ui-out.c b/gdb/ui-out.c index e8fe44c8268..8cbaa4e0bc1 100644 --- a/gdb/ui-out.c +++ b/gdb/ui-out.c @@ -563,12 +563,190 @@ ui_out::text (const char *string) } void -ui_out::message (const char *format, ...) +ui_out::call_do_message (const ui_file_style &style, const char *format, + ...) { va_list args; va_start (args, format); - do_message (format, args); + do_message (style, format, args); + va_end (args); +} + +void +ui_out::vmessage (const ui_file_style &in_style, const char *format, + va_list args) +{ + format_pieces fpieces (&format, true); + + ui_file_style style = in_style; + + for (auto &&piece : fpieces) + { + const char *current_substring = piece.string; + + gdb_assert (piece.n_int_args >= 0 && piece.n_int_args <= 2); + int intvals[2] = { 0, 0 }; + for (int i = 0; i < piece.n_int_args; ++i) + intvals[i] = va_arg (args, int); + + /* The only ones we support for now. */ + gdb_assert (piece.n_int_args == 0 + || piece.argclass == string_arg + || piece.argclass == int_arg + || piece.argclass == long_arg); + + switch (piece.argclass) + { + case string_arg: + { + const char *str = va_arg (args, const char *); + switch (piece.n_int_args) + { + case 0: + call_do_message (style, current_substring, str); + break; + case 1: + call_do_message (style, current_substring, intvals[0], str); + break; + case 2: + call_do_message (style, current_substring, + intvals[0], intvals[1], str); + break; + } + } + break; + case wide_string_arg: + gdb_assert_not_reached (_("wide_string_arg not supported in vmessage")); + break; + case wide_char_arg: + gdb_assert_not_reached (_("wide_char_arg not supported in vmessage")); + break; + case long_long_arg: + call_do_message (style, current_substring, va_arg (args, long long)); + break; + case int_arg: + { + int val = va_arg (args, int); + switch (piece.n_int_args) + { + case 0: + call_do_message (style, current_substring, val); + break; + case 1: + call_do_message (style, current_substring, intvals[0], val); + break; + case 2: + call_do_message (style, current_substring, + intvals[0], intvals[1], val); + break; + } + } + break; + case long_arg: + { + long val = va_arg (args, long); + switch (piece.n_int_args) + { + case 0: + call_do_message (style, current_substring, val); + break; + case 1: + call_do_message (style, current_substring, intvals[0], val); + break; + case 2: + call_do_message (style, current_substring, + intvals[0], intvals[1], val); + break; + } + } + break; + case double_arg: + call_do_message (style, current_substring, va_arg (args, double)); + break; + case long_double_arg: + gdb_assert_not_reached (_("long_double_arg not supported in vmessage")); + break; + case dec32float_arg: + gdb_assert_not_reached (_("dec32float_arg not supported in vmessage")); + break; + case dec64float_arg: + gdb_assert_not_reached (_("dec64float_arg not supported in vmessage")); + break; + case dec128float_arg: + gdb_assert_not_reached (_("dec128float_arg not supported in vmessage")); + break; + case ptr_arg: + switch (current_substring[2]) + { + case 'F': + { + gdb_assert (!test_flags (disallow_ui_out_field)); + base_field_s *bf = va_arg (args, base_field_s *); + switch (bf->kind) + { + case field_kind::SIGNED: + { + auto *f = (signed_field_s *) bf; + field_signed (f->name, f->val); + } + break; + case field_kind::STRING: + { + auto *f = (string_field_s *) bf; + field_string (f->name, f->str); + } + break; + } + } + break; + case 's': + { + styled_string_s *ss = va_arg (args, styled_string_s *); + call_do_message (ss->style, "%s", ss->str); + } + break; + case '[': + style = *va_arg (args, const ui_file_style *); + break; + case ']': + { + void *arg = va_arg (args, void *); + gdb_assert (arg == nullptr); + + style = {}; + } + break; + default: + call_do_message (style, current_substring, va_arg (args, void *)); + break; + } + break; + case literal_piece: + /* Print a portion of the format string that has no + directives. Note that this will not include any ordinary + %-specs, but it might include "%%". That is why we use + call_do_message here. Also, we pass a dummy argument + because some platforms have modified GCC to include + -Wformat-security by default, which will warn here if + there is no argument. */ + call_do_message (style, current_substring, 0); + break; + default: + internal_error (__FILE__, __LINE__, + _("failed internal consistency check")); + } + } +} + +void +ui_out::message (const char *format, ...) +{ + va_list args; + va_start (args, format); + + vmessage (ui_file_style (), format, args); + va_end (args); } diff --git a/gdb/ui-out.h b/gdb/ui-out.h index 6732f046719..d8adf7b7f0d 100644 --- a/gdb/ui-out.h +++ b/gdb/ui-out.h @@ -53,6 +53,12 @@ enum ui_out_flag { ui_source_list = (1 << 0), fix_multi_location_breakpoint_output = (1 << 1), + /* For CLI output, this flag is set if unfiltered output is desired. + This should only be used by low-level formatting functions. */ + unfiltered_output = (1 << 2), + /* This indicates that %pF should be disallowed in a printf format + string. */ + disallow_ui_out_field = (1 << 3) }; DEF_ENUM_FLAGS_TYPE (ui_out_flag, ui_out_flags); @@ -68,6 +74,87 @@ enum ui_out_type ui_out_type_list }; +/* The possible kinds of fields. */ +enum class field_kind + { + SIGNED, + STRING, + }; + +/* An int field, to be passed to %pF in format strings. */ + +struct base_field_s +{ + const char *name; + field_kind kind; +}; + +/* An int field, to be passed to %pF in format strings. */ + +struct signed_field_s : base_field_s +{ + LONGEST val; +}; + +/* Construct a temporary signed_field_s on the caller's stack and + return a pointer to the constructed object. We use this because + it's not possible to pass a reference via va_args. */ + +static inline signed_field_s * +signed_field (const char *name, LONGEST val, + signed_field_s &&tmp = {}) +{ + tmp.name = name; + tmp.kind = field_kind::SIGNED; + tmp.val = val; + return &tmp; +} + +/* A string field, to be passed to %pF in format strings. */ + +struct string_field_s : base_field_s +{ + const char *str; +}; + +/* Construct a temporary string_field_s on the caller's stack and + return a pointer to the constructed object. We use this because + it's not possible to pass a reference via va_args. */ + +static inline string_field_s * +string_field (const char *name, const char *str, + string_field_s &&tmp = {}) +{ + tmp.name = name; + tmp.kind = field_kind::STRING; + tmp.str = str; + return &tmp; +} + +/* A styled string. */ + +struct styled_string_s +{ + /* The style. */ + ui_file_style style; + + /* The string. */ + const char *str; +}; + +/* Construct a temporary styled_string_s on the caller's stack and + return a pointer to the constructed object. We use this because + it's not possible to pass a reference via va_args. */ + +static inline styled_string_s * +styled_string (const ui_file_style &style, const char *str, + styled_string_s &&tmp = {}) +{ + tmp.style = style; + tmp.str = str; + return &tmp; +} + class ui_out { public: @@ -110,7 +197,55 @@ class ui_out void spaces (int numspaces); void text (const char *string); + + /* Output a printf-style formatted string. In addition to the usual + printf format specs, this supports a few GDB-specific + formatters: + + - '%pF' - output a field. + + The argument is a field, wrapped in any of the base_field_s + subclasses. signed_field for integer fields, styled_field for + string fields. This is preferred over separate + uiout->field_int(), uiout_>field_string() etc. calls when the + formatted message is translatable. E.g.: + + uiout->message (_("\nWatchpoint %pF deleted because the program has " + "left the block in\n" + "which its expression is valid.\n"), + signed_field ("wpnum", b->number)); + + - '%p[' - output the following text in a specified style. + '%p]' - output the following text in the default style. + + The argument to '%p[' is a ui_file_style pointer. The argument + to '%p]' must be nullptr. + + This is useful when you want to output some portion of a string + literal in some style. E.g.: + + uiout->message (_(" %p[%p]"), + metadata_style.style ().ptr (), + reps, repeats, nullptr); + + - '%ps' - output a styled string. + + The argument is the result of a call to styled_string. This is + useful when you want to output some runtime-generated string in + some style. E.g.: + + uiout->message (_("this is a target address %ps.\n"), + styled_string (address_style.style (), + paddress (gdbarch, pc))); + + Note that these all "abuse" the %p printf format spec, in order + to be compatible with GCC's printf format checking. This is OK + because code in GDB that wants to print a host address should use + host_address_to_string instead of %p. */ void message (const char *format, ...) ATTRIBUTE_PRINTF (2, 3); + void vmessage (const ui_file_style &in_style, + const char *format, va_list args) ATTRIBUTE_PRINTF (3, 0); + void wrap_hint (const char *identstring); void flush (); @@ -161,8 +296,9 @@ class ui_out ATTRIBUTE_PRINTF (6,0) = 0; virtual void do_spaces (int numspaces) = 0; virtual void do_text (const char *string) = 0; - virtual void do_message (const char *format, va_list args) - ATTRIBUTE_PRINTF (2,0) = 0; + virtual void do_message (const ui_file_style &style, + const char *format, va_list args) + ATTRIBUTE_PRINTF (3,0) = 0; virtual void do_wrap_hint (const char *identstring) = 0; virtual void do_flush () = 0; virtual void do_redirect (struct ui_file *outstream) = 0; @@ -174,6 +310,8 @@ class ui_out { return false; } private: + void call_do_message (const ui_file_style &style, const char *format, + ...); ui_out_flags m_flags; diff --git a/gdb/ui-style.h b/gdb/ui-style.h index 24b4b59ed9f..d2e9c136263 100644 --- a/gdb/ui-style.h +++ b/gdb/ui-style.h @@ -223,6 +223,12 @@ struct ui_file_style BUF. */ bool parse (const char *buf, size_t *n_read); + /* We need this because we can't pass a reference via va_args. */ + const ui_file_style *ptr () const + { + return this; + } + private: color m_foreground = NONE; diff --git a/gdb/unittests/format_pieces-selftests.c b/gdb/unittests/format_pieces-selftests.c index 862b2da0f48..ed83d9670fd 100644 --- a/gdb/unittests/format_pieces-selftests.c +++ b/gdb/unittests/format_pieces-selftests.c @@ -27,9 +27,10 @@ namespace format_pieces { /* Verify that parsing STR gives pieces equal to EXPECTED_PIECES. */ static void -check (const char *str, const std::vector &expected_pieces) +check (const char *str, const std::vector &expected_pieces, + bool gdb_format = false) { - ::format_pieces pieces (&str); + ::format_pieces pieces (&str, gdb_format); SELF_CHECK ((pieces.end () - pieces.begin ()) == expected_pieces.size ()); SELF_CHECK (std::equal (pieces.begin (), pieces.end (), @@ -41,7 +42,7 @@ test_escape_sequences () { check ("This is an escape sequence: \\e", { - format_piece ("This is an escape sequence: \e", literal_piece), + format_piece ("This is an escape sequence: \e", literal_piece, 0), }); } @@ -50,21 +51,37 @@ test_format_specifier () { /* The format string here ends with a % sequence, to ensure we don't see a trailing empty literal piece. */ - check ("Hello %d%llx%%d%d", /* ARI: %ll */ + check ("Hello\\t %d%llx%%d%d", /* ARI: %ll */ { - format_piece ("Hello ", literal_piece), - format_piece ("%d", int_arg), - format_piece ("%llx", long_long_arg), /* ARI: %ll */ - format_piece ("%%d", literal_piece), - format_piece ("%d", int_arg), + format_piece ("Hello\t ", literal_piece, 0), + format_piece ("%d", int_arg, 0), + format_piece ("%llx", long_long_arg, 0), /* ARI: %ll */ + format_piece ("%%d", literal_piece, 0), + format_piece ("%d", int_arg, 0), }); } +static void +test_gdb_formats () +{ + check ("Hello\\t \"%p[%pF%ps%*.*d%p]\"", + { + format_piece ("Hello\\t \"", literal_piece, 0), + format_piece ("%p[", ptr_arg, 0), + format_piece ("%pF", ptr_arg, 0), + format_piece ("%ps", ptr_arg, 0), + format_piece ("%*.*d", int_arg, 2), + format_piece ("%p]", ptr_arg, 0), + format_piece ("\"", literal_piece, 0), + }, true); +} + static void run_tests () { test_escape_sequences (); test_format_specifier (); + test_gdb_formats (); } } /* namespace format_pieces */ diff --git a/gdb/utils.c b/gdb/utils.c index b7d380073ff..e685cc20847 100644 --- a/gdb/utils.c +++ b/gdb/utils.c @@ -73,13 +73,15 @@ #include "cli/cli-style.h" #include "gdbsupport/scope-exit.h" #include "gdbarch.h" +#include "cli-out.h" void (*deprecated_error_begin_hook) (void); /* Prototypes for local functions */ static void vfprintf_maybe_filtered (struct ui_file *, const char *, - va_list, int) ATTRIBUTE_PRINTF (2, 0); + va_list, bool, bool) + ATTRIBUTE_PRINTF (2, 0); static void fputs_maybe_filtered (const char *, struct ui_file *, int); @@ -1854,6 +1856,24 @@ fputs_styled (const char *linebuffer, const ui_file_style &style, /* See utils.h. */ +void +fputs_styled_unfiltered (const char *linebuffer, const ui_file_style &style, + struct ui_file *stream) +{ + /* This just makes it so we emit somewhat fewer escape + sequences. */ + if (style.is_default ()) + fputs_maybe_filtered (linebuffer, stream, 0); + else + { + set_output_style (stream, style); + fputs_maybe_filtered (linebuffer, stream, 0); + set_output_style (stream, ui_file_style ()); + } +} + +/* See utils.h. */ + void fputs_highlighted (const char *str, const compiled_regex &highlight, struct ui_file *stream) @@ -2021,34 +2041,46 @@ puts_debug (char *prefix, char *string, char *suffix) We implement three variants, vfprintf (takes a vararg list and stream), fprintf (takes a stream to write on), and printf (the usual). - Note also that a longjmp to top level may occur in this routine - (since prompt_for_continue may do so) so this routine should not be - called when cleanups are not in place. */ + Note also that this may throw a quit (since prompt_for_continue may + do so). */ static void vfprintf_maybe_filtered (struct ui_file *stream, const char *format, - va_list args, int filter) + va_list args, bool filter, bool gdbfmt) { - std::string linebuffer = string_vprintf (format, args); - fputs_maybe_filtered (linebuffer.c_str (), stream, filter); + if (gdbfmt) + { + ui_out_flags flags = disallow_ui_out_field; + if (!filter) + flags |= unfiltered_output; + cli_ui_out (stream, flags).vmessage (applied_style, format, args); + } + else + { + std::string str = string_vprintf (format, args); + fputs_maybe_filtered (str.c_str (), stream, filter); + } } void vfprintf_filtered (struct ui_file *stream, const char *format, va_list args) { - vfprintf_maybe_filtered (stream, format, args, 1); + vfprintf_maybe_filtered (stream, format, args, true, true); } void vfprintf_unfiltered (struct ui_file *stream, const char *format, va_list args) { - std::string linebuffer = string_vprintf (format, args); if (debug_timestamp && stream == gdb_stdlog) { using namespace std::chrono; int len, need_nl; + string_file sfile; + cli_ui_out (&sfile, 0).vmessage (ui_file_style (), format, args); + std::string linebuffer = std::move (sfile.string ()); + steady_clock::time_point now = steady_clock::now (); seconds s = duration_cast (now.time_since_epoch ()); microseconds us = duration_cast (now.time_since_epoch () - s); @@ -2064,13 +2096,13 @@ vfprintf_unfiltered (struct ui_file *stream, const char *format, va_list args) fputs_unfiltered (timestamp.c_str (), stream); } else - fputs_unfiltered (linebuffer.c_str (), stream); + vfprintf_maybe_filtered (stream, format, args, false, true); } void vprintf_filtered (const char *format, va_list args) { - vfprintf_maybe_filtered (gdb_stdout, format, args, 1); + vfprintf_maybe_filtered (gdb_stdout, format, args, true, false); } void @@ -2130,6 +2162,33 @@ fprintf_styled (struct ui_file *stream, const ui_file_style &style, set_output_style (stream, ui_file_style ()); } +/* See utils.h. */ + +void +vfprintf_styled (struct ui_file *stream, const ui_file_style &style, + const char *format, va_list args) +{ + set_output_style (stream, style); + vfprintf_filtered (stream, format, args); + set_output_style (stream, ui_file_style ()); +} + +/* See utils.h. */ + +void +vfprintf_styled_no_gdbfmt (struct ui_file *stream, const ui_file_style &style, + bool filter, const char *format, va_list args) +{ + std::string str = string_vprintf (format, args); + if (!str.empty ()) + { + if (!style.is_default ()) + set_output_style (stream, style); + fputs_maybe_filtered (str.c_str (), stream, filter); + if (!style.is_default ()) + set_output_style (stream, ui_file_style ()); + } +} void printf_filtered (const char *format, ...) diff --git a/gdb/utils.h b/gdb/utils.h index 7df86beec47..76f0da69f71 100644 --- a/gdb/utils.h +++ b/gdb/utils.h @@ -350,7 +350,10 @@ extern struct ui_file *gdb_stdtargin; extern void set_screen_width_and_height (int width, int height); /* More generic printf like operations. Filtered versions may return - non-locally on error. */ + non-locally on error. As an extension over plain printf, these + support some GDB-specific format specifiers. Particularly useful + here are the styling formatters: '%p[', '%p]' and '%ps'. See + ui_out::message for details. */ extern void fputs_filtered (const char *, struct ui_file *); @@ -430,6 +433,20 @@ extern void fprintf_styled (struct ui_file *stream, ...) ATTRIBUTE_PRINTF (3, 4); +extern void vfprintf_styled (struct ui_file *stream, + const ui_file_style &style, + const char *fmt, + va_list args) + ATTRIBUTE_PRINTF (3, 0); + +/* Like vfprintf_styled, but do not process gdb-specific format + specifiers. */ +extern void vfprintf_styled_no_gdbfmt (struct ui_file *stream, + const ui_file_style &style, + bool filter, + const char *fmt, va_list args) + ATTRIBUTE_PRINTF (4, 0); + /* Like fputs_filtered, but styles the output according to STYLE, when appropriate. */ @@ -437,6 +454,12 @@ extern void fputs_styled (const char *linebuffer, const ui_file_style &style, struct ui_file *stream); +/* Unfiltered variant of fputs_styled. */ + +extern void fputs_styled_unfiltered (const char *linebuffer, + const ui_file_style &style, + struct ui_file *stream); + /* Like fputs_styled, but uses highlight_style to highlight the parts of STR that match HIGHLIGHT. */