This patch extends libgdiagnostics to provide a way to capture the pp tokens making up a message string, so that SARIF and HTML sinks can retain information such as event IDs and URLs. As well as richer output, this improves the round-tripping of such information through sarif-replay.
This also allows diagnostic messages to be built up in pieces, with a drop-in replacement for fprintf, which I've found useful when attempting to port "ld" to use libgdiagnostics. Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu. Pushed to trunk as r16-2274-g2a521eee58da7c. gcc/ChangeLog: PR sarif-replay/120792 * auto-obstack.h: New file, based on material taken from pretty-print.cc. * diagnostic-digraphs.h (diagnostics::digraphs::digraph::set_description): New. (diagnostics::digraphs::node::set_label): New. * doc/libgdiagnostics/topics/compatibility.rst: Add LIBGDIAGNOSTICS_ABI_4. * doc/libgdiagnostics/topics/diagnostics.rst (diagnostic_finish_via_msg_buf): Document new entrypoint. * doc/libgdiagnostics/topics/execution-paths.rst (diagnostic_execution_path_add_event_via_msg_buf): Document new entrypoint. * doc/libgdiagnostics/topics/index.rst: Add message-buffers.rst. * doc/libgdiagnostics/topics/message-buffers.rst: New file. * doc/libgdiagnostics/topics/message-formatting.rst: Add note about message buffers. * doc/libgdiagnostics/topics/physical-locations.rst (diagnostic_add_location_with_label_via_msg_buf): Add. * doc/libgdiagnostics/tutorial/07-execution-paths.rst: Link to next section. * doc/libgdiagnostics/tutorial/08-message-buffers.rst: New file. * doc/libgdiagnostics/tutorial/index.rst: Add 08-message-buffers.rst. * libgdiagnostics++.h (libgdiagnostics::message_buffer): New class. (libgdiagnostics::execution_path::add_event_via_msg_buf): New. (libgdiagnostics::diagnostic::add_location_with_label): New. (libgdiagnostics::diagnostic::finish_via_msg_buf): New. (libgdiagnostics::graph::set_description): New overload. (libgdiagnostics::graph::add_edge): New overload. (libgdiagnostics::node::set_label): New overload. * libgdiagnostics-private.h (private_diagnostic_execution_path_add_event_2): Drop decl. (private_diagnostic_execution_path_add_event_3): New decl. * libgdiagnostics.cc: Include "pretty-print-format-impl.h", "pretty-print-markup.h", and "auto-obstack.h". (class copying_token_printer): New. (struct diagnostic_message_buffer): New. (class pp_element_message_buffer): New. (libgdiagnostics_path_event::libgdiagnostics_path_event): Replace params "gmsgid" and "args" with "msg_buf". (libgdiagnostics_path_event::print_desc): Reimplement using pp_element_message_buffer to replay m_msg_buf into "pp". (libgdiagnostics_path_event::m_desc_uncolored): Drop field. (libgdiagnostics_path_event::m_desc_colored): Drop field. (libgdiagnostics_path_event::msg_buf): New field. (diagnostic_execution_path::add_event_va): Reimplement. (diagnostic_execution_path::add_event_via_msg_buf): New. (diagnostic::add_location_with_label): New overload, using msg_buf. (diagnostic_manager::emit): Reimplement with... (diagnostic_manager::emit_va): ...this. (diagnostic_manager::emit_msg_buf): New. (FAIL_IF_NULL): Rename "p" to "ptr_arg". (diagnostic_finish_va): Update to use diagnostic_manager::emit_va. (diagnostic_graph::add_node_with_id): Rename "id" to "node_id". (diagnostic_graph_add_node): Likewise. (diagnostic_graph_add_edge): Rename "id" to "edge_id". (diagnostic_graph_get_node_by_id): Rename "id" to "node_id". (diagnostic_graph_get_edge_by_id): Rename "id" to "edge_id". (private_diagnostic_execution_path_add_event_2): Delete. (diagnostic_message_buffer_new): New public entrypoint. (diagnostic_message_buffer_release): Likewise. (diagnostic_message_buffer_append_str): Likewise. (diagnostic_message_buffer_append_text): Likewise. (diagnostic_message_buffer_append_byte): Likewise. (diagnostic_message_buffer_append_printf): Likewise. (diagnostic_message_buffer_append_event_id): Likewise. (diagnostic_message_buffer_begin_url): Likewise. (diagnostic_message_buffer_end_url): Likewise. (diagnostic_message_buffer_begin_quote): Likewise. (diagnostic_message_buffer_end_quote): Likewise. (diagnostic_message_buffer_begin_color): Likewise. (diagnostic_message_buffer_end_color): Likewise. (diagnostic_message_buffer_dump): Likewise. (diagnostic_finish_via_msg_buf): Likewise. (diagnostic_add_location_with_label_via_msg_buf): Likewise. (diagnostic_execution_path_add_event_via_msg_buf): Likewise. (diagnostic_graph_set_description_via_msg_buf): Likewise. (diagnostic_graph_add_edge_via_msg_buf): Likewise. (diagnostic_node_set_label_via_msg_buf): Likewise. (private_diagnostic_execution_path_add_event_3): New private entrypoint. * libgdiagnostics.h (LIBGDIAGNOSTICS_PARAM_FORMAT_STRING): New macro. (LIBGDIAGNOSTICS_PARAM_PRINTF_FORMAT_STRING): New macro. (diagnostic_message_buffer): New typedef. (LIBDIAGNOSTICS_HAVE_diagnostic_message_buffer): New define. (diagnostic_message_buffer_new): New decl. (diagnostic_message_buffer_release): New decl. (diagnostic_message_buffer_append_str): New decl. (diagnostic_message_buffer_append_text): New decl. (diagnostic_message_buffer_append_byte): New decl. (diagnostic_message_buffer_append_printf): New decl. (diagnostic_message_buffer_append_event_id): New decl. (diagnostic_message_buffer_begin_url): New decl. (diagnostic_message_buffer_end_url): New decl. (diagnostic_message_buffer_begin_quote): New decl. (diagnostic_message_buffer_end_quote): New decl. (diagnostic_message_buffer_begin_color): New decl. (diagnostic_message_buffer_end_color): New decl. (diagnostic_message_buffer_dump): New decl. (diagnostic_finish_via_msg_buf (diagnostic_add_location_with_label_via_msg_buf): New decl. (diagnostic_execution_path_add_event_via_msg_buf): New decl. (diagnostic_graph_set_description_via_msg_buf): New decl. (diagnostic_graph_add_edge_via_msg_buf): New decl. (diagnostic_node_set_label_via_msg_buf): New decl. * libgdiagnostics.map (LIBGDIAGNOSTICS_ABI_3): Drop private_diagnostic_execution_path_add_event_2. (LIBGDIAGNOSTICS_ABI_4): New. * libsarifreplay.cc (class annotation): Use libgdiagnostics::message_buffer rather than label_text. (add_any_annotations): Likewise. (sarif_replayer::handle_result_obj): Likewise. (make_plain_text_within_result_message): Likewise. (handle_thread_flow_location_object): Likewise. (handle_location_object): Likewise. (sarif_replayer::handle_graph_object): Likewise. (sarif_replayer::handle_node_object): Likewise. (sarif_replayer::handle_edge_object): Likewise. * pretty-print-format-impl.h (pp_token_list::push_back_byte): New decl. * pretty-print-markup.h (pp_markup::context::begin_url): New decl. (pp_markup::context::end_url): New decl. (pp_markup::context::add_event_id): New decl. * pretty-print.cc: Include "auto-obstack.h". (pp_token_list::push_back_byte): New. (struct auto_obstack): Move to auto-obstack.h. (default_token_printer): Make non-static. (pp_markup::context::begin_url): New. (pp_markup::context::end_url): New. (pp_markup::context::add_event_id): New. gcc/testsuite/ChangeLog: PR sarif-replay/120792 * libgdiagnostics.dg/sarif.py: Delete duplicate script. * libgdiagnostics.dg/test-message-buffer-c.py: New test script. * libgdiagnostics.dg/test-message-buffer.c: New test. * libgdiagnostics.dg/test-warning-with-path-c.py: Update expected output to reflect that SARIF for event messages now contains JSON pointers when referring to other events by ID. * sarif-replay.dg/2.1.0-valid/3.11.6-embedded-links.sarif: Add HTML and SARIF output, and call out to Python scripts to verify the output. Add example of a result with a link in its message. * sarif-replay.dg/2.1.0-valid/embedded-links-check-html.py: New test script. * sarif-replay.dg/2.1.0-valid/embedded-links-check-sarif-roundtrip.py: New test script. Signed-off-by: David Malcolm <dmalc...@redhat.com> --- gcc/auto-obstack.h | 58 ++ gcc/diagnostic-digraphs.h | 10 + .../libgdiagnostics/topics/compatibility.rst | 43 ++ .../libgdiagnostics/topics/diagnostics.rst | 18 + .../topics/execution-paths.rst | 22 + gcc/doc/libgdiagnostics/topics/index.rst | 1 + .../topics/message-buffers.rst | 310 ++++++++ .../topics/message-formatting.rst | 5 + .../topics/physical-locations.rst | 20 + .../tutorial/07-execution-paths.rst | 8 +- .../tutorial/08-message-buffers.rst | 75 ++ gcc/doc/libgdiagnostics/tutorial/index.rst | 1 + gcc/libgdiagnostics++.h | 143 ++++ gcc/libgdiagnostics-private.h | 29 +- gcc/libgdiagnostics.cc | 699 ++++++++++++++++-- gcc/libgdiagnostics.h | 217 ++++++ gcc/libgdiagnostics.map | 30 +- gcc/libsarifreplay.cc | 116 ++- gcc/pretty-print-format-impl.h | 1 + gcc/pretty-print-markup.h | 5 + gcc/pretty-print.cc | 69 +- gcc/testsuite/libgdiagnostics.dg/sarif.py | 23 - .../test-message-buffer-c.py | 12 + .../libgdiagnostics.dg/test-message-buffer.c | 80 ++ .../test-warning-with-path-c.py | 2 +- .../2.1.0-valid/3.11.6-embedded-links.sarif | 19 +- .../2.1.0-valid/embedded-links-check-html.py | 28 + .../embedded-links-check-sarif-roundtrip.py | 13 + 28 files changed, 1852 insertions(+), 205 deletions(-) create mode 100644 gcc/auto-obstack.h create mode 100644 gcc/doc/libgdiagnostics/topics/message-buffers.rst create mode 100644 gcc/doc/libgdiagnostics/tutorial/08-message-buffers.rst delete mode 100644 gcc/testsuite/libgdiagnostics.dg/sarif.py create mode 100644 gcc/testsuite/libgdiagnostics.dg/test-message-buffer-c.py create mode 100644 gcc/testsuite/libgdiagnostics.dg/test-message-buffer.c create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/embedded-links-check-html.py create mode 100644 gcc/testsuite/sarif-replay.dg/2.1.0-valid/embedded-links-check-sarif-roundtrip.py diff --git a/gcc/auto-obstack.h b/gcc/auto-obstack.h new file mode 100644 index 000000000000..fe16dbd1068a --- /dev/null +++ b/gcc/auto-obstack.h @@ -0,0 +1,58 @@ +/* RAII wrapper around obstack. + Copyright (C) 2024-2025 Free Software Foundation, Inc. + Contributed by David Malcolm <dmalc...@redhat.com>. + +This file is part of GCC. + +GCC 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, or (at your option) +any later version. + +GCC 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 GCC; see the file COPYING3. If not see +<http://www.gnu.org/licenses/>. */ + +#ifndef GCC_AUTO_OBSTACK_H +#define GCC_AUTO_OBSTACK_H + +/* RAII wrapper around obstack. */ + +struct auto_obstack +{ + auto_obstack () + { + obstack_init (&m_obstack); + } + + ~auto_obstack () + { + obstack_free (&m_obstack, NULL); + } + + operator obstack & () { return m_obstack; } + + void grow (const void *src, size_t length) + { + obstack_grow (&m_obstack, src, length); + } + + void *object_base () const + { + return m_obstack.object_base; + } + + size_t object_size () const + { + return obstack_object_size (&m_obstack); + } + + obstack m_obstack; +}; + +#endif /* GCC_AUTO_OBSTACK_H */ diff --git a/gcc/diagnostic-digraphs.h b/gcc/diagnostic-digraphs.h index 94cb76edd403..d6f8e7e11ac4 100644 --- a/gcc/diagnostic-digraphs.h +++ b/gcc/diagnostic-digraphs.h @@ -109,6 +109,11 @@ class digraph : public object else m_description = nullptr; } + void + set_description (std::string desc) + { + m_description = std::make_unique<std::string> (std::move (desc)); + } node * get_node_by_id (const char *id) const @@ -240,6 +245,11 @@ class node : public object else m_label = nullptr; } + void + set_label (std::string label) + { + m_label = std::make_unique<std::string> (std::move (label)); + } size_t get_num_children () const { return m_children.size (); } diff --git a/gcc/doc/libgdiagnostics/topics/compatibility.rst b/gcc/doc/libgdiagnostics/topics/compatibility.rst index 0e078d0120e5..0ca41a330095 100644 --- a/gcc/doc/libgdiagnostics/topics/compatibility.rst +++ b/gcc/doc/libgdiagnostics/topics/compatibility.rst @@ -193,6 +193,7 @@ supporting command-line options and SARIF playback: ``LIBGDIAGNOSTICS_ABI_3`` ------------------------- + ``LIBGDIAGNOSTICS_ABI_3`` covers the addition of these functions for working with directed graphs: @@ -219,3 +220,45 @@ working with directed graphs: * :func:`diagnostic_node_set_location` * :func:`diagnostic_node_set_logical_location` + +.. _LIBGDIAGNOSTICS_ABI_4: + +``LIBGDIAGNOSTICS_ABI_4`` +------------------------- + +``LIBGDIAGNOSTICS_ABI_4`` covers the addition of these functions for +working with :type:`diagnostic_message_buffer`. + + * :func:`diagnostic_message_buffer_new` + + * :func:`diagnostic_message_buffer_release` + + * :func:`diagnostic_message_buffer_append_str` + + * :func:`diagnostic_message_buffer_append_text` + + * :func:`diagnostic_message_buffer_append_byte` + + * :func:`diagnostic_message_buffer_append_printf` + + * :func:`diagnostic_message_buffer_append_event_id` + + * :func:`diagnostic_message_buffer_begin_url` + + * :func:`diagnostic_message_buffer_end_url` + + * :func:`diagnostic_message_buffer_begin_quote` + + * :func:`diagnostic_message_buffer_end_quote` + + * :func:`diagnostic_message_buffer_begin_color` + + * :func:`diagnostic_message_buffer_end_color` + + * :func:`diagnostic_message_buffer_dump` + + * :func:`diagnostic_finish_via_msg_buf` + + * :func:`diagnostic_add_location_with_label_via_msg_buf` + + * :func:`diagnostic_execution_path_add_event_via_msg_buf` diff --git a/gcc/doc/libgdiagnostics/topics/diagnostics.rst b/gcc/doc/libgdiagnostics/topics/diagnostics.rst index 3d24da0164c4..7454c6e01748 100644 --- a/gcc/doc/libgdiagnostics/topics/diagnostics.rst +++ b/gcc/doc/libgdiagnostics/topics/diagnostics.rst @@ -105,6 +105,24 @@ Diagnostics are All three parameters must be non-NULL. +.. function:: void diagnostic_finish_via_msg_buf (diagnostic *diag, \ + diagnostic_message_buffer *msg_buf) + + This is equivalent to :func:`diagnostic_finish`, but using a message + buffer rather than a format string and variadic arguments. + + ``diag`` and ``msg_buf`` must both be non-NULL. + + Calling this function transfers ownership of ``msg_buf`` to the + diagnostic - do not call :func:`diagnostic_message_buffer_release` on + it. + + This function was added in :ref:`LIBGDIAGNOSTICS_ABI_3`; you can + test for its presence using + + .. code-block:: c + + #ifdef LIBDIAGNOSTICS_HAVE_diagnostic_message_buffer Diagnostic groups ***************** diff --git a/gcc/doc/libgdiagnostics/topics/execution-paths.rst b/gcc/doc/libgdiagnostics/topics/execution-paths.rst index 321503f2f6af..8381c452a0f3 100644 --- a/gcc/doc/libgdiagnostics/topics/execution-paths.rst +++ b/gcc/doc/libgdiagnostics/topics/execution-paths.rst @@ -88,6 +88,28 @@ cross-references between events. In particular FIXME Equivalent to :func:`diagnostic_execution_path_add_event`, but using a :type:`va_list` rather than directly taking variadic arguments. +.. function:: diagnostic_event_id diagnostic_execution_path_add_event_via_msg_buf (diagnostic_execution_path *path, \ + const diagnostic_physical_location *physical_loc, \ + const diagnostic_logical_location *logical_loc, \ + unsigned stack_depth, + diagnostic_message_buffer *msg_buf) + + This is equivalent to :func:`diagnostic_execution_path_add_event` but + using a message buffer rather than a format string and variadic + arguments. + + ``path`` and ``msg_buf`` must both be non-NULL. + + Calling this function transfers ownership of ``msg_buf`` to the + path - do not call :func:`diagnostic_message_buffer_release` on it. + + This function was added in :ref:`LIBGDIAGNOSTICS_ABI_3`; you can + test for its presence using + + .. code-block:: c + + #ifdef LIBDIAGNOSTICS_HAVE_diagnostic_message_buffer + Paths are printed to text sinks, and for SARIF sinks each path is added as a ``codeFlow`` object (see SARIF 2.1.0 `§3.36 codeFlow object <https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html#_Toc141790990>`_). diff --git a/gcc/doc/libgdiagnostics/topics/index.rst b/gcc/doc/libgdiagnostics/topics/index.rst index 966f5ef4e350..437ee058a170 100644 --- a/gcc/doc/libgdiagnostics/topics/index.rst +++ b/gcc/doc/libgdiagnostics/topics/index.rst @@ -28,6 +28,7 @@ Topic reference diagnostic-manager.rst diagnostics.rst message-formatting.rst + message-buffers.rst physical-locations.rst logical-locations.rst metadata.rst diff --git a/gcc/doc/libgdiagnostics/topics/message-buffers.rst b/gcc/doc/libgdiagnostics/topics/message-buffers.rst new file mode 100644 index 000000000000..c6f5851e16e9 --- /dev/null +++ b/gcc/doc/libgdiagnostics/topics/message-buffers.rst @@ -0,0 +1,310 @@ +.. Copyright (C) 2025 Free Software Foundation, Inc. + Originally contributed by David Malcolm <dmalc...@redhat.com> + + This 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 + <https://www.gnu.org/licenses/>. + +.. default-domain:: c + +Message buffers +=============== + +.. type:: diagnostic_message_buffer + +A :type:`diagnostic_message_buffer` is a buffer into which text can be +accumulated, before being used: + +* as the message of a diagnostic, using :func:`diagnostic_finish_via_msg_buf` + +* as the text of a label for a :type:`diagnostic_physical_location` using + :func:`diagnostic_add_location_with_label_via_msg_buf` + +* as the text of an event within a :type:`diagnostic_execution_path` using + :func:`diagnostic_execution_path_add_event_via_msg_buf` + +This is to allow more flexible creation of messages than a "format string +plus variadic arguments" API. + +.. function:: diagnostic_message_buffer * diagnostic_message_buffer_new (void) + + This function creates a new :type:`diagnostic_message_buffer`. + + The caller is responsible for cleaning it up, either by handing it off + to one of the API entrypoints that takes ownership of it (such as + :func:`diagnostic_finish_via_msg_buf`), or by calling + :func:`diagnostic_message_buffer_release` on it. + + This function was added in :ref:`LIBGDIAGNOSTICS_ABI_4`; you can + test for its presence using + + .. code-block:: c + + #ifdef LIBDIAGNOSTICS_HAVE_diagnostic_message_buffer + +.. function:: void diagnostic_message_buffer_release (diagnostic_message_buffer *msg_buf) + + This function releases ``msg_buf``. + + Typically you don't need to call this, but instead will pass the + buffer to one of the API entrypoints that takes over ownership of + it (such as :func:`diagnostic_finish_via_msg_buf`); calling it + after this would lead to a double-free bug, as you no longer "own" + the buffer. + + ``msg_buf`` must be non-NULL. + + This function was added in :ref:`LIBGDIAGNOSTICS_ABI_4`; you can + test for its presence using + + .. code-block:: c + + #ifdef LIBDIAGNOSTICS_HAVE_diagnostic_message_buffer + +.. function:: void diagnostic_message_buffer_append_str (diagnostic_message_buffer *msg_buf, \ + const char *p) + + This function appends the null-terminated string ``p`` to the buffer. + The string is assumed to be UTF-8 encoded. + + ``msg_buf`` and ``p`` must both be non-NULL. + + This function was added in :ref:`LIBGDIAGNOSTICS_ABI_4`; you can + test for its presence using + + .. code-block:: c + + #ifdef LIBDIAGNOSTICS_HAVE_diagnostic_message_buffer + +.. function:: void diagnostic_message_buffer_append_text (diagnostic_message_buffer *msg_buf, \ + const char *p, \ + size_t len) + + This function appends ``len`` bytes from ``p`` to the buffer. + The bytes are assumed to be UTF-8 encoded. + + ``msg_buf`` and ``p`` must both be non-NULL. + + This function was added in :ref:`LIBGDIAGNOSTICS_ABI_4`; you can + test for its presence using + + .. code-block:: c + + #ifdef LIBDIAGNOSTICS_HAVE_diagnostic_message_buffer + +.. function:: void diagnostic_message_buffer_append_byte (diagnostic_message_buffer *msg_buf,\ + char ch) + + This function appends ``ch`` to the buffer. This should be either + ASCII, or part of UTF-8 encoded text. + + ``msg_buf`` must be non-NULL. + + This function was added in :ref:`LIBGDIAGNOSTICS_ABI_4`; you can + test for its presence using + + .. code-block:: c + + #ifdef LIBDIAGNOSTICS_HAVE_diagnostic_message_buffer + +.. function:: void diagnostic_message_buffer_append_printf (diagnostic_message_buffer *msg_buf, \ + const char *fmt, ...) + + This function appends a formatted string to the buffer, using the + formatting rules for ``printf``. + + The string is assumed to be UTF-8 encoded. + + ``msg_buf`` and ``fmt`` must both be non-NULL. + + This function was added in :ref:`LIBGDIAGNOSTICS_ABI_4`; you can + test for its presence using + + .. code-block:: c + + #ifdef LIBDIAGNOSTICS_HAVE_diagnostic_message_buffer + +.. function:: void diagnostic_message_buffer_append_event_id (diagnostic_message_buffer *msg_buf, \ + diagnostic_event_id event_id) + + This function appends a :type:`diagnostic_event_id` to the buffer. + + ``msg_buf`` must be non-NULL. + + For text output, the event will be printed in the form ``(1)``. + + This is analogous to the + :doc:`%@ message formatting code <message-formatting>`. + + This function was added in :ref:`LIBGDIAGNOSTICS_ABI_4`; you can + test for its presence using + + .. code-block:: c + + #ifdef LIBDIAGNOSTICS_HAVE_diagnostic_message_buffer + +Hyperlink support +***************** + +.. function:: void diagnostic_message_buffer_begin_url (diagnostic_message_buffer *msg_buf, \ + const char *url) + + This function indicates the beginning of a run of text that should be + associated with the given URL. The run of text should be closed with + a matching call to :func:`diagnostic_message_buffer_end_url`. + + ``msg_buf`` and ``url`` must both be non-NULL. + + For text output in a suitably modern terminal, the run of text will + be emitted as a clickable hyperlink to the URL. + + For SARIF sinks, the run of text will be emitted using SARIF's + embedded link syntax. + + This is analogous to the + :doc:`%{ message formatting code <message-formatting>`. + + This function was added in :ref:`LIBGDIAGNOSTICS_ABI_4`; you can + test for its presence using + + .. code-block:: c + + #ifdef LIBDIAGNOSTICS_HAVE_diagnostic_message_buffer + +.. function:: void diagnostic_message_buffer_end_url (diagnostic_message_buffer *msg_buf) + + This function ends a run of text within the buffer started with + :func:`diagnostic_message_buffer_begin_url`. + + ``msg_buf`` must be non-NULL. + + This is analogous to the + :doc:`%} message formatting code <message-formatting>`. + + This function was added in :ref:`LIBGDIAGNOSTICS_ABI_4`; you can + test for its presence using + + .. code-block:: c + + #ifdef LIBDIAGNOSTICS_HAVE_diagnostic_message_buffer + +Quoted text +*********** + +.. function:: void diagnostic_message_buffer_begin_quote (diagnostic_message_buffer *msg_buf) + + This function indicates the beginning of a run of text that should be + printed in quotes. The run of text should be closed with + a matching call to :func:`diagnostic_message_buffer_end_quote`. + + ``msg_buf`` must be non-NULL. + + For text output in a suitably modern terminal, the run of text will + appear in bold. + be emitted as a clickable hyperlink to the URL. + + For SARIF sinks, the run of text will be emitted using SARIF's + embedded link syntax. + + This is analogous to the + ``%<``:doc:`message formatting code <message-formatting>`. + + This function was added in :ref:`LIBGDIAGNOSTICS_ABI_4`; you can + test for its presence using + + .. code-block:: c + + #ifdef LIBDIAGNOSTICS_HAVE_diagnostic_message_buffer + +.. function:: void diagnostic_message_buffer_end_url (diagnostic_message_buffer *msg_buf) + + This function ends a run of text within the buffer started with + :func:`diagnostic_message_buffer_begin_url`. + + ``msg_buf`` must be non-NULL. + + This is analogous to the + :doc:`%> message formatting code <message-formatting>`. + + This function was added in :ref:`LIBGDIAGNOSTICS_ABI_4`; you can + test for its presence using + + .. code-block:: c + + #ifdef LIBDIAGNOSTICS_HAVE_diagnostic_message_buffer + +Color +***** + +.. function:: void diagnostic_message_buffer_begin_color (diagnostic_message_buffer *msg_buf, \ + const char *color) + + This function indicates the beginning of a run of text that should be + colorized as the given color. The run of text should be closed with + a matching call to :func:`diagnostic_message_buffer_end_color`. + + The precise set of available color names is currently undocumented. + + ``msg_buf`` and ``color`` must both be non-NULL. + + For text output in a suitable terminal, the run of text will + be colorized. + + For SARIF sinks, the run of text will be emitted using SARIF's + embedded link syntax. + + This is analogous to the + :doc:`%r message formatting code <message-formatting>`. + + This function was added in :ref:`LIBGDIAGNOSTICS_ABI_4`; you can + test for its presence using + + .. code-block:: c + + #ifdef LIBDIAGNOSTICS_HAVE_diagnostic_message_buffer + +.. function:: void diagnostic_message_buffer_end_color (diagnostic_message_buffer *msg_buf) + + This function ends a run of text within the buffer started with + :func:`diagnostic_message_buffer_begin_color`. + + ``msg_buf`` must be non-NULL. + + This is analogous to the + :doc:`%R message formatting code <message-formatting>`. + + This function was added in :ref:`LIBGDIAGNOSTICS_ABI_4`; you can + test for its presence using + + .. code-block:: c + + #ifdef LIBDIAGNOSTICS_HAVE_diagnostic_message_buffer + +Debugging a message buffer +************************** + +.. function:: void diagnostic_message_buffer_dump (const diagnostic_message_buffer *msg_buf, \ + FILE *outf) + + This function writes a representation of the contents of ``msg_buf`` + to ``outf``, for debugging. + + ``msg_buf`` can be NULL or non-NULL. + ``outf`` must be non-NULL. + + This function was added in :ref:`LIBGDIAGNOSTICS_ABI_4`; you can + test for its presence using + + .. code-block:: c + + #ifdef LIBDIAGNOSTICS_HAVE_diagnostic_message_buffer diff --git a/gcc/doc/libgdiagnostics/topics/message-formatting.rst b/gcc/doc/libgdiagnostics/topics/message-formatting.rst index 7064b702be21..803feba13cc9 100644 --- a/gcc/doc/libgdiagnostics/topics/message-formatting.rst +++ b/gcc/doc/libgdiagnostics/topics/message-formatting.rst @@ -23,6 +23,11 @@ Message formatting Various libgdiagnostics entrypoints take a format string and variadic arguments. +.. note:: + + See also :type:`diagnostic_message_buffer`, which offers an + alternative way to build up messages. + The format strings take codes prefixed by ``%``, or ``%q`` to put the result in quotes. For example:: diff --git a/gcc/doc/libgdiagnostics/topics/physical-locations.rst b/gcc/doc/libgdiagnostics/topics/physical-locations.rst index 099e27e98224..be8e7ebc5d1f 100644 --- a/gcc/doc/libgdiagnostics/topics/physical-locations.rst +++ b/gcc/doc/libgdiagnostics/topics/physical-locations.rst @@ -284,3 +284,23 @@ This diagnostic has three locations | ~~ ^ ~~~~~ | | | | int const char * + +.. function:: void diagnostic_add_location_with_label_via_msg_buf (diagnostic *diag, \ + const diagnostic_physical_location *loc, \ + diagnostic_message_buffer *msg_buf) + + This is equivalent to :func:`diagnostic_add_location_with_label` but + using a message buffer rather than a text string. + + ``diag`` and ``msg_buf`` must both be non-NULL. + + Calling this function transfers ownership of ``msg_buf`` to the + diagnostic - do not call :func:`diagnostic_message_buffer_release` on + it. + + This function was added in :ref:`LIBGDIAGNOSTICS_ABI_3`; you can + test for its presence using + + .. code-block:: c + + #ifdef LIBDIAGNOSTICS_HAVE_diagnostic_message_buffer diff --git a/gcc/doc/libgdiagnostics/tutorial/07-execution-paths.rst b/gcc/doc/libgdiagnostics/tutorial/07-execution-paths.rst index 0ac8bf07b44e..9147171ad6f5 100644 --- a/gcc/doc/libgdiagnostics/tutorial/07-execution-paths.rst +++ b/gcc/doc/libgdiagnostics/tutorial/07-execution-paths.rst @@ -134,8 +134,6 @@ Here's the above example in full: :end-before: end full example -Moving on -********* - -That's the end of the tutorial. For more information on libgdiagnostics, see -the :doc:`topic guide <../topics/index>`. +See the :doc:`guide to execution paths <../topics/execution-paths>` +for more information, or go on to +:doc:`the next section of the tutorial <08-message-buffers>`. diff --git a/gcc/doc/libgdiagnostics/tutorial/08-message-buffers.rst b/gcc/doc/libgdiagnostics/tutorial/08-message-buffers.rst new file mode 100644 index 000000000000..a83c50ce91ba --- /dev/null +++ b/gcc/doc/libgdiagnostics/tutorial/08-message-buffers.rst @@ -0,0 +1,75 @@ +.. Copyright (C) 2025 Free Software Foundation, Inc. + Originally contributed by David Malcolm <dmalc...@redhat.com> + + This 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 + <https://www.gnu.org/licenses/>. + +.. default-domain:: c + +Tutorial part 8: message buffers +================================ + +In previous examples, we finished a diagnostic with a call to +:func:`diagnostic_finish`, which takes a format string and arguments +to determine the text message of the diagnostic. + +Sometimes this approach is inconvenient, such as where you might want to +build up a message programatically from a series of components. +Additionally, you might have existing code that uses ``fprintf``, whereas +:func:`diagnostic_finish` has its +:doc:`own formatting conventions <../topics/message-formatting>` which are +:strong:`not` the same as printf. + +For this reason libgdiagnostics (from ``LIBGDIAGNOSTICS_ABI_3`` onwards) +supports :type:`diagnostic_message_buffer`, which can be used to accumulate a +message before using it. + +You create a :type:`diagnostic_message_buffer` using +:func:`diagnostic_message_buffer_new`. + +There are various API entrypoints for accumulating text into the buffer. + +For example: + +.. literalinclude:: ../../../testsuite/libgdiagnostics.dg/test-message-buffer.c + :language: c + :start-after: /* begin quoted source */ + :end-before: /* end quoted source */ + +Running this will produce this text output:: + +.. code-block:: console + + $ ./test-message-buffer.c.exe + ./test-message-buffer.c.exe: error: this is a string; foo; int: 42 str: mostly harmless; this is a link 'this is quoted' highlight A highlight B (1). + +where in a suitably-capable terminal if a text sink is directly +connected to a tty: + +* the ``this is a link`` will be a clickable hyperlink + (and the URL will be captured in SARIF output). + +* the quoted text will be in bold + +* the ``highlight A`` and ``highlight B`` text will be colorized + +* the event ID will be colorized (and will be a URL in SARIF output + if used within a :type:`diagnostic_execution_path`). + + +Moving on +********* + +That's the end of the tutorial. For more information on libgdiagnostics, see +the :doc:`topic guide <../topics/index>`. diff --git a/gcc/doc/libgdiagnostics/tutorial/index.rst b/gcc/doc/libgdiagnostics/tutorial/index.rst index 172a28c6c772..09a15e9c209e 100644 --- a/gcc/doc/libgdiagnostics/tutorial/index.rst +++ b/gcc/doc/libgdiagnostics/tutorial/index.rst @@ -30,3 +30,4 @@ The following tutorial gives an overview of how to use libgdiagnostics. 05-warnings.rst 06-fix-it-hints.rst 07-execution-paths.rst + 08-message-buffers.rst diff --git a/gcc/libgdiagnostics++.h b/gcc/libgdiagnostics++.h index 4beee446d8b4..c955d56db5b3 100644 --- a/gcc/libgdiagnostics++.h +++ b/gcc/libgdiagnostics++.h @@ -37,6 +37,7 @@ class diagnostic; class graph; class node; class edge; +class message_buffer; /* Wrapper around a borrowed diagnostic_text_sink *. */ @@ -134,6 +135,65 @@ public: const diagnostic_logical_location *m_inner; }; +/* Wrapper around a diagnostic_message_buffer *, with ownership. */ + +class message_buffer +{ +public: + message_buffer () : m_inner (nullptr) {} + message_buffer (diagnostic_message_buffer *inner) : m_inner (inner) {} + ~message_buffer () + { + if (m_inner) + diagnostic_message_buffer_release (m_inner); + } + message_buffer (const message_buffer &) = delete; + message_buffer (message_buffer &&other) + { + m_inner = other.m_inner; + other.m_inner = nullptr; + } + message_buffer& operator= (const message_buffer &) = delete; + message_buffer& operator= (message_buffer &&other) + { + if (m_inner) + diagnostic_message_buffer_release (m_inner); + m_inner = other.m_inner; + other.m_inner = nullptr; + return *this; + } + + message_buffer& + operator+= (const char *str) + { + diagnostic_message_buffer_append_str (m_inner, str); + return *this; + } + + message_buffer& + operator+= (char ch) + { + diagnostic_message_buffer_append_byte (m_inner, ch); + return *this; + } + + message_buffer & + begin_url (const char *url) + { + diagnostic_message_buffer_begin_url (m_inner, url); + return *this; + } + + message_buffer & + end_url () + { + diagnostic_message_buffer_end_url (m_inner); + return *this; + } + + diagnostic_message_buffer *m_inner; +}; + /* RAII class around a diagnostic_execution_path *. */ class execution_path @@ -191,6 +251,12 @@ public: va_list *args) LIBGDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (5, 0); + diagnostic_event_id + add_event_via_msg_buf (physical_location physical_loc, + logical_location logical_loc, + unsigned stack_depth, + message_buffer &&msg_buf); + diagnostic_execution_path *m_inner; bool m_owned; }; @@ -230,6 +296,10 @@ public: add_location_with_label (physical_location loc, const char *text); + void + add_location_with_label (physical_location loc, + message_buffer &&text); + void set_logical_location (logical_location loc); @@ -261,6 +331,9 @@ public: LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2) LIBGDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (2, 0); + void + finish_via_msg_buf (message_buffer &&msg_buf); + ::diagnostic * const m_inner; }; @@ -450,6 +523,8 @@ public: void set_description (const char *); + void + set_description (message_buffer &&); node get_node_by_id (const char *id) const; @@ -459,6 +534,8 @@ public: edge add_edge (const char *id, node src_node, node dst_node, const char *label); + edge + add_edge (const char *id, node src_node, node dst_node, message_buffer &&label); diagnostic_graph *m_inner; bool m_owned; @@ -474,6 +551,8 @@ public: void set_label (const char *); + void + set_label (message_buffer &&); void set_location (physical_location loc); @@ -591,6 +670,21 @@ execution_path::add_event_va (physical_location physical_loc, args); } +inline diagnostic_event_id +execution_path::add_event_via_msg_buf (physical_location physical_loc, + logical_location logical_loc, + unsigned stack_depth, + message_buffer &&msg_buf) +{ + diagnostic_message_buffer *inner_msg_buf = msg_buf.m_inner; + msg_buf.m_inner = nullptr; + return diagnostic_execution_path_add_event_via_msg_buf (m_inner, + physical_loc.m_inner, + logical_loc.m_inner, + stack_depth, + inner_msg_buf); +} + // class group inline @@ -633,6 +727,17 @@ diagnostic::add_location_with_label (physical_location loc, diagnostic_add_location_with_label (m_inner, loc.m_inner, text); } +inline void +diagnostic::add_location_with_label (physical_location loc, + message_buffer &&msg_buf) +{ + diagnostic_message_buffer *inner_msg_buf = msg_buf.m_inner; + msg_buf.m_inner = nullptr; + diagnostic_add_location_with_label_via_msg_buf (m_inner, + loc.m_inner, + inner_msg_buf); +} + inline void diagnostic::add_location (physical_location loc) { @@ -710,6 +815,14 @@ diagnostic::finish_va (const char *fmt, va_list *args) diagnostic_finish_va (m_inner, fmt, args); } +inline void +diagnostic::finish_via_msg_buf (message_buffer &&msg_buf) +{ + diagnostic_message_buffer *inner_msg_buf = msg_buf.m_inner; + msg_buf.m_inner = nullptr; + diagnostic_finish_via_msg_buf (m_inner, inner_msg_buf); +} + // class manager inline file @@ -821,6 +934,14 @@ graph::set_description (const char *desc) diagnostic_graph_set_description (m_inner, desc); } +inline void +graph::set_description (message_buffer &&msg_buf) +{ + diagnostic_message_buffer *inner_msg_buf = msg_buf.m_inner; + msg_buf.m_inner = nullptr; + diagnostic_graph_set_description_via_msg_buf (m_inner, inner_msg_buf); +} + inline node graph::get_node_by_id (const char *id) const { @@ -845,6 +966,20 @@ graph::add_edge (const char *id, label)); } +inline edge +graph::add_edge (const char *id, + node src_node, node dst_node, + message_buffer &&label) +{ + diagnostic_message_buffer *inner_label = label.m_inner; + label.m_inner = nullptr; + return edge (diagnostic_graph_add_edge_via_msg_buf (m_inner, + id, + src_node.m_inner, + dst_node.m_inner, + inner_label)); +} + // class node inline void @@ -853,6 +988,14 @@ node::set_label (const char *label) diagnostic_node_set_label (m_inner, label); } +inline void +node::set_label (message_buffer &&label) +{ + diagnostic_message_buffer *inner_label = label.m_inner; + label.m_inner = nullptr; + diagnostic_node_set_label_via_msg_buf (m_inner, inner_label); +} + inline void node::set_location (physical_location loc) { diff --git a/gcc/libgdiagnostics-private.h b/gcc/libgdiagnostics-private.h index 0f628e429f93..0e90f8720079 100644 --- a/gcc/libgdiagnostics-private.h +++ b/gcc/libgdiagnostics-private.h @@ -31,20 +31,6 @@ extern "C" { /* Entrypoints added in LIBGDIAGNOSTICS_ABI_3. */ -extern diagnostic_event_id -private_diagnostic_execution_path_add_event_2 (diagnostic_execution_path *path, - const diagnostic_physical_location *physical_loc, - const diagnostic_logical_location *logical_loc, - unsigned stack_depth, - diagnostic_graph *state_graph, - const char *fmt, ...) - LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) - LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (2) - LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (3) - LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (5) - LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (6) - LIBGDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (6, 7); - extern void private_diagnostic_graph_set_property_bag (diagnostic_graph &graph, std::unique_ptr<json::object> properties); @@ -57,6 +43,21 @@ extern void private_diagnostic_edge_set_property_bag (diagnostic_edge &edge, std::unique_ptr<json::object> properties); +/* Entrypoint added in LIBGDIAGNOSTICS_ABI_4. */ + +extern diagnostic_event_id +private_diagnostic_execution_path_add_event_3 (diagnostic_execution_path *path, + const diagnostic_physical_location *physical_loc, + const diagnostic_logical_location *logical_loc, + unsigned stack_depth, + diagnostic_graph *state_graph, + diagnostic_message_buffer *msg_buf) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (2) + LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (3) + LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (5) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (6); + } // extern "C" #endif /* LIBGDIAGNOSTICS_PRIVATE_H */ diff --git a/gcc/libgdiagnostics.cc b/gcc/libgdiagnostics.cc index 04812dce4fe6..172f19c59bd6 100644 --- a/gcc/libgdiagnostics.cc +++ b/gcc/libgdiagnostics.cc @@ -39,6 +39,9 @@ along with GCC; see the file COPYING3. If not see #include "edit-context.h" #include "libgdiagnostics.h" #include "libgdiagnostics-private.h" +#include "pretty-print-format-impl.h" +#include "pretty-print-markup.h" +#include "auto-obstack.h" class owned_nullable_string { @@ -246,6 +249,97 @@ private: diagnostic_source_printing_options m_source_printing; }; +/* A token_printer that makes a deep copy of the pp_token_list + into another obstack. */ + +class copying_token_printer : public token_printer +{ +public: + copying_token_printer (obstack &dst_obstack, + pp_token_list &dst_token_list) + : m_dst_obstack (dst_obstack), + m_dst_token_list (dst_token_list) + { + } + + void + print_tokens (pretty_printer *, + const pp_token_list &tokens) final override + { + for (auto iter = tokens.m_first; iter; iter = iter->m_next) + switch (iter->m_kind) + { + default: + gcc_unreachable (); + + case pp_token::kind::text: + { + const pp_token_text *sub = as_a <const pp_token_text *> (iter); + /* Copy the text, with null terminator. */ + obstack_grow (&m_dst_obstack, sub->m_value.get (), + strlen (sub->m_value.get ()) + 1); + m_dst_token_list.push_back_text + (label_text::borrow (XOBFINISH (&m_dst_obstack, + const char *))); + } + break; + + case pp_token::kind::begin_color: + { + pp_token_begin_color *sub = as_a <pp_token_begin_color *> (iter); + /* Copy the color, with null terminator. */ + obstack_grow (&m_dst_obstack, sub->m_value.get (), + strlen (sub->m_value.get ()) + 1); + m_dst_token_list.push_back<pp_token_begin_color> + (label_text::borrow (XOBFINISH (&m_dst_obstack, + const char *))); + } + break; + case pp_token::kind::end_color: + m_dst_token_list.push_back<pp_token_end_color> (); + break; + + case pp_token::kind::begin_quote: + m_dst_token_list.push_back<pp_token_begin_quote> (); + break; + case pp_token::kind::end_quote: + m_dst_token_list.push_back<pp_token_end_quote> (); + break; + + case pp_token::kind::begin_url: + { + pp_token_begin_url *sub = as_a <pp_token_begin_url *> (iter); + /* Copy the URL, with null terminator. */ + obstack_grow (&m_dst_obstack, sub->m_value.get (), + strlen (sub->m_value.get ()) + 1); + m_dst_token_list.push_back<pp_token_begin_url> + (label_text::borrow (XOBFINISH (&m_dst_obstack, + const char *))); + } + break; + case pp_token::kind::end_url: + m_dst_token_list.push_back<pp_token_end_url> (); + break; + + case pp_token::kind::event_id: + { + pp_token_event_id *sub = as_a <pp_token_event_id *> (iter); + m_dst_token_list.push_back<pp_token_event_id> (sub->m_event_id); + } + break; + + case pp_token::kind::custom_data: + /* These should have been eliminated by replace_custom_tokens. */ + gcc_unreachable (); + break; + } + } + +private: + obstack &m_dst_obstack; + pp_token_list &m_dst_token_list; +}; + class sarif_sink : public sink { public: @@ -255,6 +349,107 @@ public: const sarif_generation_options &sarif_gen_opts); }; +struct diagnostic_message_buffer +{ + diagnostic_message_buffer () + : m_tokens (m_obstack) + { + } + + diagnostic_message_buffer (const char *gmsgid, + va_list *args) + : m_tokens (m_obstack) + { + text_info text (gmsgid, args, errno); + pretty_printer pp; + pp.set_output_stream (nullptr); + copying_token_printer tok_printer (m_obstack, m_tokens); + pp.set_token_printer (&tok_printer); + pp_format (&pp, &text); + pp_output_formatted_text (&pp, nullptr); + } + + + std::string to_string () const; + + auto_obstack m_obstack; + pp_token_list m_tokens; +}; + +/* A pp_element subclass that replays the saved tokens in a + diagnostic_message_buffer. */ + +class pp_element_message_buffer : public pp_element +{ +public: + pp_element_message_buffer (diagnostic_message_buffer &msg_buf) + : m_msg_buf (msg_buf) + { + } + + void add_to_phase_2 (pp_markup::context &ctxt) final override + { + /* Convert to text, possibly with colorization, URLs, etc. */ + for (auto iter = m_msg_buf.m_tokens.m_first; iter; iter = iter->m_next) + switch (iter->m_kind) + { + default: + gcc_unreachable (); + + case pp_token::kind::text: + { + pp_token_text *sub = as_a <pp_token_text *> (iter); + pp_string (&ctxt.m_pp, sub->m_value.get ()); + ctxt.push_back_any_text (); + } + break; + + case pp_token::kind::begin_color: + { + pp_token_begin_color *sub = as_a <pp_token_begin_color *> (iter); + ctxt.begin_highlight_color (sub->m_value.get ()); + } + break; + case pp_token::kind::end_color: + ctxt.end_highlight_color (); + break; + + case pp_token::kind::begin_quote: + ctxt.begin_quote (); + break; + case pp_token::kind::end_quote: + ctxt.end_quote (); + break; + + case pp_token::kind::begin_url: + { + pp_token_begin_url *sub = as_a <pp_token_begin_url *> (iter); + ctxt.begin_url (sub->m_value.get ()); + } + break; + case pp_token::kind::end_url: + ctxt.end_url (); + break; + + case pp_token::kind::event_id: + { + pp_token_event_id *sub = as_a <pp_token_event_id *> (iter); + gcc_assert (sub->m_event_id.known_p ()); + ctxt.add_event_id (sub->m_event_id); + } + break; + + case pp_token::kind::custom_data: + /* We don't have a way of handling custom_data tokens here. */ + gcc_unreachable (); + break; + } + } + +private: + diagnostic_message_buffer &m_msg_buf; +}; + /* Helper for the linemap code. */ static size_t @@ -508,9 +703,15 @@ public: m_sinks.push_back (std::move (sink)); } - void emit (diagnostic &diag, const char *msgid, va_list *args) + void emit_va (diagnostic &diag, const char *msgid, va_list *args) LIBGDIAGNOSTICS_PARAM_GCC_FORMAT_STRING(3, 0); + void emit (diagnostic &diag, const char *msgid, ...) + LIBGDIAGNOSTICS_PARAM_GCC_FORMAT_STRING(3, 4); + + void emit_msg_buf (diagnostic &diag, + diagnostic_message_buffer &msg_buf); + diagnostic_file * new_file (const char *name, const char *sarif_source_language) @@ -755,10 +956,10 @@ struct diagnostic_graph : public diagnostics::digraphs::digraph diagnostic_graph (diagnostic_manager &) {} diagnostic_node * - add_node_with_id (std::string id, + add_node_with_id (std::string node_id, diagnostic_node *parent_node); diagnostic_edge * - add_edge_with_label (const char *id, + add_edge_with_label (const char *edge_id, diagnostic_node &src_node, diagnostic_node &dst_node, const char *label); @@ -791,15 +992,14 @@ public: const diagnostic_logical_location *logical_loc, unsigned stack_depth, std::unique_ptr<diagnostic_graph> state_graph, - const char *gmsgid, - va_list *args) + std::unique_ptr<diagnostic_message_buffer> msg_buf) : m_physical_loc (physical_loc), m_logical_loc (logical_loc), m_stack_depth (stack_depth), - m_state_graph (std::move (state_graph)) + m_state_graph (std::move (state_graph)), + m_msg_buf (std::move (msg_buf)) { - m_desc_uncolored = make_desc (gmsgid, args, false); - m_desc_colored = make_desc (gmsgid, args, true); + gcc_assert (m_msg_buf); } /* diagnostic_event vfunc implementations. */ @@ -816,10 +1016,11 @@ public: void print_desc (pretty_printer &pp) const final override { - if (pp_show_color (&pp)) - pp_string (&pp, m_desc_colored.get ()); - else - pp_string (&pp, m_desc_uncolored.get ()); + if (m_msg_buf) + { + pp_element_message_buffer e_msg_buf (*m_msg_buf); + pp_printf (&pp, "%e", &e_msg_buf); + } } logical_location get_logical_location () const final override @@ -877,8 +1078,7 @@ private: const diagnostic_logical_location *m_logical_loc; unsigned m_stack_depth; std::unique_ptr<diagnostic_graph> m_state_graph; - label_text m_desc_uncolored; - label_text m_desc_colored; + std::unique_ptr<diagnostic_message_buffer> m_msg_buf; }; class libgdiagnostics_path_thread : public diagnostic_thread @@ -911,14 +1111,31 @@ struct diagnostic_execution_path : public diagnostic_path std::unique_ptr<diagnostic_graph> state_graph, const char *gmsgid, va_list *args) + { + auto msg_buf = std::make_unique<diagnostic_message_buffer> (gmsgid, args); + + m_events.push_back + (std::make_unique<libgdiagnostics_path_event> (physical_loc, + logical_loc, + stack_depth, + std::move (state_graph), + std::move (msg_buf))); + return m_events.size () - 1; + } + + diagnostic_event_id_t + add_event_via_msg_buf (const diagnostic_physical_location *physical_loc, + const diagnostic_logical_location *logical_loc, + unsigned stack_depth, + std::unique_ptr<diagnostic_graph> state_graph, + std::unique_ptr<diagnostic_message_buffer> msg_buf) { m_events.push_back (std::make_unique<libgdiagnostics_path_event> (physical_loc, logical_loc, stack_depth, std::move (state_graph), - gmsgid, - args)); + std::move (msg_buf))); return m_events.size () - 1; } @@ -1041,6 +1258,19 @@ public: m_labels.push_back (std::move (label)); } + void + add_location_with_label (const diagnostic_physical_location *loc, + std::unique_ptr<diagnostic_message_buffer> msg_buf) + { + std::string str = msg_buf->to_string (); + std::unique_ptr<range_label> label + = std::make_unique <impl_range_label> (str.c_str ()); + m_rich_loc.add_range (as_location_t (loc), + SHOW_RANGE_WITHOUT_CARET, + label.get ()); + m_labels.push_back (std::move (label)); + } + void set_logical_location (const diagnostic_logical_location *logical_loc) { @@ -1290,6 +1520,64 @@ sarif_sink::sarif_sink (diagnostic_manager &mgr, mgr.get_dc ().add_sink (std::move (inner_sink)); } +// struct diagnostic_message_buffer + +std::string +diagnostic_message_buffer::to_string () const +{ + std::string result; + + /* Convert to text, dropping colorization, URLs, etc. */ + for (auto iter = m_tokens.m_first; iter; iter = iter->m_next) + switch (iter->m_kind) + { + default: + gcc_unreachable (); + + case pp_token::kind::text: + { + pp_token_text *sub = as_a <pp_token_text *> (iter); + result += sub->m_value.get (); + } + break; + + case pp_token::kind::begin_color: + case pp_token::kind::end_color: + // Skip + break; + + case pp_token::kind::begin_quote: + result += open_quote; + break; + + case pp_token::kind::end_quote: + result += close_quote; + break; + + case pp_token::kind::begin_url: + case pp_token::kind::end_url: + // Skip + break; + + case pp_token::kind::event_id: + { + pp_token_event_id *sub = as_a <pp_token_event_id *> (iter); + gcc_assert (sub->m_event_id.known_p ()); + result += '('; + result += std::to_string (sub->m_event_id.one_based ()); + result += ')'; + } + break; + + case pp_token::kind::custom_data: + /* We don't have a way of handling custom_data tokens here. */ + gcc_unreachable (); + break; + } + + return result; +} + /* struct diagnostic_manager. */ void @@ -1302,7 +1590,7 @@ diagnostic_manager::write_patch (FILE *dst_stream) } void -diagnostic_manager::emit (diagnostic &diag, const char *msgid, va_list *args) +diagnostic_manager::emit_va (diagnostic &diag, const char *msgid, va_list *args) { set_line_table_global (); @@ -1331,6 +1619,24 @@ GCC_DIAGNOSTIC_POP m_current_diag = nullptr; } +void +diagnostic_manager::emit (diagnostic &diag, const char *msgid, ...) +{ + va_list args; + va_start (args, msgid); + emit_va (diag, msgid, &args); + va_end (args); +} + +void +diagnostic_manager::emit_msg_buf (diagnostic &diag, + diagnostic_message_buffer &msg_buf) +{ + + pp_element_message_buffer e_msg_buf (msg_buf); + emit (diag, "%e", &e_msg_buf); +} + diagnostic_execution_path * diagnostic_manager::new_execution_path () { @@ -1365,10 +1671,10 @@ diagnostic_manager::take_global_graph (std::unique_ptr<diagnostic_graph> graph) /* Error-checking at the API boundary. */ #define FAIL_IF_NULL(PTR_ARG) \ - do { \ - volatile const void *p = (PTR_ARG); \ - if (!p) { \ - fprintf (stderr, "%s: %s must be non-NULL\n", \ + do { \ + volatile const void *ptr_arg = (PTR_ARG); \ + if (!ptr_arg) { \ + fprintf (stderr, "%s: %s must be non-NULL\n", \ __func__, #PTR_ARG); \ abort (); \ } \ @@ -2044,7 +2350,7 @@ diagnostic_finish_va (diagnostic *diag, const char *gmsgid, va_list *args) else progname = "progname"; auto_diagnostic_group d; - diag->get_manager ().emit (*diag, gmsgid, args); + diag->get_manager ().emit_va (*diag, gmsgid, args); delete diag; } @@ -2165,13 +2471,14 @@ diagnostic_manager_set_analysis_target (diagnostic_manager *mgr, mgr->get_dc ().set_main_input_filename (file->get_name ()); } -// struct diagnostic_graph : public diagnostics::digraphs::graph<foo_traits> +/* Public entrypoint. */ diagnostic_node * -diagnostic_graph::add_node_with_id (std::string id, +diagnostic_graph::add_node_with_id (std::string node_id, diagnostic_node *parent_node) { - auto node_up = std::make_unique<diagnostic_node> (*this, std::move (id)); + auto node_up + = std::make_unique<diagnostic_node> (*this, std::move (node_id)); diagnostic_node *new_node = node_up.get (); if (parent_node) parent_node->add_child (std::move (node_up)); @@ -2180,14 +2487,16 @@ diagnostic_graph::add_node_with_id (std::string id, return new_node; } +/* Public entrypoint. */ + diagnostic_edge * -diagnostic_graph::add_edge_with_label (const char *id, +diagnostic_graph::add_edge_with_label (const char *edge_id, diagnostic_node &src_node, diagnostic_node &dst_node, const char *label) { auto edge_up - = std::make_unique<diagnostic_edge> (*this, id, + = std::make_unique<diagnostic_edge> (*this, edge_id, src_node, dst_node); diagnostic_edge *new_edge = edge_up.get (); if (label) @@ -2247,22 +2556,24 @@ diagnostic_graph_set_description (diagnostic_graph *graph, graph->set_description (desc); } +/* Public entrypoint. */ + diagnostic_node * diagnostic_graph_add_node (diagnostic_graph *graph, - const char *id, + const char *node_id, diagnostic_node *parent_node) { FAIL_IF_NULL (graph); - FAIL_IF_NULL (id); + FAIL_IF_NULL (node_id); - return graph->add_node_with_id (id, parent_node); + return graph->add_node_with_id (node_id, parent_node); } /* Public entrypoint. */ diagnostic_edge * diagnostic_graph_add_edge (diagnostic_graph *graph, - const char *id, + const char *edge_id, diagnostic_node *src_node, diagnostic_node *dst_node, const char *label) @@ -2271,31 +2582,31 @@ diagnostic_graph_add_edge (diagnostic_graph *graph, FAIL_IF_NULL (src_node); FAIL_IF_NULL (dst_node); - return graph->add_edge_with_label (id, *src_node, *dst_node, label); + return graph->add_edge_with_label (edge_id, *src_node, *dst_node, label); } /* Public entrypoint. */ diagnostic_node * diagnostic_graph_get_node_by_id (diagnostic_graph *graph, - const char *id) + const char *node_id) { FAIL_IF_NULL (graph); - FAIL_IF_NULL (id); + FAIL_IF_NULL (node_id); - return static_cast<diagnostic_node *> (graph->get_node_by_id (id)); + return static_cast<diagnostic_node *> (graph->get_node_by_id (node_id)); } /* Public entrypoint. */ diagnostic_edge * diagnostic_graph_get_edge_by_id (diagnostic_graph *graph, - const char *id) + const char *edge_id) { FAIL_IF_NULL (graph); - FAIL_IF_NULL (id); + FAIL_IF_NULL (edge_id); - return static_cast<diagnostic_edge *> (graph->get_edge_by_id (id)); + return static_cast<diagnostic_edge *> (graph->get_edge_by_id (edge_id)); } /* Public entrypoint. */ @@ -2320,6 +2631,8 @@ diagnostic_node_set_label (diagnostic_node *node, node->set_label (label); } +/* Public entrypoint. */ + void diagnostic_node_set_logical_location (diagnostic_node *node, const diagnostic_logical_location *logical_loc) @@ -2332,34 +2645,6 @@ diagnostic_node_set_logical_location (diagnostic_node *node, /* Private entrypoint. */ -diagnostic_event_id -private_diagnostic_execution_path_add_event_2 (diagnostic_execution_path *path, - const diagnostic_physical_location *physical_loc, - const diagnostic_logical_location *logical_loc, - unsigned stack_depth, - diagnostic_graph *state_graph, - const char *gmsgid, ...) - -{ - FAIL_IF_NULL (path); - FAIL_IF_NULL (gmsgid); - - va_list args; - va_start (args, gmsgid); - diagnostic_event_id_t result - = path->add_event_va (physical_loc, - logical_loc, - stack_depth, - std::unique_ptr <diagnostic_graph> (state_graph), - gmsgid, &args); - va_end (args); - - return as_diagnostic_event_id (result); - -} - -/* Private entrypoint. */ - void private_diagnostic_graph_set_property_bag (diagnostic_graph &graph, std::unique_ptr<json::object> properties) @@ -2384,3 +2669,285 @@ private_diagnostic_edge_set_property_bag (diagnostic_edge &edge, { edge.set_property_bag (std::move (properties)); } + +/* Public entrypoint. */ + +diagnostic_message_buffer * +diagnostic_message_buffer_new () +{ + return new diagnostic_message_buffer (); +} + +/* Public entrypoint. */ + +void +diagnostic_message_buffer_release (diagnostic_message_buffer *msg_buf) +{ + FAIL_IF_NULL (msg_buf); + delete msg_buf; +} + +void +diagnostic_message_buffer_append_str (diagnostic_message_buffer *msg_buf, + const char *p) +{ + FAIL_IF_NULL (msg_buf); + FAIL_IF_NULL (p); + msg_buf->m_tokens.push_back_text (label_text::take (xstrdup (p))); +} + +/* Public entrypoint. */ + +void +diagnostic_message_buffer_append_text (diagnostic_message_buffer *msg_buf, + const char *p, + size_t len) +{ + FAIL_IF_NULL (msg_buf); + FAIL_IF_NULL (p); + msg_buf->m_tokens.push_back_text (label_text::take (xstrndup (p, len))); +} + +/* Public entrypoint. */ + +void +diagnostic_message_buffer_append_byte (diagnostic_message_buffer *msg_buf, + char ch) +{ + FAIL_IF_NULL (msg_buf); + msg_buf->m_tokens.push_back_byte (ch); +} + +/* Public entrypoint. */ + +void +diagnostic_message_buffer_append_printf (diagnostic_message_buffer *msg_buf, + const char *fmt, ...) +{ + FAIL_IF_NULL (msg_buf); + FAIL_IF_NULL (fmt); + + va_list args; + va_start (args, fmt); + + char *formatted_buf = xvasprintf (fmt, args); + + va_end (args); + + msg_buf->m_tokens.push_back_text (label_text::take (formatted_buf)); +} + +/* Public entrypoint. */ + +void +diagnostic_message_buffer_append_event_id (diagnostic_message_buffer *msg_buf, + diagnostic_event_id event_id) +{ + FAIL_IF_NULL (msg_buf); + msg_buf->m_tokens.push_back<pp_token_event_id> (event_id); +} + +/* Public entrypoint. */ + +void +diagnostic_message_buffer_begin_url (diagnostic_message_buffer *msg_buf, + const char *url) +{ + FAIL_IF_NULL (msg_buf); + FAIL_IF_NULL (url); + msg_buf->m_tokens.push_back<pp_token_begin_url> + (label_text::take (xstrdup (url))); +} + +/* Public entrypoint. */ + +void +diagnostic_message_buffer_end_url (diagnostic_message_buffer *msg_buf) +{ + FAIL_IF_NULL (msg_buf); + msg_buf->m_tokens.push_back<pp_token_end_url> (); +} + +/* Public entrypoint. */ + +void +diagnostic_message_buffer_begin_quote (diagnostic_message_buffer *msg_buf) +{ + FAIL_IF_NULL (msg_buf); + msg_buf->m_tokens.push_back<pp_token_begin_quote> (); +} + +/* Public entrypoint. */ + +void +diagnostic_message_buffer_end_quote (diagnostic_message_buffer *msg_buf) +{ + FAIL_IF_NULL (msg_buf); + msg_buf->m_tokens.push_back<pp_token_end_quote> (); +} + +/* Public entrypoint. */ + +void +diagnostic_message_buffer_begin_color (diagnostic_message_buffer *msg_buf, + const char *color) +{ + FAIL_IF_NULL (msg_buf); + FAIL_IF_NULL (color); + msg_buf->m_tokens.push_back<pp_token_begin_color> + (label_text::take (xstrdup (color))); +} + +/* Public entrypoint. */ + +void +diagnostic_message_buffer_end_color (diagnostic_message_buffer *msg_buf) +{ + FAIL_IF_NULL (msg_buf); + msg_buf->m_tokens.push_back<pp_token_end_color> (); +} + +/* Public entrypoint. */ + +void +diagnostic_message_buffer_dump (const diagnostic_message_buffer *msg_buf, + FILE *outf) +{ + FAIL_IF_NULL (msg_buf); + FAIL_IF_NULL (outf); + + msg_buf->m_tokens.dump (outf); +} + +/* Public entrypoint. */ + +void +diagnostic_finish_via_msg_buf (diagnostic *diag, + diagnostic_message_buffer *msg_buf) +{ + FAIL_IF_NULL (diag); + FAIL_IF_NULL (msg_buf); + + if (const char *tool_name + = diag->get_manager ().get_client_version_info ()->m_name.get_str ()) + progname = tool_name; + else + progname = "progname"; + auto_diagnostic_group d; + diag->get_manager ().emit_msg_buf (*diag, *msg_buf); + delete diag; + delete msg_buf; +} + +/* Public entrypoint. */ + +void +diagnostic_add_location_with_label_via_msg_buf (diagnostic *diag, + const diagnostic_physical_location *loc, + diagnostic_message_buffer *msg_buf) +{ + FAIL_IF_NULL (diag); + diag->get_manager ().assert_valid_diagnostic_physical_location (loc); + FAIL_IF_NULL (msg_buf); + + std::unique_ptr<diagnostic_message_buffer> msg_buf_up (msg_buf); + diag->add_location_with_label (loc, std::move (msg_buf_up)); +} + +/* Public entrypoint. */ + +diagnostic_event_id +diagnostic_execution_path_add_event_via_msg_buf (diagnostic_execution_path *path, + const diagnostic_physical_location *physical_loc, + const diagnostic_logical_location *logical_loc, + unsigned stack_depth, + diagnostic_message_buffer *msg_buf) +{ + FAIL_IF_NULL (path); + FAIL_IF_NULL (msg_buf); + + std::unique_ptr<diagnostic_message_buffer> msg_buf_up (msg_buf); + diagnostic_event_id_t result + = path->add_event_via_msg_buf (physical_loc, + logical_loc, + stack_depth, + nullptr, + std::move (msg_buf_up)); + return as_diagnostic_event_id (result); +} + +/* Public entrypoint. */ + +void +diagnostic_graph_set_description_via_msg_buf (diagnostic_graph *graph, + diagnostic_message_buffer *desc) +{ + FAIL_IF_NULL (graph); + + if (desc) + graph->set_description (desc->to_string ()); + else + graph->set_description (nullptr); +} + +/* Public entrypoint. */ + +diagnostic_edge * +diagnostic_graph_add_edge_via_msg_buf (diagnostic_graph *graph, + const char *edge_id, + diagnostic_node *src_node, + diagnostic_node *dst_node, + diagnostic_message_buffer *label) +{ + FAIL_IF_NULL (graph); + FAIL_IF_NULL (src_node); + FAIL_IF_NULL (dst_node); + + if (label) + { + std::string label_str (label->to_string ()); + return graph->add_edge_with_label (edge_id, *src_node, *dst_node, + label_str.c_str ()); + } + else + return graph->add_edge_with_label (edge_id, *src_node, *dst_node, + nullptr); +} + +/* Public entrypoint. */ + +void +diagnostic_node_set_label_via_msg_buf (diagnostic_node *node, + diagnostic_message_buffer *label) +{ + FAIL_IF_NULL (node); + + if (label) + node->set_label (label->to_string ()); + else + node->set_label (nullptr); +} + +/* Private entrypoint. */ + +diagnostic_event_id +private_diagnostic_execution_path_add_event_3 (diagnostic_execution_path *path, + const diagnostic_physical_location *physical_loc, + const diagnostic_logical_location *logical_loc, + unsigned stack_depth, + diagnostic_graph *state_graph, + diagnostic_message_buffer *msg_buf) +{ + FAIL_IF_NULL (path); + FAIL_IF_NULL (msg_buf); + + diagnostic_event_id_t result + = path->add_event_via_msg_buf + (physical_loc, + logical_loc, + stack_depth, + std::unique_ptr <diagnostic_graph> (state_graph), + std::unique_ptr <diagnostic_message_buffer> (msg_buf)); + + return as_diagnostic_event_id (result); +} diff --git a/gcc/libgdiagnostics.h b/gcc/libgdiagnostics.h index f79790a6cf7f..c202feb1cae3 100644 --- a/gcc/libgdiagnostics.h +++ b/gcc/libgdiagnostics.h @@ -50,6 +50,13 @@ extern "C" { #define LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL(ARG_NUM) /* empty; for the human reader */ +# if (LIBGDIAGNOSTICS_GCC_VERSION >= 4001) +# define LIBGDIAGNOSTICS_PARAM_FORMAT_STRING(FMT_KIND, FMT_ARG_NUM, ARGS_ARG_NUM) \ + __attribute__ ((__format__ (FMT_KIND, FMT_ARG_NUM, ARGS_ARG_NUM))) +# else +# define LIBGDIAGNOSTICS_PARAM_FORMAT_STRING(FMT_KIND, FMT_ARG_NUM, ARGS_ARG_NUM) +# endif /* GNUC >= 4.1 */ + #define LIBGDIAGNOSTICS_PARAM_GCC_FORMAT_STRING(FMT_ARG_NUM, ARGS_ARG_NUM) \ LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (FMT_ARG_NUM) /* In theory we'd also add @@ -59,6 +66,10 @@ extern "C" { of -Wall but undocumented, and much fussier than I'd want to inflict on users of libgdiagnostics. */ +#define LIBGDIAGNOSTICS_PARAM_PRINTF_FORMAT_STRING(FMT_ARG_NUM, ARGS_ARG_NUM) \ + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (FMT_ARG_NUM) \ + LIBGDIAGNOSTICS_PARAM_FORMAT_STRING(gnu_printf, FMT_ARG_NUM, ARGS_ARG_NUM) + /********************************************************************** Data structures and types. All structs within the API are opaque. @@ -230,6 +241,8 @@ enum diagnostic_level typedef struct diagnostic_execution_path diagnostic_execution_path; typedef int diagnostic_event_id; +typedef struct diagnostic_message_buffer diagnostic_message_buffer; + /********************************************************************** API entrypoints. **********************************************************************/ @@ -899,6 +912,209 @@ diagnostic_node_set_logical_location (diagnostic_node *node, LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (2); +/* Message buffers. */ + +#define LIBDIAGNOSTICS_HAVE_diagnostic_message_buffer + +/* Create a new diagnostic_message_buffer. + Added in LIBGDIAGNOSTICS_ABI_4. */ + +extern diagnostic_message_buffer * +diagnostic_message_buffer_new (void); + +/* Release a diagnostic_message_buffer that hasn't been used. + Added in LIBGDIAGNOSTICS_ABI_4. */ + +extern void +diagnostic_message_buffer_release (diagnostic_message_buffer *msg_buf) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1); + +/* Append a UTF-8 encoded null-terminated string to the buffer. + Added in LIBGDIAGNOSTICS_ABI_4. */ + +extern void +diagnostic_message_buffer_append_str (diagnostic_message_buffer *msg_buf, + const char *p) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2); + +/* Append a UTF-8 encoded run of bytes to the buffer. + Added in LIBGDIAGNOSTICS_ABI_4. */ + +extern void +diagnostic_message_buffer_append_text (diagnostic_message_buffer *msg_buf, + const char *p, + size_t len) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2); + +/* Append a byte to to the buffer. This should be either + ASCII, or part of UTF-8 encoded text. + Added in LIBGDIAGNOSTICS_ABI_4. */ + +extern void +diagnostic_message_buffer_append_byte (diagnostic_message_buffer *msg_buf, + char ch) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1); + +/* Append a formatted string to the buffer, using the formatting rules + for "printf". + The string is assumed to be UTF-8 encoded. + Added in LIBGDIAGNOSTICS_ABI_4. */ + +extern void +diagnostic_message_buffer_append_printf (diagnostic_message_buffer *msg_buf, + const char *fmt, ...) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBGDIAGNOSTICS_PARAM_PRINTF_FORMAT_STRING (2, 3); + +/* Append a diagnostic_event_id to the buffer in the form "(1)". + Added in LIBGDIAGNOSTICS_ABI_4. */ + +extern void +diagnostic_message_buffer_append_event_id (diagnostic_message_buffer *msg_buf, + diagnostic_event_id event_id) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1); + +/* Begin a run of text associated with the given URL. + Added in LIBGDIAGNOSTICS_ABI_4. */ + +extern void +diagnostic_message_buffer_begin_url (diagnostic_message_buffer *msg_buf, + const char *url) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2); + +/* End a run of text started with diagnostic_message_buffer_begin_url. + Added in LIBGDIAGNOSTICS_ABI_4. */ + +extern void +diagnostic_message_buffer_end_url (diagnostic_message_buffer *msg_buf) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1); + +/* Begin a run of text to be printed in quotes. + Added in LIBGDIAGNOSTICS_ABI_4. */ + +extern void +diagnostic_message_buffer_begin_quote (diagnostic_message_buffer *msg_buf) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1); + +/* End a run of text started with diagnostic_message_buffer_begin_quote. + Added in LIBGDIAGNOSTICS_ABI_4. */ + +extern void +diagnostic_message_buffer_end_quote (diagnostic_message_buffer *msg_buf) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1); + +/* Begin a run of text to be printed with color. + Added in LIBGDIAGNOSTICS_ABI_4. */ + +extern void +diagnostic_message_buffer_begin_color (diagnostic_message_buffer *msg_buf, + const char *color) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2); + +/* End a run of text started with diagnostic_message_buffer_begin_color. + Added in LIBGDIAGNOSTICS_ABI_4. */ + +extern void +diagnostic_message_buffer_end_color (diagnostic_message_buffer *msg_buf) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1); + +/* Write a debugging representation of MSG_BUG to OUTF. + Added in LIBGDIAGNOSTICS_ABI_4. */ + +extern void +diagnostic_message_buffer_dump (const diagnostic_message_buffer *msg_buf, + FILE *outf) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2); + +/* As diagnostic_finish, but takes ownership of MSG_BUF. + Added in LIBGDIAGNOSTICS_ABI_4. */ + +extern void +diagnostic_finish_via_msg_buf (diagnostic *diag, + diagnostic_message_buffer *msg_buf) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2); + +/* As diagnostic_add_location_with_label but takes ownership of MSG_BUF. + Added in LIBGDIAGNOSTICS_ABI_4. */ + +extern void +diagnostic_add_location_with_label_via_msg_buf (diagnostic *diag, + const diagnostic_physical_location *loc, + diagnostic_message_buffer *msg_buf) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (2) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3); + +/* As diagnostic_execution_path_add_event but takes ownership of MSG_BUF. + Added in LIBGDIAGNOSTICS_ABI_4. */ + +extern diagnostic_event_id +diagnostic_execution_path_add_event_via_msg_buf (diagnostic_execution_path *path, + const diagnostic_physical_location *physical_loc, + const diagnostic_logical_location *logical_loc, + unsigned stack_depth, + diagnostic_message_buffer *msg_buf) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (2) + LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (3) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (5); + +/* Set the description of GRAPH for use + in the value of the SARIF "description" property + (SARIF v2.1.0 section 3.39.2). + + Takes ownership of DESC, if non-null. + + Added in LIBGDIAGNOSTICS_ABI_4. */ + +extern void +diagnostic_graph_set_description_via_msg_buf (diagnostic_graph *graph, + diagnostic_message_buffer *desc) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (2); + +/* Create and add a new edge within GRAPH. + + If non-null, then EDGE_ID must be unique within edges in GRAPH; + if EDGE_ID is null then a unique id of the form "edge0", "edge1", etc + will be used automatically. + + Takes ownership of LABEL, if non-null. + + The new edge is owned by GRAPH. + Added in LIBGDIAGNOSTICS_ABI_4. */ + +extern diagnostic_edge * +diagnostic_graph_add_edge_via_msg_buf (diagnostic_graph *graph, + const char *edge_id, + diagnostic_node *src_node, + diagnostic_node *dst_node, + diagnostic_message_buffer *label) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (2) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (4) + LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (5); + +/* Set the label of NODE for use + in the value of the SARIF "label" property + (SARIF v2.1.0 section 3.40.3). + + Takes ownership of LABEL, if non-null. + + Added in LIBGDIAGNOSTICS_ABI_4. */ + +extern void +diagnostic_node_set_label_via_msg_buf (diagnostic_node *node, + diagnostic_message_buffer *label) + LIBGDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1) + LIBGDIAGNOSTICS_PARAM_CAN_BE_NULL (2); + /* DEFERRED: - thread-safety - plural forms @@ -906,6 +1122,7 @@ diagnostic_node_set_logical_location (diagnostic_node *node, - locations within binary files - options and URLs for warnings - enable/disable of warnings by kind + - command-line arguments - plugin metadata. */ #ifdef __cplusplus diff --git a/gcc/libgdiagnostics.map b/gcc/libgdiagnostics.map index cae28d14425a..91f3951a35b4 100644 --- a/gcc/libgdiagnostics.map +++ b/gcc/libgdiagnostics.map @@ -69,6 +69,7 @@ LIBGDIAGNOSTICS_ABI_0 diagnostic_finish; diagnostic_finish_va; + diagnostic_physical_location_get_file; local: *; @@ -108,8 +109,35 @@ LIBGDIAGNOSTICS_ABI_3 { diagnostic_node_set_logical_location; # Private hooks used by sarif-replay - private_diagnostic_execution_path_add_event_2; private_diagnostic_graph_set_property_bag; private_diagnostic_node_set_property_bag; private_diagnostic_edge_set_property_bag; } LIBGDIAGNOSTICS_ABI_2; + +# Add diagnostic_message_buffer +LIBGDIAGNOSTICS_ABI_4 { + global: + diagnostic_message_buffer_new; + diagnostic_message_buffer_release; + diagnostic_message_buffer_append_str; + diagnostic_message_buffer_append_text; + diagnostic_message_buffer_append_byte; + diagnostic_message_buffer_append_printf; + diagnostic_message_buffer_append_event_id; + diagnostic_message_buffer_begin_url; + diagnostic_message_buffer_end_url; + diagnostic_message_buffer_begin_quote; + diagnostic_message_buffer_end_quote; + diagnostic_message_buffer_begin_color; + diagnostic_message_buffer_end_color; + diagnostic_message_buffer_dump; + diagnostic_finish_via_msg_buf; + diagnostic_add_location_with_label_via_msg_buf; + diagnostic_execution_path_add_event_via_msg_buf; + diagnostic_graph_set_description_via_msg_buf; + diagnostic_graph_add_edge_via_msg_buf; + diagnostic_node_set_label_via_msg_buf; + + # Private hook used by sarif-replay + private_diagnostic_execution_path_add_event_3; +} LIBGDIAGNOSTICS_ABI_3; diff --git a/gcc/libsarifreplay.cc b/gcc/libsarifreplay.cc index cad535bc5287..815869886013 100644 --- a/gcc/libsarifreplay.cc +++ b/gcc/libsarifreplay.cc @@ -282,14 +282,14 @@ class annotation { public: annotation (libgdiagnostics::physical_location phys_loc, - label_text label) + libgdiagnostics::message_buffer label) : m_phys_loc (phys_loc), m_label (std::move (label)) { } libgdiagnostics::physical_location m_phys_loc; - label_text m_label; + libgdiagnostics::message_buffer m_label; }; using id_map = std::map<std::string, const json::string *>; @@ -333,7 +333,7 @@ private: enum status emit_sarif_as_diagnostics (const json::value &jv); - label_text + libgdiagnostics::message_buffer make_plain_text_within_result_message (const json::object *tool_component_obj, const json::object &message_obj, const json::object *rule_obj); @@ -1181,12 +1181,12 @@ sarif_replayer::get_level_from_level_str (const json::string &level_str) static void add_any_annotations (libgdiagnostics::diagnostic &diag, - const std::vector<annotation> &annotations) + std::vector<annotation> &annotations) { for (auto &annotation : annotations) - if (annotation.m_label.get ()) + if (annotation.m_label.m_inner) diag.add_location_with_label (annotation.m_phys_loc, - annotation.m_label.get ()); + std::move (annotation.m_label)); else diag.add_location (annotation.m_phys_loc); } @@ -1249,13 +1249,13 @@ sarif_replayer::handle_result_obj (const json::object &result_obj, } // §3.27.11 "message" property - label_text text; + libgdiagnostics::message_buffer msg_buf; if (auto message_obj = get_optional_property<json::object> (result_obj, PROP_result_message)) - text = make_plain_text_within_result_message (nullptr, // TODO: tool_component_obj, - *message_obj, - rule_obj); - if (!text.get ()) + msg_buf = make_plain_text_within_result_message (nullptr, // TODO: tool_component_obj, + *message_obj, + rule_obj); + if (!msg_buf.m_inner) return status::err_invalid_sarif; // §3.27.12 "locations" property @@ -1368,7 +1368,8 @@ sarif_replayer::handle_result_obj (const json::object &result_obj, } // §3.27.22 relatedLocations property - std::vector<std::pair<libgdiagnostics::diagnostic, label_text>> notes; + std::vector<std::pair<libgdiagnostics::diagnostic, + libgdiagnostics::message_buffer>> notes; const property_spec_ref prop_related_locations ("result", "relatedLocations", "3.27.22"); if (auto related_locations_arr @@ -1400,18 +1401,18 @@ sarif_replayer::handle_result_obj (const json::object &result_obj, prop_message)) { /* Treat related locations with a message as a "note". */ - label_text text + libgdiagnostics::message_buffer msg_buf (make_plain_text_within_result_message (tool_component_obj, *message_obj, rule_obj)); - if (!text.get ()) + if (!msg_buf.m_inner) return status::err_invalid_sarif; auto note (m_output_mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_NOTE)); note.set_location (physical_loc); note.set_logical_location (logical_loc); add_any_annotations (note, annotations); - notes.push_back ({std::move (note), std::move (text)}); + notes.push_back ({std::move (note), std::move (msg_buf)}); } else { @@ -1434,14 +1435,14 @@ sarif_replayer::handle_result_obj (const json::object &result_obj, handle_fix_object (err, *fix_obj); } - err.finish ("%s", text.get ()); + err.finish_via_msg_buf (std::move (msg_buf)); // Flush any notes for (auto &iter : notes) { auto ¬e = iter.first; - auto &text = iter.second; - note.finish ("%s", text.get ()); + auto &msg_buf = iter.second; + note.finish_via_msg_buf (std::move (msg_buf)); } return status::ok; @@ -1569,13 +1570,10 @@ maybe_consume_embedded_link (const char *&iter_src) and substitute for any placeholders (§3.11.5) and handle any embedded links (§3.11.6). - Limitations: - - we don't preserve destinations within embedded links - MESSAGE_OBJ is "theMessage" RULE_OBJ is "theRule". */ -label_text +libgdiagnostics::message_buffer sarif_replayer:: make_plain_text_within_result_message (const json::object *tool_component_obj, const json::object &message_obj, @@ -1588,7 +1586,7 @@ make_plain_text_within_result_message (const json::object *tool_component_obj, rule_obj, js_str); if (!original_text) - return label_text::borrow (nullptr); + return libgdiagnostics::message_buffer (); gcc_assert (js_str); @@ -1598,7 +1596,7 @@ make_plain_text_within_result_message (const json::object *tool_component_obj, = get_optional_property<json::array> (message_obj, arguments_prop); /* Duplicate original_text, substituting any placeholders. */ - std::string accum; + libgdiagnostics::message_buffer result (diagnostic_message_buffer_new ()); const char *iter_src = original_text; while (char ch = *iter_src) @@ -1614,7 +1612,7 @@ make_plain_text_within_result_message (const json::object *tool_component_obj, " but message object has no %qs property", (int)arg_idx, arguments_prop.get_property_name ()); - return label_text::borrow (nullptr); + return libgdiagnostics::message_buffer (); } if (arg_idx >= arguments->length ()) { @@ -1625,20 +1623,20 @@ make_plain_text_within_result_message (const json::object *tool_component_obj, arguments_prop.get_property_name (), (int)arg_idx); // TODO: might be nice to add a note showing the args - return label_text::borrow (nullptr); + return libgdiagnostics::message_buffer (); } auto replacement_jstr = require_string (*arguments->get (arg_idx), arguments_prop); if (!replacement_jstr) - return label_text::borrow (nullptr); - accum += replacement_jstr->get_string (); + return libgdiagnostics::message_buffer (); + result += replacement_jstr->get_string (); } else if (ch == '{' || ch == '}') { /* '{' and '}' are escaped by repeating them. */ if (iter_src[1] == ch) { - accum += ch; + result += ch; iter_src += 2; } else @@ -1648,24 +1646,25 @@ make_plain_text_within_result_message (const json::object *tool_component_obj, report_invalid_sarif (*js_str, msgs_with_placeholders, "unescaped '%c' within message string", ch); - return label_text::borrow (nullptr); + return libgdiagnostics::message_buffer (); } } else if (auto link = maybe_consume_embedded_link (iter_src)) { - accum += link->text; - /* TODO: use the destination. */ + result.begin_url (link->destination.c_str ()); + result += link->text.c_str (); + result.end_url (); /* TODO: potentially could try to convert intra-sarif links into event ids. */ } else { - accum += ch; + result += ch; iter_src++; } } - return label_text::take (xstrdup (accum.c_str ())); + return result; } /* Handle a value that should be a multiformatMessageString object (§3.12). @@ -1799,7 +1798,7 @@ handle_thread_flow_location_object (const json::object &tflow_loc_obj, { libgdiagnostics::physical_location physical_loc; libgdiagnostics::logical_location logical_loc; - label_text message; + libgdiagnostics::message_buffer msg_buf; int stack_depth = 0; const property_spec_ref location_prop @@ -1821,7 +1820,7 @@ handle_thread_flow_location_object (const json::object &tflow_loc_obj, = get_optional_property<json::object> (*location_obj, location_message)) { - message = make_plain_text_within_result_message + msg_buf = make_plain_text_within_result_message (nullptr, *message_obj, nullptr/* TODO. */); @@ -1874,21 +1873,18 @@ handle_thread_flow_location_object (const json::object &tflow_loc_obj, return s; } - if (message.get ()) - private_diagnostic_execution_path_add_event_2 (path.m_inner, - physical_loc.m_inner, - logical_loc.m_inner, - stack_depth, - state_graph.m_inner, - "%s", message.get ()); - else - private_diagnostic_execution_path_add_event_2 (path.m_inner, - physical_loc.m_inner, - logical_loc.m_inner, - stack_depth, - state_graph.m_inner, - ""); + if (!msg_buf.m_inner) + msg_buf.m_inner = diagnostic_message_buffer_new (); + + private_diagnostic_execution_path_add_event_3 (path.m_inner, + physical_loc.m_inner, + logical_loc.m_inner, + stack_depth, + state_graph.m_inner, + msg_buf.m_inner); + state_graph.m_owned = false; + msg_buf.m_inner = nullptr; return status::ok; } @@ -1967,7 +1963,7 @@ handle_location_object (const json::object &location_obj, if (s != status::ok) return s; - label_text label; + libgdiagnostics::message_buffer label; // §3.30.14 message property { @@ -2304,13 +2300,13 @@ sarif_replayer::handle_graph_object (const json::object &graph_json_obj, if (auto description_obj = get_optional_property<json::object> (graph_json_obj, description_prop)) { - label_text text + auto msg_buf = make_plain_text_within_result_message (&run_obj, *description_obj, nullptr); - if (!text.get ()) + if (!msg_buf.m_inner) return status::err_invalid_sarif; - out_graph.set_description (text.get ()); + out_graph.set_description (std::move (msg_buf)); } // §3.39.3: MAY contain a "nodes" property @@ -2403,13 +2399,13 @@ sarif_replayer::handle_node_object (const json::object &node_json_obj, if (auto label_obj = get_optional_property<json::object> (node_json_obj, label_prop)) { - label_text text + auto msg_buf = make_plain_text_within_result_message (&run_obj, *label_obj, nullptr); - if (!text.get ()) + if (!msg_buf.m_inner) return nullptr; - new_node.set_label (text.get ()); + new_node.set_label (std::move (msg_buf)); } // §3.40.4 "location" property @@ -2484,7 +2480,7 @@ sarif_replayer::handle_edge_object (const json::object &edge_json_obj, edge_id_map[id] = id_str; // §3.41.3 "label" property - label_text label; + libgdiagnostics::message_buffer label; const property_spec_ref label_prop ("edge", "label", "3.41.3"); if (auto label_obj @@ -2493,7 +2489,7 @@ sarif_replayer::handle_edge_object (const json::object &edge_json_obj, label = make_plain_text_within_result_message (nullptr, *label_obj, nullptr); - if (!label.get ()) + if (!label.m_inner) return nullptr; } @@ -2513,7 +2509,7 @@ sarif_replayer::handle_edge_object (const json::object &edge_json_obj, if (!dst_node.m_inner) return nullptr; - auto result = graph.add_edge (id, src_node, dst_node, label.get ()); + auto result = graph.add_edge (id, src_node, dst_node, std::move (label)); if (auto properties = maybe_get_property_bag (edge_json_obj)) private_diagnostic_edge_set_property_bag (*result.m_inner, diff --git a/gcc/pretty-print-format-impl.h b/gcc/pretty-print-format-impl.h index cbbd21f57179..90692c80c7e8 100644 --- a/gcc/pretty-print-format-impl.h +++ b/gcc/pretty-print-format-impl.h @@ -334,6 +334,7 @@ public: push_back (std::move (tok)); } void push_back_text (label_text &&text); + void push_back_byte (char ch); void push_back (std::unique_ptr<pp_token> tok); void push_back_list (pp_token_list &&list); diff --git a/gcc/pretty-print-markup.h b/gcc/pretty-print-markup.h index 18e298cb183d..6c0719d3e922 100644 --- a/gcc/pretty-print-markup.h +++ b/gcc/pretty-print-markup.h @@ -45,6 +45,11 @@ public: void begin_highlight_color (const char *color_name); void end_highlight_color (); + void begin_url (const char *url); + void end_url (); + + void add_event_id (diagnostic_event_id_t event_id); + void push_back_any_text (); pretty_printer &m_pp; diff --git a/gcc/pretty-print.cc b/gcc/pretty-print.cc index 6ecfcb26c43c..76bbf2b8cd9a 100644 --- a/gcc/pretty-print.cc +++ b/gcc/pretty-print.cc @@ -30,6 +30,7 @@ along with GCC; see the file COPYING3. If not see #include "diagnostic-color.h" #include "diagnostic-event-id.h" #include "diagnostic-highlight-colors.h" +#include "auto-obstack.h" #include "selftest.h" #if HAVE_ICONV @@ -714,7 +715,7 @@ static int decode_utf8_char (const unsigned char *, size_t len, unsigned int *); static void pp_quoted_string (pretty_printer *, const char *, size_t = -1); -static void +extern void default_token_printer (pretty_printer *pp, const pp_token_list &tokens); @@ -1326,6 +1327,15 @@ pp_token_list::push_back_text (label_text &&text) push_back<pp_token_text> (std::move (text)); } +void +pp_token_list::push_back_byte (char ch) +{ + char buf[2]; + buf[0] = ch; + buf[1] = '\0'; + push_back_text (label_text::take (xstrdup (buf))); +} + void pp_token_list::push_back (std::unique_ptr<pp_token> tok) { @@ -2177,38 +2187,6 @@ format_phase_2 (pretty_printer *pp, gcc_assert (!formatters[argno]); } -struct auto_obstack -{ - auto_obstack () - { - obstack_init (&m_obstack); - } - - ~auto_obstack () - { - obstack_free (&m_obstack, NULL); - } - - operator obstack & () { return m_obstack; } - - void grow (const void *src, size_t length) - { - obstack_grow (&m_obstack, src, length); - } - - void *object_base () const - { - return m_obstack.object_base; - } - - size_t object_size () const - { - return obstack_object_size (&m_obstack); - } - - obstack m_obstack; -}; - /* Phase 3 of formatting a message (phases 1 and 2 done by pp_format). Pop a pp_formatted_chunks from chunk_obstack, collecting all the tokens from @@ -2261,7 +2239,7 @@ pp_output_formatted_text (pretty_printer *pp, /* Default implementation of token printing. */ -static void +void default_token_printer (pretty_printer *pp, const pp_token_list &tokens) { @@ -3188,6 +3166,29 @@ pp_markup::context::end_highlight_color () m_formatted_token_list->push_back<pp_token_end_color> (); } +void +pp_markup::context::begin_url (const char *url) +{ + push_back_any_text (); + m_formatted_token_list->push_back<pp_token_begin_url> + (label_text::take (xstrdup (url))); +} + +void +pp_markup::context::end_url () +{ + push_back_any_text (); + m_formatted_token_list->push_back<pp_token_end_url> (); +} + +void +pp_markup::context::add_event_id (diagnostic_event_id_t event_id) +{ + gcc_assert (event_id.known_p ()); + push_back_any_text (); + m_formatted_token_list->push_back<pp_token_event_id> (event_id); +} + void pp_markup::context::push_back_any_text () { diff --git a/gcc/testsuite/libgdiagnostics.dg/sarif.py b/gcc/testsuite/libgdiagnostics.dg/sarif.py deleted file mode 100644 index 7daf35b58190..000000000000 --- a/gcc/testsuite/libgdiagnostics.dg/sarif.py +++ /dev/null @@ -1,23 +0,0 @@ -import json -import os - -def sarif_from_env(): - # return parsed JSON content a SARIF_PATH file - json_filename = os.environ['SARIF_PATH'] - json_filename += '.sarif' - print('json_filename: %r' % json_filename) - with open(json_filename) as f: - json_data = f.read() - return json.loads(json_data) - -def get_location_artifact_uri(location): - return location['physicalLocation']['artifactLocation']['uri'] - -def get_location_physical_region(location): - return location['physicalLocation']['region'] - -def get_location_snippet_text(location): - return location['physicalLocation']['contextRegion']['snippet']['text'] - -def get_location_relationships(location): - return location['relationships'] diff --git a/gcc/testsuite/libgdiagnostics.dg/test-message-buffer-c.py b/gcc/testsuite/libgdiagnostics.dg/test-message-buffer-c.py new file mode 100644 index 000000000000..9d14b9a7bda9 --- /dev/null +++ b/gcc/testsuite/libgdiagnostics.dg/test-message-buffer-c.py @@ -0,0 +1,12 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +def test_message_in_generated_sarif(sarif): + result = get_result_by_index(sarif, 0) + assert result['level'] == 'error' + assert result['message']['text'] == "this is a string; foo; int: 42 str: mostly harmless; [this is a link](https://example.com/) 'this is quoted' highlight A highlight B (1)." diff --git a/gcc/testsuite/libgdiagnostics.dg/test-message-buffer.c b/gcc/testsuite/libgdiagnostics.dg/test-message-buffer.c new file mode 100644 index 000000000000..a958fc577036 --- /dev/null +++ b/gcc/testsuite/libgdiagnostics.dg/test-message-buffer.c @@ -0,0 +1,80 @@ +/* Example of using a message buffer to build the text of a diagnostic + in pieces before emitting it. */ + +#include "libgdiagnostics.h" +#include "test-helpers.h" + +int +main () +{ + begin_test ("test-message-buffer.c.exe", + "test-message-buffer.c.sarif", + __FILE__, "c"); + + diagnostic_event_id event_id = 0; + + /* begin quoted source */ + diagnostic *d = diagnostic_begin (diag_mgr, + DIAGNOSTIC_LEVEL_ERROR); + + diagnostic_message_buffer *msg_buf = diagnostic_message_buffer_new (); + + /* Add a null-terminated string. */ + diagnostic_message_buffer_append_str (msg_buf, "this is a string; "); + + /* Add a length-specified string. */ + diagnostic_message_buffer_append_text (msg_buf, "foobar", 3); + + /* "printf"-formatting. */ + diagnostic_message_buffer_append_printf (msg_buf, + "; int: %i str: %s; ", + 42, "mostly harmless"); + + /* Adding a URL. */ + diagnostic_message_buffer_begin_url (msg_buf, "https://example.com/"); + diagnostic_message_buffer_append_str (msg_buf, "this is a link"); + diagnostic_message_buffer_end_url (msg_buf); + + diagnostic_message_buffer_append_str (msg_buf, " "); + + /* Add quoted text. */ + diagnostic_message_buffer_begin_quote (msg_buf); + diagnostic_message_buffer_append_str (msg_buf, "this is quoted"); + diagnostic_message_buffer_end_quote (msg_buf); + + diagnostic_message_buffer_append_str (msg_buf, " "); + + /* Add colorized text. */ + diagnostic_message_buffer_begin_color (msg_buf, "highlight-a"); + diagnostic_message_buffer_append_str (msg_buf, "highlight A"); + diagnostic_message_buffer_end_color (msg_buf); + + diagnostic_message_buffer_append_str (msg_buf, " "); + + diagnostic_message_buffer_begin_color (msg_buf, "highlight-b"); + diagnostic_message_buffer_append_str (msg_buf, "highlight B"); + diagnostic_message_buffer_end_color (msg_buf); + + diagnostic_message_buffer_append_str (msg_buf, " "); + + /* Add an event ID. This will be printed as "(1)". */ + diagnostic_message_buffer_append_event_id (msg_buf, event_id); + + /* Add an ASCII char. */ + diagnostic_message_buffer_append_byte (msg_buf, '.'); + + diagnostic_finish_via_msg_buf (d, msg_buf); + /* end quoted source */ + + return end_test (); +}; + +/* Verify the output from the text sink. + { dg-regexp "test-message-buffer.c.exe: error: this is a string; foo; int: 42 str: mostly harmless; this is a link 'this is quoted' highlight A highlight B \\(1\\)." } */ + +/* Verify that some JSON was written to a file with the expected name: + { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest test-message-buffer.c "test-message-buffer-c.py" } } */ diff --git a/gcc/testsuite/libgdiagnostics.dg/test-warning-with-path-c.py b/gcc/testsuite/libgdiagnostics.dg/test-warning-with-path-c.py index af1e7b980fa9..61ccb93336a9 100644 --- a/gcc/testsuite/libgdiagnostics.dg/test-warning-with-path-c.py +++ b/gcc/testsuite/libgdiagnostics.dg/test-warning-with-path-c.py @@ -101,7 +101,7 @@ def test_sarif_output_for_warning_with_path(sarif): == ' PyList_Append(list, item);\n' assert tfl_2_loc['logicalLocations'] == location['logicalLocations'] assert tfl_2_loc['message']['text'] \ - == "when calling 'PyList_Append', passing NULL from (1) as argument 1" + == "when calling 'PyList_Append', passing NULL from [(1)](sarif:/runs/0/results/0/codeFlows/0/threadFlows/0/locations/0) as argument 1" assert tfl_2['nestingLevel'] == 0 assert tfl_2['executionOrder'] == 3 diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/3.11.6-embedded-links.sarif b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/3.11.6-embedded-links.sarif index bc64521716c6..cd7b8228742f 100644 --- a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/3.11.6-embedded-links.sarif +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/3.11.6-embedded-links.sarif @@ -1,3 +1,6 @@ +/* { dg-additional-options "-fdiagnostics-add-output=experimental-html:file=3.11.6-embedded-links.sarif.html,javascript=no" } */ +/* { dg-additional-options "-fdiagnostics-add-output=sarif:file=3.11.6-embedded-links.sarif.roundtrip.sarif" } */ + {"$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json", "version": "2.1.0", "runs": [{"tool": {"driver": {"name": "hand-written"}}, @@ -16,10 +19,24 @@ hand-written: warning: 002: Prohibited term used in [para\[0\]\\spans\[2\](1). /* With the fix from https://github.com/oasis-tcs/sarif-spec/issues/656 */ {"message": {"text": "003: Prohibited term used in [para\\[0\\]\\\\spans\\[2\\]](1)."}, - "locations": []} + "locations": []}, /* { dg-begin-multiline-output "" } hand-written: warning: 003: Prohibited term used in para[0]\spans[2]. + { dg-end-multiline-output "" } */ + + {"message": {"text": "004: This is a [link](http://www.example.com)."}, + "locations": []} +/* { dg-begin-multiline-output "" } +hand-written: warning: 004: This is a link. { dg-end-multiline-output "" } */ ]}]} +/* Use a Python script to verify various properties about the generated + .html file: + { dg-final { run-html-pytest 3.11.6-embedded-links.sarif "2.1.0-valid/embedded-links-check-html.py" } } */ + +/* Use a Python script to verify various properties about the *generated* + .sarif file: + { dg-final { run-sarif-pytest 3.11.6-embedded-links.sarif.roundtrip "2.1.0-valid/embedded-links-check-sarif-roundtrip.py" } } */ + diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/embedded-links-check-html.py b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/embedded-links-check-html.py new file mode 100644 index 000000000000..ff1c2f261853 --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/embedded-links-check-html.py @@ -0,0 +1,28 @@ +from htmltest import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def html_tree(): + return html_tree_from_env() + +def test_generated_html(html_tree): + root = html_tree.getroot () + assert root.tag == make_tag('html') + + head = root.find('xhtml:head', ns) + assert head is not None + + # Get "warning: 004: This is a link." + diag = get_diag_by_index(html_tree, 3) + + msg = get_message_within_diag(diag) + assert msg is not None + + assert_tag(msg[0], 'strong') + assert msg[0].text == 'warning: ' + assert msg[0].tail == ' 004: This is a ' + assert_tag(msg[1], 'a') + assert msg[1].text == 'link' + assert msg[1].get('href') == 'http://www.example.com' + assert msg[1].tail == '. ' diff --git a/gcc/testsuite/sarif-replay.dg/2.1.0-valid/embedded-links-check-sarif-roundtrip.py b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/embedded-links-check-sarif-roundtrip.py new file mode 100644 index 000000000000..171339e37571 --- /dev/null +++ b/gcc/testsuite/sarif-replay.dg/2.1.0-valid/embedded-links-check-sarif-roundtrip.py @@ -0,0 +1,13 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +def test_roundtrip_of_url_in_generated_sarif(sarif): + # Get "warning: 004: This is a link." + result = get_result_by_index(sarif, 3) + assert result['level'] == 'warning' + assert result['message']['text'] == "004: This is a [link](http://www.example.com)." -- 2.26.3