public inbox for binutils@sourceware.org
 help / color / mirror / Atom feed
* [PATCH/RFC] libdiagnostics: a shared library for emitting diagnostics
@ 2023-11-06 22:29 David Malcolm
  2023-11-06 22:29 ` [PATCH 1/2] libdiagnostics: header and examples David Malcolm
                   ` (3 more replies)
  0 siblings, 4 replies; 29+ messages in thread
From: David Malcolm @ 2023-11-06 22:29 UTC (permalink / raw)
  To: gcc-patches, binutils; +Cc: Nick Clifton, Simon Sobisch, David Malcolm

It's fairly easy for tools to implement simple diagnostics
via fprintf of 
  FILE:LINE:COLUMN: error: message
to stderr, but as diagnostics get more featureful, using a shared
library makes sense.

This patch kit extends GCC to add a new "libdiagnostics" shared library
on the host, built around GCC's existing diagnostic-handling code, exposed
via a pure C API intended for client code that wants to emit GCC-style
diagnostics. It implements:
- quoting pertinent source code (with a cache)
- underlining points and ranges in the source code, possibly with labels
- emitting fix-it hints
- generating patches from fix-it hints
- SARIF output

The first patch (for GCC) shows libdiagnostic.h (the public header
file), along with examples of simple self-contained programs that
show various uses of the API.

The second patch (for GCC) is the work-in-progress implementation.

The third patch (for binutils) is an experiment at using the API
with gas.

Status: this is a rough prototype.  I'm posting it now to get
feedback, both from GCC developers, and from projects that might make
use of this library (binutils? GNU Cobol? others?).  The header
file has a "TODO" list at the end listing various known unfinished
aspects, and "known unknowns".

Thoughts?


David Malcolm (2):
  libdiagnostics: header and examples
  libdiagnostics: work-in-progress implementation

 gcc/Makefile.in                               |  134 +-
 gcc/configure                                 |    2 +-
 gcc/configure.ac                              |    2 +-
 gcc/input.h                                   |    2 +-
 gcc/libdiagnostics.cc                         | 1124 +++++++++++++++++
 gcc/libdiagnostics.h                          |  544 ++++++++
 gcc/libdiagnostics.map                        |   57 +
 .../libdiagnostics.dg/libdiagnostics.exp      |  544 ++++++++
 .../libdiagnostics.dg/test-error-with-note.c  |   57 +
 gcc/testsuite/libdiagnostics.dg/test-error.c  |   49 +
 .../libdiagnostics.dg/test-fix-it-hint.c      |   48 +
 .../libdiagnostics.dg/test-helpers.h          |   29 +
 .../libdiagnostics.dg/test-labelled-ranges.c  |   52 +
 .../libdiagnostics.dg/test-logical-location.c |   62 +
 .../libdiagnostics.dg/test-metadata.c         |   53 +
 .../libdiagnostics.dg/test-multiple-lines.c   |   58 +
 .../test-note-with-fix-it-hint.c              |   51 +
 .../libdiagnostics.dg/test-warning.c          |   52 +
 .../test-write-sarif-to-file.c                |   46 +
 .../test-write-text-to-file.c                 |   47 +
 20 files changed, 3008 insertions(+), 5 deletions(-)
 create mode 100644 gcc/libdiagnostics.cc
 create mode 100644 gcc/libdiagnostics.h
 create mode 100644 gcc/libdiagnostics.map
 create mode 100644 gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error-with-note.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-helpers.h
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-logical-location.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-metadata.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-multiple-lines.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-warning.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-write-text-to-file.c

-- 
2.26.3


^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH 1/2] libdiagnostics: header and examples
  2023-11-06 22:29 [PATCH/RFC] libdiagnostics: a shared library for emitting diagnostics David Malcolm
@ 2023-11-06 22:29 ` David Malcolm
  2023-11-06 22:29 ` [PATCH 2/2] libdiagnostics: work-in-progress implementation David Malcolm
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 29+ messages in thread
From: David Malcolm @ 2023-11-06 22:29 UTC (permalink / raw)
  To: gcc-patches, binutils; +Cc: Nick Clifton, Simon Sobisch, David Malcolm

Here's a work-in-progress patch for GCC that adds a libdiagnostics.h
header describing the public interface, along with various testcases
that show usage examples for the API.  Various aspects of this need
work; posting now for early feedback on overall direction.

How does the interface look?

gcc/ChangeLog:
	* libdiagnostics.h: New file.

gcc/testsuite/ChangeLog:
	* libdiagnostics.dg/test-error-with-note.c: New test.
	* libdiagnostics.dg/test-error.c: New test.
	* libdiagnostics.dg/test-fix-it-hint.c: New test.
	* libdiagnostics.dg/test-helpers.h: New.
	* libdiagnostics.dg/test-labelled-ranges.c: New test.
	* libdiagnostics.dg/test-logical-location.c: New test.
	* libdiagnostics.dg/test-metadata.c: New test.
	* libdiagnostics.dg/test-multiple-lines.c: New test.
	* libdiagnostics.dg/test-note-with-fix-it-hint.c: New test.
	* libdiagnostics.dg/test-warning.c: New test.
	* libdiagnostics.dg/test-write-sarif-to-file.c: New test.
	* libdiagnostics.dg/test-write-text-to-file.c: New test.
---
 gcc/libdiagnostics.h                          | 544 ++++++++++++++++++
 .../libdiagnostics.dg/test-error-with-note.c  |  57 ++
 gcc/testsuite/libdiagnostics.dg/test-error.c  |  49 ++
 .../libdiagnostics.dg/test-fix-it-hint.c      |  48 ++
 .../libdiagnostics.dg/test-helpers.h          |  29 +
 .../libdiagnostics.dg/test-labelled-ranges.c  |  52 ++
 .../libdiagnostics.dg/test-logical-location.c |  62 ++
 .../libdiagnostics.dg/test-metadata.c         |  53 ++
 .../libdiagnostics.dg/test-multiple-lines.c   |  58 ++
 .../test-note-with-fix-it-hint.c              |  51 ++
 .../libdiagnostics.dg/test-warning.c          |  52 ++
 .../test-write-sarif-to-file.c                |  46 ++
 .../test-write-text-to-file.c                 |  47 ++
 13 files changed, 1148 insertions(+)
 create mode 100644 gcc/libdiagnostics.h
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error-with-note.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-helpers.h
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-logical-location.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-metadata.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-multiple-lines.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-warning.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-write-text-to-file.c

diff --git a/gcc/libdiagnostics.h b/gcc/libdiagnostics.h
new file mode 100644
index 00000000000..672594598fa
--- /dev/null
+++ b/gcc/libdiagnostics.h
@@ -0,0 +1,544 @@
+/* A pure C API for emitting diagnostics.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+
+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 LIBDIAGNOSTICS_H
+#define LIBDIAGNOSTICS_H
+
+/* We use FILE * for streams */
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**********************************************************************
+ Macros for attributes.
+ These are all currently empty, and thus for the human reader rather than
+ the compiler.
+ **********************************************************************/
+
+#define LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL(ARG_NUM)
+
+#define LIBDIAGNOSTICS_PARAM_CAN_BE_NULL(ARG_NUM)
+
+#define LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING(FMT_ARG_NUM, ARGS_ARG_NUM)
+
+
+/**********************************************************************
+ Data structures and types.
+ All structs within the API are opaque.
+ **********************************************************************/
+
+/* An opaque bundle of state for a client of the library.
+   Has zero of more "sinks" to which diagnostics are emitted.
+   Responsibilities:
+   - location-management
+   - caching of source file content
+   - patch generation.  */
+typedef struct diagnostic_manager diagnostic_manager;
+
+/* Types relating to diagnostic output sinks.  */
+
+/* An enum for determining if we should colorize a text output sink.  */
+enum diagnostic_colorize
+{
+  DIAGNOSTIC_COLORIZE_IF_TTY,
+  DIAGNOSTIC_COLORIZE_NO,
+  DIAGNOSTIC_COLORIZE_YES
+};
+
+/* An enum for choosing the SARIF version for a SARIF output sink.
+   Eventually the SARIF output may support multiple SARIF versions.  */
+
+enum diagnostic_sarif_version
+{
+  DIAGNOSTIC_SARIF_VERSION_2_1_0
+};
+
+/* Types relating to "physical" source locations i.e. locations within
+   specific files expressed via line/column.  */
+
+/* Opaque type describing a particular input file.  */
+typedef struct diagnostic_file diagnostic_file;
+
+/* An opaque key into a database of source locations within
+   a diagnostic_manager.  Locations are created by various API calls into
+   the diagnostic_manager expressing source code points and ranges.  They
+   persist until the diagnostic_manager is released, which cleans them
+   up.
+
+   The value 0 means "UNKNOWN", and can be returned by the manager as a
+   fallback when a problem occurs (e.g. too many locations).
+
+   A diagnostic_location_t location can be a single point within the source
+   code, such as here (at the the '"' at the start of the string literal):
+
+     int i = "foo";
+             ^
+
+   or be a range with a start and finish, and a "caret" location.
+
+      a = (foo && bar)
+          ~~~~~^~~~~~~
+   where the caret here is at the first "&", and the start and finish
+   are at the parentheses.  */
+
+typedef unsigned int diagnostic_location_t;
+
+/* Types for storing line and column information in text files.
+
+   Both libdiagnostics and emacs number source *lines* starting at 1, but
+   they have differing conventions for *columns*.
+
+   libdiagnostics uses a 1-based convention for source columns,
+   whereas Emacs's M-x column-number-mode uses a 0-based convention.
+
+   For example, an error in the initial, left-hand
+   column of source line 3 is reported by libdiagnostics as:
+
+      some-file.c:3:1: error: ...etc...
+
+   On navigating to the location of that error in Emacs
+   (e.g. via "next-error"),
+   the locus is reported in the Mode Line
+   (assuming M-x column-number-mode) as:
+
+     some-file.c   10%   (3, 0)
+
+   i.e. "3:1:" in libdiagnostics corresponds to "(3, 0)" in Emacs.  */
+
+typedef unsigned int diagnostic_line_num_t;
+typedef unsigned int diagnostic_column_num_t;
+
+/* An opaque type describing a "logical" source location
+   e.g. "within function 'foo'".  */
+
+typedef struct diagnostic_logical_location diagnostic_logical_location;
+
+/* An enum for discriminating between different kinds of logical location
+   for a diagnostic.
+
+   Roughly corresponds to logicalLocation's "kind" property in SARIF v2.1.0
+   (section 3.33.7).  */
+
+enum diagnostic_logical_location_kind_t
+{
+  DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION,
+  DIAGNOSTIC_LOGICAL_LOCATION_KIND_MEMBER,
+  DIAGNOSTIC_LOGICAL_LOCATION_KIND_MODULE,
+  DIAGNOSTIC_LOGICAL_LOCATION_KIND_NAMESPACE,
+  DIAGNOSTIC_LOGICAL_LOCATION_KIND_TYPE,
+  DIAGNOSTIC_LOGICAL_LOCATION_KIND_RETURN_TYPE,
+  DIAGNOSTIC_LOGICAL_LOCATION_KIND_PARAMETER,
+  DIAGNOSTIC_LOGICAL_LOCATION_KIND_VARIABLE
+};
+
+/* A "diagnostic" is an opaque bundle of state for a particular
+   diagnostic that is being constructed in memory.
+
+   A diagnostic has a primary location and zero or more secondary
+   locations.  For example:
+
+      a = (foo && bar)
+          ~~~~~^~~~~~~
+
+   This diagnostic has a single diagnostic_location_t, with the caret
+   at the first "&", and the start/finish at the parentheses.
+
+   Contrast with:
+
+      a = (foo && bar)
+           ~~~ ^~ ~~~
+
+   This diagnostic has three locations
+   - The primary location (at "&&") has its caret and start location at
+   the first "&" and end at the second "&.
+   - The secondary location for "foo" has its start and finish at the "f"
+   and "o" of "foo"; the caret is not flagged for display, but is perhaps at
+   the "f" of "foo".
+   - Similarly, the other secondary location (for "bar") has its start and
+   finish at the "b" and "r" of "bar"; the caret is not flagged for
+   display, but is perhaps at the"b" of "bar".  */
+typedef struct diagnostic diagnostic;
+
+enum diagnostic_level
+{
+  DIAGNOSTIC_LEVEL_ERROR,
+  DIAGNOSTIC_LEVEL_WARNING,
+  DIAGNOSTIC_LEVEL_NOTE
+};
+
+/**********************************************************************
+ API entrypoints.
+ **********************************************************************/
+
+/* Create a new diagnostic_manager.
+   The client needs to call diagnostic_release_manager on it at some
+   point.
+   Note that no output sinks are created by default.  */
+
+extern diagnostic_manager *
+diagnostic_manager_new (void);
+
+/* Release a diagnostic_manager.
+   This will flush output to all of the output sinks, and clean up. */
+
+extern void
+diagnostic_manager_release (diagnostic_manager *)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Optional metadata about the manager.  */
+
+/* Set a string suitable for use as the value of the SARIF "name" property
+   (SARIF v2.1.0 section 3.19.8).  */
+
+extern void
+diagnostic_manager_set_tool_name (diagnostic_manager *diag_mgr,
+				  const char *value)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Set a string suitable for use as the value of the SARIF "fullName" property
+   (SARIF v2.1.0 section 3.19.9).  */
+
+extern void
+diagnostic_manager_set_full_name (diagnostic_manager *diag_mgr,
+				  const char *value)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Set a string suitable for use as the value of the SARIF "version" property
+   (SARIF v2.1.0 section 3.19.13).  */
+
+extern void
+diagnostic_manager_set_version_string (diagnostic_manager *diag_mgr,
+				       const char *value)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Set a string suitable for use as the value of the SARIF "informationUri"
+   property (SARIF v2.1.0 section 3.19.17).  */
+
+extern void
+diagnostic_manager_set_version_url (diagnostic_manager *diag_mgr,
+				    const char *value)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Destinations for diagnostics.  */
+
+/* Add a new output sink to DIAG_MGR, which writes GCC-style diagnostics
+   to DST_STREAM.
+   The output for each diagnostic is written and flushed as each
+   diagnostic is finished.  */
+
+extern void
+diagnostic_manager_add_text_sink (diagnostic_manager *diag_mgr,
+				  FILE *dst_stream,
+				  enum diagnostic_colorize colorize)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Add a new output sink to DIAG_MGR, which writes SARIF of the given
+   version to DST_STREAM.
+   The output is not written until DIAG_MGR is released.  */
+
+extern void
+diagnostic_manager_add_sarif_sink (diagnostic_manager *diag_mgr,
+				   FILE *dst_stream,
+				   enum diagnostic_sarif_version version)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Write a patch to DST_STREAM consisting of all fix-it hints
+   on all diagnostics that have been finished on DIAG_MGR.  */
+
+extern void
+diagnostic_manager_write_patch (diagnostic_manager *diag_mgr,
+				FILE *dst_stream)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Location management.  */
+
+/* Create a new diagnostic_file * for file NAME.
+
+   Repeated calls with matching NAMEs will return the
+   same object.
+
+   If SARIF_SOURCE_LANGUAGE is non-NULL, it specifies a "sourceLanguage"
+   value for the file when use when writing SARIF.
+   See SARIF v2.1.0 Appendix J for suggested values for various
+   programmming languages.  */
+
+extern const diagnostic_file *
+diagnostic_manager_new_file (diagnostic_manager *diag_mgr,
+			     const char *name,
+			     const char *sarif_source_language)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3);
+
+/* Attempt to create a diagnostic_location_t representing
+   FILENAME:LINE_NUM, with no column information
+   (thus "the whole line").  */
+
+extern diagnostic_location_t
+diagnostic_manager_new_location_from_file_and_line (diagnostic_manager *diag_mgr,
+						    const diagnostic_file *file,
+						    diagnostic_line_num_t line_num)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Attempt to create a diagnostic_location_t representing
+   FILENAME:LINE_NUM:COLUMN_NUM.  */
+
+extern diagnostic_location_t
+diagnostic_manager_new_location_from_file_line_column (diagnostic_manager *diag_mgr,
+						       const diagnostic_file *file,
+						       diagnostic_line_num_t line_num,
+						       diagnostic_column_num_t column_num)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Attempt to create a diagnostic_location_t representing a range within
+   a source file, with a highlighted "caret" location.
+
+   All must be within the same file, but they can be on different lines.
+
+   For example, consider the location of the binary expression below:
+
+     ...|__________1111111112222222
+     ...|12345678901234567890123456
+     ...|
+     521|int sum (int foo, int bar)
+     522|{
+     523|   return foo + bar;
+     ...|          ~~~~^~~~~
+     524|}
+
+   The location's caret is at the "+", line 523 column 15, but starts
+   earlier, at the "f" of "foo" at column 11.  The finish is at the "r"
+   of "bar" at column 19.  */
+
+extern diagnostic_location_t
+diagnostic_manager_new_location_from_range (diagnostic_manager *diag_mgr,
+					    diagnostic_location_t loc_caret,
+					    diagnostic_location_t loc_start,
+					    diagnostic_location_t loc_end)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+// TODO
+extern void
+diagnostic_debug_dump_file (const diagnostic_file *file,
+			    FILE *out)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+// TODO
+extern void
+diagnostic_debug_dump_location (const diagnostic_manager *diag_mgr,
+				diagnostic_location_t loc,
+				FILE *out)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+/* A bundle of state describing a logical location in the user's source,
+   such as "in function 'foo'".
+
+   SHORT_NAME can be NULL, or else a string suitable for use by
+   the SARIF logicalLocation "name" property (SARIF v2.1.0 section 3.33.4).
+
+   FULLY_QUALIFIED_NAME can be NULL or else a string  suitable for use by
+   the SARIF logicalLocation "fullyQualifiedName" property
+   (SARIF v2.1.0 section 3.33.5).
+
+   DECORATED_NAME can be NULL or else a string  suitable for use by
+   the SARIF logicalLocation "decoratedName" property
+   (SARIF v2.1.0 section 3.33.6).  */
+
+extern const diagnostic_logical_location *
+diagnostic_manager_new_logical_location (diagnostic_manager *diag_mgr,
+					 enum diagnostic_logical_location_kind_t kind,
+					 const diagnostic_logical_location *parent,
+					 const char *short_name,
+					 const char *fully_qualified_name,
+					 const char *decorated_name)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (4)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (5)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (6);
+
+/* Diagnostic groups.  */
+
+// FIXME: docs
+
+extern void
+diagnostic_manager_begin_group (diagnostic_manager *diag_mgr)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+extern void
+diagnostic_manager_end_group (diagnostic_manager *diag_mgr)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Step-by-step creation of a diagnostic.  */
+
+extern diagnostic *
+diagnostic_begin (diagnostic_manager *diag_mgr,
+		  enum diagnostic_level level)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+#if 0
+// FIXME: TODO
+extern void
+diagnostic_set_option (diagnostic *diag,
+		       const char *option,
+		       const char *url);
+#endif
+
+/* Associate this diagnostic with the given ID within
+   the Common Weakness Enumeration.  */
+
+extern void
+diagnostic_set_cwe (diagnostic *diag,
+		    unsigned cwe_id)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Associate this diagnostic with a particular rule that has been violated
+   (such as in a coding standard, or within a specification).
+   The rule must have at least one of a title and a URL, but these
+   can be NULL.
+   A diagnostic can be associated with zero or more rules.  */
+
+extern void
+diagnostic_add_rule (diagnostic *diag,
+		     const char *title,
+		     const char *url)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3);
+
+/* Set the primary location of DIAG.  */
+
+extern void
+diagnostic_set_location (diagnostic *diag,
+			 diagnostic_location_t loc)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Set the primary location of DIAG, with a label.  */
+
+extern void
+diagnostic_set_location_with_label (diagnostic *diag,
+				    diagnostic_location_t loc,
+				    const char *fmt, ...)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+/* Add a secondary location to DIAG.  */
+
+extern void
+diagnostic_add_location (diagnostic *diag,
+			 diagnostic_location_t loc)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Add a secondary location to DIAG, with a label.  */
+
+extern void
+diagnostic_add_location_with_label (diagnostic *diag,
+				    diagnostic_location_t loc,
+				    const char *text)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+/* Set the logical location of DIAG.  */
+
+extern void
+diagnostic_set_logical_location (diagnostic *diag,
+				 const diagnostic_logical_location *logical_loc)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Fix-it hints.  */
+
+extern void
+diagnostic_add_fix_it_hint_insert_before (diagnostic *diag,
+					  diagnostic_location_t loc,
+					  const char *addition)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+extern void
+diagnostic_add_fix_it_hint_insert_after (diagnostic *diag,
+					 diagnostic_location_t loc,
+					 const char *addition)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+extern void
+diagnostic_add_fix_it_hint_replace (diagnostic *diag,
+				    diagnostic_location_t loc,
+				    const char *replacement)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+extern void
+diagnostic_add_fix_it_hint_delete (diagnostic *diag,
+				   diagnostic_location_t loc)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Emit DIAG to all sinks of its manager, and release DIAG.
+   Use FMT for the message.
+   TODO: this uses gcc's pretty-print format, which is *not* printf.
+   TODO: who is responsible for putting FMT through gettext?  */
+
+extern void
+diagnostic_finish (diagnostic *diag, const char *fmt, ...)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2)
+  LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (2, 3);
+
+// FIXME: we might want one that handles plurals
+#if 0
+extern void
+diagnostic_finish_n (diagnostic_manager *diag_mgr,
+		     int n,
+		     const char *singular_fmt,
+		     const char *plural_fmt,
+		     ...);
+#endif
+
+/* TODO:
+
+   DEFERRED:
+   - thread-safety
+   - plural forms
+   - enum about what a "column number" means (bytes, unichars, etc)
+   - locations within binary files
+   - options and URLs for warnings
+   - enable/disable of warnings by kind
+   - execution paths associated with/triggering a problem
+   - plugin metadata.  */
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif  /* LIBDIAGNOSTICS_H  */
diff --git a/gcc/testsuite/libdiagnostics.dg/test-error-with-note.c b/gcc/testsuite/libdiagnostics.dg/test-error-with-note.c
new file mode 100644
index 00000000000..6ba470331eb
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-error-with-note.c
@@ -0,0 +1,57 @@
+/* Example of emitting an error with an associated note.
+
+   Intended output is similar to:
+
+PATH/test-error-with-note.c:6: error: can't find 'foo'
+    6 | PRINT "hello world!";
+      |        ^~~~~~~~~~~~
+PATH/test-error-with-note.c:6: note: have you looked behind the couch?
+
+   along with the equivalent in SARIF.  */
+
+#include "libdiagnostics.h"
+
+/*
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_add_text_sink (diag_mgr, stderr,
+				    DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
+				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  diagnostic_location_t loc_start
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8);
+  diagnostic_location_t loc_end
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19);
+  diagnostic_location_t loc_range
+    = diagnostic_manager_new_location_from_range (diag_mgr,
+						  loc_start,
+						  loc_start,
+						  loc_end);
+
+  diagnostic_manager_begin_group (diag_mgr);
+  
+  diagnostic *err = diagnostic_begin (diag_mgr,
+				      DIAGNOSTIC_LEVEL_ERROR);
+  diagnostic_set_location (err, loc_range);
+  diagnostic_finish (err, "can't find %qs", "foo");
+
+  diagnostic *note = diagnostic_begin (diag_mgr, DIAGNOSTIC_LEVEL_NOTE);
+  diagnostic_set_location (note, loc_range);
+  diagnostic_finish (note, "have you looked behind the couch?");
+
+  diagnostic_manager_end_group (diag_mgr);
+
+  diagnostic_manager_release (diag_mgr);
+  return 0;
+};
diff --git a/gcc/testsuite/libdiagnostics.dg/test-error.c b/gcc/testsuite/libdiagnostics.dg/test-error.c
new file mode 100644
index 00000000000..72ed6ed8a7d
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-error.c
@@ -0,0 +1,49 @@
+/* Example of emitting an error.
+
+   Intended output is similar to:
+
+PATH/test-error-with-note.c:6: error: can't find 'foo'
+    6 | PRINT "hello world!";
+      |        ^~~~~~~~~~~~
+
+   along with the equivalent in SARIF.  */
+
+#include "libdiagnostics.h"
+
+/*
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_add_text_sink (diag_mgr, stderr,
+				    DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
+				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  diagnostic_location_t loc_start
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8);
+  diagnostic_location_t loc_end
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19);
+  diagnostic_location_t loc_range
+    = diagnostic_manager_new_location_from_range (diag_mgr,
+						  loc_start,
+						  loc_start,
+						  loc_end);
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_ERROR);
+  diagnostic_set_location (d, loc_range);
+  
+  diagnostic_finish (d, "can't find %qs", "foo");
+
+  diagnostic_manager_release (diag_mgr);
+  return 0;
+};
diff --git a/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.c b/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.c
new file mode 100644
index 00000000000..bc8a4256f7e
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.c
@@ -0,0 +1,48 @@
+/* Example of a fix-it hint, including patch generation.
+
+   Intended output is similar to:
+
+PATH/test-fix-it-hint.c:19: error: unknown field 'colour'; did you mean 'color'
+   19 |   return p->colour;
+      |             ^~~~~~
+      |             color
+
+   along with the equivalent in SARIF, and a generated patch (on stderr) to
+   make the change.  */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+/*
+_________11111111112
+12345678901234567890
+  return p->colour;
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_add_text_sink (diag_mgr, stderr,
+				    DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
+				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  diagnostic_location_t loc_token = make_range (diag_mgr, file, line_num, 13, 18);
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_ERROR);
+  diagnostic_set_location (d, loc_token);
+
+  diagnostic_add_fix_it_hint_replace (d, loc_token, "color");
+  
+  diagnostic_finish (d, "unknown field %qs; did you mean %qs", "colour", "color");
+
+  diagnostic_manager_write_patch (diag_mgr, stderr);
+
+  diagnostic_manager_release (diag_mgr);
+  return 0;
+}
diff --git a/gcc/testsuite/libdiagnostics.dg/test-helpers.h b/gcc/testsuite/libdiagnostics.dg/test-helpers.h
new file mode 100644
index 00000000000..1bbc1ce4364
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-helpers.h
@@ -0,0 +1,29 @@
+/* Common utility code shared between test cases.  */
+
+#ifndef TEST_HELPERS_H
+#define TEST_HELPERS_H
+
+diagnostic_location_t
+make_range (diagnostic_manager *diag_mgr,
+	    const diagnostic_file *file,
+	    diagnostic_line_num_t line_num,
+	    diagnostic_column_num_t start_column,
+	    diagnostic_column_num_t end_column)
+{
+  diagnostic_location_t loc_start
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr,
+							     file,
+							     line_num,
+							     start_column);
+  diagnostic_location_t loc_end
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr,
+							     file,
+							     line_num,
+							     end_column);
+  return diagnostic_manager_new_location_from_range (diag_mgr,
+						     loc_start,
+						     loc_start,
+						     loc_end);
+}
+
+#endif /* #ifndef TEST_HELPERS_H */
diff --git a/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.c b/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.c
new file mode 100644
index 00000000000..f48751cbc1b
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.c
@@ -0,0 +1,52 @@
+/* Example of multiple locations, with labelling of ranges.
+
+   Intended output is similar to:
+
+PATH/test-labelled-ranges.c:9: error: mismatching types: 'int' and 'const char *'
+   19 |   42 + "foo"
+      |   ~~ ^ ~~~~~
+      |   |    |
+      |   int  const char *
+
+   along with the equivalent in SARIF.  */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+/*
+_________11111111112
+12345678901234567890
+  42 + "foo"
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_add_text_sink (diag_mgr, stderr,
+				    DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
+				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  diagnostic_location_t loc_operator
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 6);
+
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_ERROR);
+  diagnostic_set_location (d, loc_operator);
+  diagnostic_add_location_with_label (d,
+				      make_range (diag_mgr, file, line_num, 3, 4),
+				      "int");
+  diagnostic_add_location_with_label (d,
+				      make_range (diag_mgr, file, line_num, 8, 12),
+				      "const char *");
+  
+  diagnostic_finish (d, "mismatching types: %qs and %qs", "int", "const char *");
+  
+  diagnostic_manager_release (diag_mgr);
+  return 0;
+}
diff --git a/gcc/testsuite/libdiagnostics.dg/test-logical-location.c b/gcc/testsuite/libdiagnostics.dg/test-logical-location.c
new file mode 100644
index 00000000000..c1a0c1a1e1d
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-logical-location.c
@@ -0,0 +1,62 @@
+/* Example of using a logical location.
+
+   Intended output is similar to:
+
+In function 'test_qualified_name':
+PATH/test-error-with-note.c:6: error: can't find 'foo'
+    6 | PRINT "hello world!";
+      |        ^~~~~~~~~~~~
+
+   FIXME: the text doesn't currently show the logical location.
+
+   along with the equivalent in SARIF.  */
+
+#include "libdiagnostics.h"
+
+/* Placeholder source:
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_add_text_sink (diag_mgr, stderr,
+				    DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
+				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  diagnostic_location_t loc_start
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8);
+  diagnostic_location_t loc_end
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19);
+  diagnostic_location_t loc_range
+    = diagnostic_manager_new_location_from_range (diag_mgr,
+					  loc_start,
+					  loc_start,
+					  loc_end);
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_ERROR);
+  diagnostic_set_location (d, loc_range);
+
+  const diagnostic_logical_location *logical_loc
+    = diagnostic_manager_new_logical_location (diag_mgr,
+					       DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION,
+					       NULL, /* parent */
+					       "test_short_name",
+					       "test_qualified_name",
+					       "test_decorated_name");
+  
+  diagnostic_set_logical_location (d, logical_loc);
+  
+  diagnostic_finish (d, "can't find %qs", "foo");
+  
+  diagnostic_manager_release (diag_mgr);
+  return 0;
+};
diff --git a/gcc/testsuite/libdiagnostics.dg/test-metadata.c b/gcc/testsuite/libdiagnostics.dg/test-metadata.c
new file mode 100644
index 00000000000..f6ffff3a030
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-metadata.c
@@ -0,0 +1,53 @@
+/* Example of setting a CWE and adding extra metadata.
+
+   Intended output is similar to:
+
+PATH/test-metadata.c:21: warning: never use 'gets' [CWE-242] [STR34-C]
+   21 |   gets (buf);
+      |   ^~~~~~~~~~
+
+   where the metadata tags are linkified in a sufficiently capable terminal,
+   along with the equivalent in SARIF.  */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+/* Placeholder source:
+_________11111111112
+12345678901234567890
+void test_cwe (void)
+{
+  char buf[1024];
+  gets (buf);
+}
+*/
+const int line_num = __LINE__ - 3;
+
+int
+main ()
+{
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_set_tool_name (diag_mgr, "FooChecker");
+  diagnostic_manager_set_full_name (diag_mgr, "FooChecker 0.1 (en_US)");
+  diagnostic_manager_set_version_string (diag_mgr, "0.1");
+  diagnostic_manager_set_version_url (diag_mgr, "https://www.example.com/0.1/");
+
+  diagnostic_manager_add_text_sink (diag_mgr, stderr,
+				    DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
+				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  diagnostic_location_t loc_token = make_range (diag_mgr, file, line_num, 3, 12);
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_WARNING);
+  diagnostic_set_location (d, loc_token);
+  diagnostic_set_cwe (d, 242); /* CWE-242: Use of Inherently Dangerous Function.  */
+  diagnostic_add_rule (d, "STR34-C", "https://example.com/");
+  
+  diagnostic_finish (d, "never use %qs", "gets");
+
+  diagnostic_manager_release (diag_mgr);
+  return 0;
+}
diff --git a/gcc/testsuite/libdiagnostics.dg/test-multiple-lines.c b/gcc/testsuite/libdiagnostics.dg/test-multiple-lines.c
new file mode 100644
index 00000000000..d5f5a795071
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-multiple-lines.c
@@ -0,0 +1,58 @@
+/* Example of a warning with multiple locations in various source lines,
+   with an insertion fix-it hint.
+
+   Intended output is similar to:
+   
+/PATH/test-multiple-lines.c:17: warning: missing comma
+   16 | const char *strs[3] = {"foo",
+      |                        ~~~~~ 
+   17 |                        "bar"
+      |                        ~~~~~^
+   18 |                        "baz"};
+      |                        ~~~~~ 
+
+   along with the equivalent in SARIF.  */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+/* Placeholder source (missing comma after "bar"):
+_________11111111112222222222
+12345678901234567890123456789
+const char *strs[3] = {"foo",
+                       "bar"
+                       "baz"};
+*/
+const int foo_line_num = __LINE__ - 4;
+
+int
+main ()
+{
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_add_text_sink (diag_mgr, stderr,
+				    DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
+				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  diagnostic_location_t loc_comma
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, foo_line_num + 1, 29);
+  diagnostic_location_t loc_foo = make_range (diag_mgr, file, foo_line_num, 24, 28);
+  diagnostic_location_t loc_bar = make_range (diag_mgr, file, foo_line_num + 1, 24, 28);
+  diagnostic_location_t loc_baz = make_range (diag_mgr, file, foo_line_num + 2, 24, 28);
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_WARNING);
+  diagnostic_set_location (d, loc_comma);
+  diagnostic_add_location (d, loc_foo);
+  diagnostic_add_location (d, loc_bar);
+  diagnostic_add_location (d, loc_baz);
+
+  diagnostic_add_fix_it_hint_insert_after (d, loc_bar, ",");
+  
+  diagnostic_finish (d, "missing comma");
+  
+  diagnostic_manager_release (diag_mgr);
+  return 0;
+};
diff --git a/gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint.c b/gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint.c
new file mode 100644
index 00000000000..3740e9456e7
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint.c
@@ -0,0 +1,51 @@
+/* Example of a grouped error and note, with a fix-it hint on the note.
+
+   Intended output is similar to:
+   
+/PATH/test-note-with-fix-it-hint.c:19: error: unknown field 'colour'
+   19 |   return p->colour;
+      |             ^~~~~~
+/PATH/test-note-with-fix-it-hint.c:19: note: did you mean 'color'
+   19 |   return p->colour;
+      |             ^~~~~~
+      |             color
+
+   along with the equivalent in SARIF.  */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+/* Placeholder source:
+_________11111111112
+12345678901234567890
+  return p->colour;
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_add_text_sink (diag_mgr, stderr, DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_manager_add_sarif_sink (diag_mgr, stderr, DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  diagnostic_location_t loc_token = make_range (diag_mgr, file, line_num, 13, 18);
+
+  diagnostic_manager_begin_group (diag_mgr);
+
+  diagnostic *err = diagnostic_begin (diag_mgr, DIAGNOSTIC_LEVEL_ERROR);
+  diagnostic_set_location (err, loc_token);
+  diagnostic_finish (err, "unknown field %qs", "colour");
+
+  diagnostic *n = diagnostic_begin (diag_mgr, DIAGNOSTIC_LEVEL_NOTE);
+  diagnostic_set_location (n, loc_token);
+  diagnostic_add_fix_it_hint_replace (n, loc_token, "color");
+  diagnostic_finish (n, "did you mean %qs", "color");
+
+  diagnostic_manager_end_group (diag_mgr);
+
+  diagnostic_manager_release (diag_mgr);
+  return 0;
+}
diff --git a/gcc/testsuite/libdiagnostics.dg/test-warning.c b/gcc/testsuite/libdiagnostics.dg/test-warning.c
new file mode 100644
index 00000000000..9492dd35cbd
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-warning.c
@@ -0,0 +1,52 @@
+/* Example of emitting a warning.
+
+   Intended output is similar to:
+   
+/PATH/test-warning.c:15: warning: this is a warning
+   15 | PRINT "hello world!";
+      |        ^~~~~~~~~~~~
+
+   along with the equivalent in SARIF.  */
+
+#include "libdiagnostics.h"
+
+/*
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_add_text_sink (diag_mgr, stderr,
+				    DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
+				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr,
+							     __FILE__,
+							     "c");
+
+  diagnostic_location_t loc_start
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8);
+  diagnostic_location_t loc_end
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19);
+  diagnostic_location_t loc_range
+    = diagnostic_manager_new_location_from_range (diag_mgr,
+						  loc_start,
+						  loc_start,
+						  loc_end);
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_WARNING);
+  diagnostic_set_location (d, loc_range);
+  
+  diagnostic_finish (d, "this is a warning");
+  
+  diagnostic_manager_release (diag_mgr);
+  return 0;
+};
diff --git a/gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file.c b/gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file.c
new file mode 100644
index 00000000000..b8dd1ae80ab
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file.c
@@ -0,0 +1,46 @@
+/* Example of writing diagnostics as SARIF to a file.  */
+
+#include "libdiagnostics.h"
+
+/*
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  FILE *outfile = fopen ("test.txt", "w");
+  if (!outfile)
+    return -1;
+
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_add_sarif_sink (diag_mgr, outfile,
+				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  diagnostic_location_t loc_start
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8);
+  diagnostic_location_t loc_end
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19);
+  diagnostic_location_t loc_range
+    = diagnostic_manager_new_location_from_range (diag_mgr,
+						  loc_start,
+						  loc_start,
+						  loc_end);
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_ERROR);
+  diagnostic_set_location (d, loc_range);
+  
+  diagnostic_finish (d, "can't find %qs", "foo");
+
+  diagnostic_manager_release (diag_mgr);
+
+  fclose (outfile);
+
+  return 0;
+};
diff --git a/gcc/testsuite/libdiagnostics.dg/test-write-text-to-file.c b/gcc/testsuite/libdiagnostics.dg/test-write-text-to-file.c
new file mode 100644
index 00000000000..44bec28c6f4
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-write-text-to-file.c
@@ -0,0 +1,47 @@
+/* Example of writing diagnostics in text form, but to a file, 
+   rather than stderr.  */
+
+#include "libdiagnostics.h"
+
+/*
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  FILE *outfile = fopen ("test.txt", "w");
+  if (!outfile)
+    return -1;
+
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_add_text_sink (diag_mgr, outfile,
+				    DIAGNOSTIC_COLORIZE_NO);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  diagnostic_location_t loc_start
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8);
+  diagnostic_location_t loc_end
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19);
+  diagnostic_location_t loc_range
+    = diagnostic_manager_new_location_from_range (diag_mgr,
+						  loc_start,
+						  loc_start,
+						  loc_end);
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_ERROR);
+  diagnostic_set_location (d, loc_range);
+  
+  diagnostic_finish (d, "can't find %qs", "foo");
+
+  diagnostic_manager_release (diag_mgr);
+
+  fclose (outfile);
+
+  return 0;
+};
-- 
2.26.3


^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH 2/2] libdiagnostics: work-in-progress implementation
  2023-11-06 22:29 [PATCH/RFC] libdiagnostics: a shared library for emitting diagnostics David Malcolm
  2023-11-06 22:29 ` [PATCH 1/2] libdiagnostics: header and examples David Malcolm
@ 2023-11-06 22:29 ` David Malcolm
  2023-11-07  7:54   ` Simon Sobisch
  2023-11-06 22:29 ` [PATCH] binutils: experimental use of libdiagnostics in gas David Malcolm
  2023-11-21 22:20 ` [PATCH 0/6] v2 of libdiagnostics David Malcolm
  3 siblings, 1 reply; 29+ messages in thread
From: David Malcolm @ 2023-11-06 22:29 UTC (permalink / raw)
  To: gcc-patches, binutils; +Cc: Nick Clifton, Simon Sobisch, David Malcolm

Here's a work-in-progress patch for GCC that adds the implementation
of libdiagnostics.  Various aspects of this need work; posting now
for early feedback on overall direction.  For example, the testsuite
doesn't yet check the output from the test client programs (and I'm
not quite sure of the best way to express that in DejaGnu).

gcc/ChangeLog:
	* Makefile.in (lang_checks): Add check-libdiagnostics.
	(start.encap): Add libdiagnostics.
	(libdiagnostics_OBJS): New.
	...plus a bunch of stuff hacked up from jit/Make-lang.in.
	* configure: Regenerate.
	* configure.ac (check_languages): Add check-libdiagnostics.
	* input.h: Add FIXME.
	* libdiagnostics.cc: New file.
	* libdiagnostics.map: New file.

gcc/testsuite/ChangeLog:
	* libdiagnostics.dg/libdiagnostics.exp: New, based on jit.exp.
---
 gcc/Makefile.in                               |  134 +-
 gcc/configure                                 |    2 +-
 gcc/configure.ac                              |    2 +-
 gcc/input.h                                   |    2 +-
 gcc/libdiagnostics.cc                         | 1124 +++++++++++++++++
 gcc/libdiagnostics.map                        |   57 +
 .../libdiagnostics.dg/libdiagnostics.exp      |  544 ++++++++
 7 files changed, 1860 insertions(+), 5 deletions(-)
 create mode 100644 gcc/libdiagnostics.cc
 create mode 100644 gcc/libdiagnostics.map
 create mode 100644 gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index ff77d3cdc64..8f93ae48024 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -611,7 +611,7 @@ host_xm_defines=@host_xm_defines@
 xm_file_list=@xm_file_list@
 xm_include_list=@xm_include_list@
 xm_defines=@xm_defines@
-lang_checks=
+lang_checks=check-libdiagnostics
 lang_checks_parallelized=
 lang_opt_files=@lang_opt_files@ $(srcdir)/c-family/c.opt $(srcdir)/common.opt $(srcdir)/params.opt $(srcdir)/analyzer/analyzer.opt
 lang_specs_files=@lang_specs_files@
@@ -2153,7 +2153,7 @@ all.cross: native gcc-cross$(exeext) cpp$(exeext) specs \
 	libgcc-support lang.all.cross doc selftest @GENINSRC@ srcextra
 # This is what must be made before installing GCC and converting libraries.
 start.encap: native xgcc$(exeext) cpp$(exeext) specs \
-	libgcc-support lang.start.encap @GENINSRC@ srcextra
+	libgcc-support lang.start.encap libdiagnostics @GENINSRC@ srcextra
 # These can't be made until after GCC can run.
 rest.encap: lang.rest.encap
 # This is what is made with the host's compiler
@@ -2242,6 +2242,136 @@ cpp$(exeext): $(GCC_OBJS) c-family/cppspec.o libcommon-target.a $(LIBDEPS) \
 	  c-family/cppspec.o $(EXTRA_GCC_OBJS) libcommon-target.a \
 	  $(EXTRA_GCC_LIBS) $(LIBS)
 
+
+libdiagnostics_OBJS = libdiagnostics.o \
+	libcommon.a
+
+# FIXME:
+# Define the names for selecting jit in LANGUAGES.
+# Note that it would be nice to move the dependency on g++
+# into the jit rule, but that needs a little bit of work
+# to do the right thing within all.cross.
+
+LIBDIAGNOSTICS_VERSION_NUM = 0
+LIBDIAGNOSTICS_MINOR_NUM = 0
+LIBDIAGNOSTICS_RELEASE_NUM = 1
+
+ifneq (,$(findstring mingw,$(target)))
+LIBDIAGNOSTICS_FILENAME = libdiagnostics-$(LIBDIAGNOSTICS_VERSION_NUM).dll
+LIBDIAGNOSTICS_IMPORT_LIB = libdiagnostics.dll.a
+
+libdiagnostics: $(LIBDIAGNOSTICS_FILENAME) \
+	$(FULL_DRIVER_NAME)
+
+else
+
+ifneq (,$(findstring darwin,$(host)))
+
+LIBDIAGNOSTICS_AGE = 1
+LIBDIAGNOSTICS_BASENAME = libdiagnostics
+
+LIBDIAGNOSTICS_SONAME = \
+  ${libdir}/$(LIBDIAGNOSTICS_BASENAME).$(LIBDIAGNOSTICS_VERSION_NUM).dylib
+LIBDIAGNOSTICS_FILENAME = $(LIBDIAGNOSTICS_BASENAME).$(LIBDIAGNOSTICS_VERSION_NUM).dylib
+LIBDIAGNOSTICS_LINKER_NAME = $(LIBDIAGNOSTICS_BASENAME).dylib
+
+# Conditionalize the use of the LD_VERSION_SCRIPT_OPTION and
+# LD_SONAME_OPTION depending if configure found them, using $(if)
+# We have to define a LIBDIAGNOSTICS_COMMA here, otherwise the commas in the "true"
+# result are treated as separators by the $(if).
+LIBDIAGNOSTICS_COMMA := ,
+LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION = \
+	$(if $(LD_VERSION_SCRIPT_OPTION),\
+	  -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_VERSION_SCRIPT_OPTION)$(LIBDIAGNOSTICS_COMMA)$(srcdir)/libdiagnostics.map)
+
+LIBDIAGNOSTICS_SONAME_OPTION = \
+	$(if $(LD_SONAME_OPTION), \
+	     -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_SONAME_OPTION)$(LIBDIAGNOSTICS_COMMA)$(LIBDIAGNOSTICS_SONAME))
+
+LIBDIAGNOSTICS_SONAME_SYMLINK = $(LIBDIAGNOSTICS_FILENAME)
+LIBDIAGNOSTICS_LINKER_NAME_SYMLINK = $(LIBDIAGNOSTICS_LINKER_NAME)
+
+libdiagnostics: $(LIBDIAGNOSTICS_FILENAME) \
+	$(LIBDIAGNOSTICS_SYMLINK) \
+	$(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK) \
+	$(FULL_DRIVER_NAME)
+
+else
+
+LIBDIAGNOSTICS_LINKER_NAME = libdiagnostics.so
+LIBDIAGNOSTICS_SONAME = $(LIBDIAGNOSTICS_LINKER_NAME).$(LIBDIAGNOSTICS_VERSION_NUM)
+LIBDIAGNOSTICS_FILENAME = \
+  $(LIBDIAGNOSTICS_SONAME).$(LIBDIAGNOSTICS_MINOR_NUM).$(LIBDIAGNOSTICS_RELEASE_NUM)
+
+LIBDIAGNOSTICS_LINKER_NAME_SYMLINK = $(LIBDIAGNOSTICS_LINKER_NAME)
+LIBDIAGNOSTICS_SONAME_SYMLINK = $(LIBDIAGNOSTICS_SONAME)
+
+# Conditionalize the use of the LD_VERSION_SCRIPT_OPTION and
+# LD_SONAME_OPTION depending if configure found them, using $(if)
+# We have to define a LIBDIAGNOSTICS_COMMA here, otherwise the commas in the "true"
+# result are treated as separators by the $(if).
+LIBDIAGNOSTICS_COMMA := ,
+LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION = \
+	$(if $(LD_VERSION_SCRIPT_OPTION),\
+	  -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_VERSION_SCRIPT_OPTION)$(LIBDIAGNOSTICS_COMMA)$(srcdir)/libdiagnostics.map)
+
+LIBDIAGNOSTICS_SONAME_OPTION = \
+	$(if $(LD_SONAME_OPTION), \
+	     -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_SONAME_OPTION)$(LIBDIAGNOSTICS_COMMA)$(LIBDIAGNOSTICS_SONAME))
+
+libdiagnostics: $(LIBDIAGNOSTICS_FILENAME) \
+	$(LIBDIAGNOSTICS_SYMLINK) \
+	$(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK) \
+	$(FULL_DRIVER_NAME)
+
+endif
+endif
+
+libdiagnostics.serial = $(LIBDIAGNOSTICS_FILENAME)
+
+# Tell GNU make to ignore these if they exist.
+.PHONY: libdiagnostics
+
+ifneq (,$(findstring mingw,$(target)))
+# Create import library
+LIBDIAGNOSTICS_EXTRA_OPTS = -Wl,--out-implib,$(LIBDIAGNOSTICS_IMPORT_LIB)
+else
+
+ifneq (,$(findstring darwin,$(host)))
+# TODO : Construct a Darwin-style symbol export file.
+LIBDIAGNOSTICS_EXTRA_OPTS = -Wl,-compatibility_version,$(LIBDIAGNOSTICS_VERSION_NUM) \
+	-Wl,-current_version,$(LIBDIAGNOSTICS_VERSION_NUM).$(LIBDIAGNOSTICS_MINOR_NUM).$(LIBDIAGNOSTICS_AGE) \
+	$(LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION) \
+	$(LIBDIAGNOSTICS_SONAME_OPTION)
+else
+
+LIBDIAGNOSTICS_EXTRA_OPTS = $(LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION) \
+	$(LIBDIAGNOSTICS_SONAME_OPTION)
+endif
+endif
+
+$(LIBDIAGNOSTICS_FILENAME): $(libdiagnostics_OBJS) $(CPPLIB) $(EXTRA_GCC_LIBS) $(LIBS) \
+	$(LIBDEPS) $(srcdir)/libdiagnostics.map
+	@$(call LINK_PROGRESS,$(INDEX.libdiagnostics),start)
+	+$(LLINKER) $(ALL_LINKERFLAGS) $(LDFLAGS) -o $@ -shared \
+	     $(libdiagnostics_OBJS) \
+	     $(CPPLIB) $(EXTRA_GCC_LIBS) $(LIBS) \
+	     $(LIBDIAGNOSTICS_EXTRA_OPTS)
+	@$(call LINK_PROGRESS,$(INDEX.libdiagnostics),end)
+
+# Create symlinks when not building for Windows
+ifeq (,$(findstring mingw,$(target)))
+
+ifeq (,$(findstring darwin,$(host)))
+# but only one level for Darwin, version info is embedded.
+$(LIBDIAGNOSTICS_SONAME_SYMLINK): $(LIBDIAGNOSTICS_FILENAME)
+	ln -sf $(LIBDIAGNOSTICS_FILENAME) $(LIBDIAGNOSTICS_SONAME_SYMLINK)
+endif
+
+$(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK): $(LIBDIAGNOSTICS_SONAME_SYMLINK)
+	ln -sf $(LIBDIAGNOSTICS_SONAME_SYMLINK) $(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK)
+endif
+
 # Dump a specs file to make -B./ read these specs over installed ones.
 $(SPECS): xgcc$(exeext)
 	$(GCC_FOR_TARGET) -dumpspecs > tmp-specs
diff --git a/gcc/configure b/gcc/configure
index 77f33ee4df6..abcb70b1b5f 100755
--- a/gcc/configure
+++ b/gcc/configure
@@ -31997,7 +31997,7 @@ $as_echo "#define ENABLE_LTO 1" >>confdefs.h
 	esac
 done
 
-check_languages=
+check_languages=check-libdiagnostics
 for language in $all_selected_languages
 do
 	check_languages="$check_languages check-$language"
diff --git a/gcc/configure.ac b/gcc/configure.ac
index 10982cdfc09..5968670b2d6 100644
--- a/gcc/configure.ac
+++ b/gcc/configure.ac
@@ -7281,7 +7281,7 @@ changequote([,])dnl
 	esac
 done
 
-check_languages=
+check_languages=check-libdiagnostics
 for language in $all_selected_languages
 do
 	check_languages="$check_languages check-$language"
diff --git a/gcc/input.h b/gcc/input.h
index 5eac1dc40a6..e4d230cc112 100644
--- a/gcc/input.h
+++ b/gcc/input.h
@@ -113,10 +113,10 @@ class char_span
   size_t m_n_elts;
 };
 
+// FIXME: eliminate these; use global_dc->m_file_cache
 extern char_span location_get_source_line (const char *file_path, int line);
 extern char *get_source_text_between (location_t, location_t);
 extern char_span get_source_file_content (const char *file_path);
-
 extern bool location_missing_trailing_newline (const char *file_path);
 
 /* Forward decl of slot within file_cache, so that the definition doesn't
diff --git a/gcc/libdiagnostics.cc b/gcc/libdiagnostics.cc
new file mode 100644
index 00000000000..788b28edc53
--- /dev/null
+++ b/gcc/libdiagnostics.cc
@@ -0,0 +1,1124 @@
+/* C++ implementation of a pure C API for emitting diagnostics.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+
+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/>.  */
+
+#include "config.h"
+#define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
+#include "system.h"
+#include "coretypes.h"
+#include "intl.h"
+#include "diagnostic.h"
+#include "diagnostic-color.h"
+#include "diagnostic-url.h"
+#include "diagnostic-metadata.h"
+#include "diagnostic-client-data-hooks.h"
+#include "logical-location.h"
+#include "edit-context.h"
+#include "make-unique.h"
+#include "libdiagnostics.h"
+
+class owned_nullable_string
+{
+public:
+  owned_nullable_string () : m_str (nullptr) {}
+  owned_nullable_string (const char *str)
+  : m_str (str ? ::xstrdup (str) : nullptr)
+  {
+  }
+
+  ~owned_nullable_string ()
+  {
+    free (m_str);
+  }
+
+  void set (const char *str)
+  {
+    free (m_str);
+    m_str = str ? ::xstrdup (str) : nullptr;
+  }
+
+  const char *get_str () const { return m_str; }
+
+  char *xstrdup () const
+  {
+    return m_str ? ::xstrdup (m_str) : nullptr;
+  }
+
+private:
+  char *m_str;
+};
+
+/* This has to be a "struct" as it is exposed in the C API.  */
+
+struct diagnostic_file
+{
+  diagnostic_file (const char *name, const char *sarif_source_language)
+  : m_name (name), m_sarif_source_language (sarif_source_language)
+  {
+  }
+
+  const char *get_name () const { return m_name.get_str (); }
+  const char *get_sarif_source_language () const
+  {
+    return m_sarif_source_language.get_str ();
+  }
+
+private:
+  owned_nullable_string m_name;
+  owned_nullable_string m_sarif_source_language;
+};
+
+/* This has to be a "struct" as it is exposed in the C API.  */
+
+struct diagnostic_logical_location : public logical_location
+{
+  diagnostic_logical_location (enum diagnostic_logical_location_kind_t kind,
+			       const diagnostic_logical_location *parent,
+			       const char *short_name,
+			       const char *fully_qualified_name,
+			       const char *decorated_name)
+  : m_kind (kind),
+    m_parent (parent),
+    m_short_name (short_name),
+    m_fully_qualified_name (fully_qualified_name),
+    m_decorated_name (decorated_name)
+  {
+  }
+
+  const char *get_short_name () const final override
+  {
+    return m_short_name.get_str ();
+  }
+  const char *get_name_with_scope () const final override
+  {
+    return m_fully_qualified_name.get_str ();
+  }
+  const char *get_internal_name () const final override
+  {
+    return m_decorated_name.get_str ();
+  }
+  enum logical_location_kind get_kind () const final override
+  {
+    switch (m_kind)
+      {
+      default:
+	gcc_unreachable ();
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION:
+	return LOGICAL_LOCATION_KIND_FUNCTION;
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_MEMBER:
+	return LOGICAL_LOCATION_KIND_MEMBER;
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_MODULE:
+	return LOGICAL_LOCATION_KIND_MODULE;
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_NAMESPACE:
+	return LOGICAL_LOCATION_KIND_NAMESPACE;
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_TYPE:
+	return LOGICAL_LOCATION_KIND_TYPE;
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_RETURN_TYPE:
+	return LOGICAL_LOCATION_KIND_RETURN_TYPE;
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_PARAMETER:
+	return LOGICAL_LOCATION_KIND_PARAMETER;
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_VARIABLE:
+	return LOGICAL_LOCATION_KIND_VARIABLE;
+      }
+  }
+
+private:
+  enum diagnostic_logical_location_kind_t m_kind;
+  const diagnostic_logical_location *m_parent;
+  owned_nullable_string m_short_name;
+  owned_nullable_string m_fully_qualified_name;
+  owned_nullable_string m_decorated_name;
+};
+
+class sink
+{
+public:
+  virtual ~sink ();
+
+  void begin_group ()
+  {
+    m_dc.begin_group ();
+  }
+  void end_group ()
+  {
+    m_dc.end_group ();
+  }
+
+  void emit (diagnostic &diag, const char *msgid, va_list *args)
+    LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING(3, 0);
+
+protected:
+  sink (diagnostic_manager &mgr);
+
+  static char *
+  get_option_name_cb (diagnostic_context *, int, diagnostic_t, diagnostic_t)
+  {
+    return nullptr; // FIXME
+  }
+
+  diagnostic_manager &m_mgr;
+
+  /* One context per sink.  */
+  diagnostic_context m_dc;
+};
+
+class text_sink : public sink
+{
+public:
+  text_sink (diagnostic_manager &mgr,
+	     FILE *dst_stream,
+	     enum diagnostic_colorize colorize);
+
+  void
+  on_begin_text_diagnostic (diagnostic_info *info);
+
+private:
+  const diagnostic_logical_location *m_current_logical_loc;
+};
+
+class sarif_sink : public sink
+{
+public:
+  sarif_sink (diagnostic_manager &mgr,
+	      FILE *dst_stream,
+	      enum diagnostic_sarif_version version);
+};
+
+/* Helper for the linemap code.  */
+
+static size_t
+round_alloc_size (size_t s)
+{
+  return s;
+}
+
+class impl_diagnostic_client_data_hooks : public diagnostic_client_data_hooks
+{
+public:
+  impl_diagnostic_client_data_hooks (diagnostic_manager &mgr)
+  : m_mgr (mgr)
+  {}
+
+  const client_version_info *get_any_version_info () const final override;
+  const logical_location *get_current_logical_location () const final override;
+  const char * maybe_get_sarif_source_language (const char *filename)
+    const final override;
+  void add_sarif_invocation_properties (sarif_object &invocation_obj)
+    const final override;
+
+private:
+  diagnostic_manager &m_mgr;
+};
+
+class impl_client_version_info : public client_version_info
+{
+public:
+  const char *get_tool_name () const final override
+  {
+    return m_name.get_str ();
+  }
+
+  char *maybe_make_full_name () const final override
+  {
+    return m_full_name.xstrdup ();
+  }
+
+  const char *get_version_string () const final override
+  {
+    return m_version.get_str ();
+  }
+
+  char *maybe_make_version_url () const final override
+  {
+    return m_version_url.xstrdup ();
+  }
+
+  void for_each_plugin (plugin_visitor &) const final override
+  {
+    // No-op.
+  }
+
+  owned_nullable_string m_name;
+  owned_nullable_string m_full_name;
+  owned_nullable_string m_version;
+  owned_nullable_string m_version_url;
+};
+
+/* This has to be a "struct" as it is exposed in the C API.  */
+
+struct diagnostic_manager
+{
+public:
+  diagnostic_manager ()
+  : m_current_diag (nullptr)
+  {
+    linemap_init (&m_line_table, BUILTINS_LOCATION);
+    m_line_table.m_reallocator = xrealloc;
+    m_line_table.m_round_alloc_size = round_alloc_size;
+    m_line_table.default_range_bits = 5;
+  }
+  ~diagnostic_manager ()
+  {
+    /* Clean up sinks first, as they can use other fields. */
+    for (size_t i = 0; i < m_sinks.size (); i++)
+      m_sinks[i] = nullptr;
+
+    for (auto iter : m_str_to_file_map)
+      delete iter.second;
+  }
+
+  line_maps *get_line_table () { return &m_line_table; }
+  file_cache *get_file_cache () { return &m_file_cache; }
+
+  void write_patch (FILE *dst_stream);
+
+  void add_sink (std::unique_ptr<sink> sink)
+  {
+    m_sinks.push_back (std::move (sink));
+  }
+
+  void emit (diagnostic &diag, const char *msgid, va_list *args)
+    LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING(3, 0);
+
+  const diagnostic_file *
+  new_file (const char *name,
+	    const char *sarif_source_language)
+  {
+    if (diagnostic_file **slot = m_str_to_file_map.get (name))
+      return *slot;
+    diagnostic_file *file = new diagnostic_file (name, sarif_source_language);
+    m_str_to_file_map.put (file->get_name (), file);
+    return file;
+  }
+
+  diagnostic_location_t
+  new_location_from_file_and_line (const diagnostic_file *file,
+				   diagnostic_line_num_t linenum)
+  {
+    // FIXME: this is a hack...
+    /* Build a simple linemap describing some locations. */
+    if (LINEMAPS_ORDINARY_USED (&m_line_table) == 0)
+      linemap_add (&m_line_table, LC_ENTER, false, file->get_name (), 0);
+    else
+      {
+	const line_map *map
+	  = linemap_add (&m_line_table, LC_RENAME_VERBATIM, false,
+			 file->get_name (), 0);
+	// FIXME:
+	((line_map_ordinary *)map)->included_from = UNKNOWN_LOCATION;
+      }
+    linemap_line_start (&m_line_table, linenum, 100);
+    return linemap_position_for_column (&m_line_table, 0);
+  }
+
+
+  diagnostic_location_t
+  new_location_from_file_line_column (const diagnostic_file *file,
+				      diagnostic_line_num_t line_num,
+				      diagnostic_column_num_t column_num)
+  {
+    // FIXME: this is a hack...
+    /* Build a simple linemap describing some locations. */
+    linemap_add (&m_line_table, LC_ENTER, false, file->get_name (), 0);
+    linemap_line_start (&m_line_table, line_num, 100);
+    return linemap_position_for_column (&m_line_table, column_num);
+  }
+
+  diagnostic_location_t
+  new_location_from_range (diagnostic_location_t loc_caret,
+			   diagnostic_location_t loc_start,
+			   diagnostic_location_t loc_end)
+  {
+    return m_line_table.make_location (loc_caret, loc_start, loc_end);
+  }
+
+  const diagnostic_logical_location *
+  new_logical_location (enum diagnostic_logical_location_kind_t kind,
+			const diagnostic_logical_location *parent,
+			const char *short_name,
+			const char *fully_qualified_name,
+			const char *decorated_name)
+  {
+    std::unique_ptr<diagnostic_logical_location> logical_loc
+      = ::make_unique<diagnostic_logical_location> (kind,
+						    parent,
+						    short_name,
+						    fully_qualified_name,
+						    decorated_name);
+    const diagnostic_logical_location *result = logical_loc.get ();
+    m_logical_locs.push_back (std::move (logical_loc));
+    return result;
+  }
+
+  void begin_group ()
+  {
+    for (auto &sink : m_sinks)
+      sink->begin_group ();
+  }
+
+  void end_group ()
+  {
+    for (auto &sink : m_sinks)
+      sink->end_group ();
+  }
+
+  const char *
+  maybe_get_sarif_source_language (const char *filename)
+  {
+    if (diagnostic_file **slot = m_str_to_file_map.get (filename))
+      {
+	gcc_assert (*slot);
+	return (*slot)->get_sarif_source_language ();
+      }
+    return nullptr;
+  }
+
+  const diagnostic *get_current_diag () { return m_current_diag; }
+
+  const client_version_info *get_client_version_info () const
+  {
+    return &m_client_version_info;
+  }
+  impl_client_version_info *get_client_version_info ()
+  {
+    return &m_client_version_info;
+  }
+
+  void
+  assert_valid_diagnostic_location_t (diagnostic_location_t loc) const
+  {
+    // TODO
+    (void)loc;
+  }
+
+  /* FIXME: Various things still used the "line_table" global variable.
+     Set it to be this diagnostic_manager's m_line_table.
+     Ideally we should eliminate this global (and this function).  */
+  void set_line_table_global ()
+  {
+    line_table = &m_line_table;
+  }
+
+private:
+  line_maps m_line_table;
+  file_cache m_file_cache;
+  impl_client_version_info m_client_version_info;
+  std::vector<std::unique_ptr<sink>> m_sinks;
+  hash_map<nofree_string_hash, diagnostic_file *> m_str_to_file_map;
+  std::vector<std::unique_ptr<diagnostic_logical_location>> m_logical_locs;
+  const diagnostic *m_current_diag;
+  edit_context m_edit_context;
+};
+
+class impl_rich_location : public rich_location
+{
+public:
+  impl_rich_location (line_maps *set)
+  : rich_location (set, UNKNOWN_LOCATION)
+  {}
+};
+
+class impl_range_label : public range_label
+{
+public:
+  impl_range_label (const char *text)
+  : m_text (xstrdup (text))
+  {}
+
+  ~impl_range_label () { free (m_text); }
+
+  label_text get_text (unsigned) const final override
+  {
+    return label_text::borrow (m_text);
+  }
+
+private:
+  char *m_text;
+};
+
+class impl_rule : public diagnostic_metadata::rule
+{
+public:
+  impl_rule (const char *title, const char *url)
+  :  m_title (title),
+     m_url (url)
+  {
+  }
+
+  char *make_description () const final override
+  {
+    return m_title.xstrdup ();
+  }
+
+  char *make_url () const final override
+  {
+    return m_url.xstrdup ();
+  }
+
+private:
+  owned_nullable_string m_title;
+  owned_nullable_string m_url;
+};
+
+/* This has to be a "struct" as it is exposed in the C API.  */
+
+struct diagnostic
+{
+public:
+  diagnostic (diagnostic_manager &diag_mgr,
+	      enum diagnostic_level level)
+  : m_diag_mgr (diag_mgr),
+    m_level (level),
+    m_rich_loc (diag_mgr.get_line_table ()),
+    m_logical_loc (nullptr)
+  {}
+
+  diagnostic_manager &get_manager () const
+  {
+    return m_diag_mgr;
+  }
+
+  enum diagnostic_level get_level () const { return m_level; }
+
+  rich_location *get_rich_location () { return &m_rich_loc; }
+  const diagnostic_metadata *get_metadata () { return &m_metadata; }
+
+#if 0
+  diagnostic_location_t get_location () const { return m_loc; }
+#endif
+
+  void set_cwe (unsigned cwe_id)
+  {
+    m_metadata.add_cwe (cwe_id);
+  }
+
+  void add_rule (const char *title,
+		 const char *url)
+  {
+    std::unique_ptr<impl_rule> rule = ::make_unique<impl_rule> (title, url);
+    m_metadata.add_rule (*rule.get ());
+    m_rules.push_back (std::move (rule));
+  }
+
+  void set_location (diagnostic_location_t loc)
+  {
+    m_rich_loc.set_range (0, loc, SHOW_RANGE_WITH_CARET);
+  }
+
+  void
+  add_location (diagnostic_location_t loc)
+  {
+    m_rich_loc.add_range (loc, SHOW_RANGE_WITHOUT_CARET);
+  }
+
+  void
+  add_location_with_label (diagnostic_location_t loc,
+			   const char *text)
+  {
+    std::unique_ptr<range_label> label = ::make_unique <impl_range_label> (text);
+    m_rich_loc.add_range (loc,
+			  SHOW_RANGE_WITHOUT_CARET,
+			  label.get ());
+    m_labels.push_back (std::move (label));
+  }
+
+  void
+  set_logical_location (const diagnostic_logical_location *logical_loc)
+  {
+    m_logical_loc = logical_loc;
+  }
+  const diagnostic_logical_location *get_logical_location () const
+  {
+    return m_logical_loc;
+  }
+
+private:
+  diagnostic_manager &m_diag_mgr;
+  enum diagnostic_level m_level;
+  impl_rich_location m_rich_loc;
+  const diagnostic_logical_location *m_logical_loc;
+  diagnostic_metadata m_metadata;
+  std::vector<std::unique_ptr<range_label>> m_labels;
+  std::vector<std::unique_ptr<impl_rule>> m_rules;
+};
+
+static diagnostic_t
+diagnostic_t_from_diagnostic_level (enum diagnostic_level level)
+{
+  switch (level)
+    {
+    default:
+      gcc_unreachable ();
+    case DIAGNOSTIC_LEVEL_ERROR:
+      return DK_ERROR;
+    case DIAGNOSTIC_LEVEL_WARNING:
+      return DK_WARNING;
+    case DIAGNOSTIC_LEVEL_NOTE:
+      return DK_NOTE;
+    }
+}
+
+/* class impl_diagnostic_client_data_hooks.  */
+
+const client_version_info *
+impl_diagnostic_client_data_hooks::get_any_version_info () const
+{
+  return m_mgr.get_client_version_info ();
+}
+
+const logical_location *
+impl_diagnostic_client_data_hooks::get_current_logical_location () const
+{
+  gcc_assert (m_mgr.get_current_diag ());
+
+  return m_mgr.get_current_diag ()->get_logical_location ();
+}
+
+const char *
+impl_diagnostic_client_data_hooks::
+maybe_get_sarif_source_language (const char *filename) const
+{
+  return m_mgr.maybe_get_sarif_source_language (filename);
+}
+
+void
+impl_diagnostic_client_data_hooks::
+add_sarif_invocation_properties (sarif_object &) const
+{
+  // No-op.
+}
+
+/* class sink.  */
+
+void
+sink::emit (diagnostic &diag, const char *msgid, va_list *args)
+{
+  diagnostic_info info;
+  diagnostic_set_info (&info, msgid, args, diag.get_rich_location (),
+		       diagnostic_t_from_diagnostic_level (diag.get_level ()));
+  info.metadata = diag.get_metadata ();
+  diagnostic_report_diagnostic (&m_dc, &info);
+}
+
+sink::sink (diagnostic_manager &mgr)
+: m_mgr (mgr)
+{
+  diagnostic_initialize (&m_dc, 0);
+  m_dc.m_client_aux_data = this;
+  m_dc.m_option_name = get_option_name_cb;
+  m_dc.set_client_data_hooks (new impl_diagnostic_client_data_hooks (mgr));
+  m_dc.file_cache_init ();
+}
+
+sink::~sink ()
+{
+  diagnostic_finish (&m_dc);
+}
+
+/* class text_sink : public sink.  */
+
+text_sink::text_sink (diagnostic_manager &mgr,
+		      FILE *dst_stream,
+		      enum diagnostic_colorize colorize)
+: sink (mgr)
+{
+  m_dc.set_show_cwe (true);
+  m_dc.set_show_rules (true);
+
+  diagnostic_color_rule_t color_rule;
+  switch (colorize)
+    {
+    default:
+      gcc_unreachable ();
+    case DIAGNOSTIC_COLORIZE_IF_TTY:
+      color_rule = DIAGNOSTICS_COLOR_AUTO;
+      break;
+    case DIAGNOSTIC_COLORIZE_NO:
+      color_rule = DIAGNOSTICS_COLOR_NO;
+      break;
+    case DIAGNOSTIC_COLORIZE_YES:
+      color_rule = DIAGNOSTICS_COLOR_YES;
+      break;
+    }
+  diagnostic_color_init (&m_dc, color_rule);
+  m_dc.printer->buffer->stream = dst_stream;
+  m_dc.m_text_callbacks.begin_diagnostic
+    = [] (diagnostic_context *context,
+	  diagnostic_info *info)
+      {
+	text_sink *sink = static_cast<text_sink *> (context->m_client_aux_data);
+	sink->on_begin_text_diagnostic (info);
+      };
+  m_dc.m_source_printing.enabled = true; // FIXME
+  m_dc.m_source_printing.colorize_source_p = true; // FIXME
+  m_dc.m_source_printing.show_labels_p = true; // FIXME
+  m_dc.m_source_printing.show_line_numbers_p = true; // FIXME
+  m_dc.m_source_printing.min_margin_width = 6; // FIXME
+}
+
+void
+text_sink::on_begin_text_diagnostic (diagnostic_info *info)
+{
+  const diagnostic *diag = m_mgr.get_current_diag ();
+  gcc_assert (diag);
+  const diagnostic_logical_location *diag_logical_loc
+    = diag->get_logical_location ();
+  if (m_current_logical_loc != diag_logical_loc)
+    {
+      m_current_logical_loc = diag_logical_loc;
+      if (m_current_logical_loc)
+	{
+	  char *old_prefix = pp_take_prefix (m_dc.printer);
+	  switch (m_current_logical_loc->get_kind ())
+	    {
+	    default:
+	      break;
+	    case LOGICAL_LOCATION_KIND_FUNCTION:
+	      if (const char *name
+		  = m_current_logical_loc->get_name_with_scope ())
+		{
+		  pp_printf (m_dc.printer, _("In function %qs"), name);
+		  pp_character (m_dc.printer, ':');
+		  pp_newline (m_dc.printer);
+		}
+	      break;
+	      // FIXME: handle other cases
+	    }
+	  m_dc.printer->prefix = old_prefix;
+	}
+    }
+  pp_set_prefix (m_dc.printer,
+		 diagnostic_build_prefix (&m_dc, info));
+}
+
+/* class sarif_sink : public sink.  */
+
+sarif_sink::sarif_sink (diagnostic_manager &mgr,
+			FILE *dst_stream,
+			enum diagnostic_sarif_version)
+: sink (mgr)
+{
+  diagnostic_output_format_init_sarif_stream (&m_dc, dst_stream);
+}
+
+/* struct diagnostic_manager.  */
+
+void
+diagnostic_manager::write_patch (FILE *dst_stream)
+{
+  pretty_printer pp;
+  pp.buffer->stream = dst_stream;
+  m_edit_context.print_diff (&pp, true);
+  pp_flush (&pp);
+}
+
+void
+diagnostic_manager::emit (diagnostic &diag, const char *msgid, va_list *args)
+{
+  set_line_table_global ();
+
+  m_current_diag = &diag;
+  for (auto &sink : m_sinks)
+    {
+      va_list arg_copy;
+      va_copy (arg_copy, *args);
+      sink->emit (diag, msgid, &arg_copy);
+    }
+
+  rich_location *rich_loc = diag.get_rich_location ();
+  if (rich_loc->fixits_can_be_auto_applied_p ())
+    m_edit_context.add_fixits (rich_loc);
+
+  m_current_diag = nullptr;
+}
+
+/* Public entrypoints.  */
+
+/* Public entrypoint for clients to acquire a diagnostic_manager.  */
+
+diagnostic_manager *
+diagnostic_manager_new (void)
+{
+  return new diagnostic_manager ();
+}
+
+/* Public entrypoint for clients to release a diagnostic_manager.  */
+
+void
+diagnostic_manager_release (diagnostic_manager *diag_mgr)
+{
+  delete diag_mgr;
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_set_tool_name (diagnostic_manager *diag_mgr,
+				  const char *value)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (value);
+
+  diag_mgr->get_client_version_info ()->m_name.set (value);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_set_full_name (diagnostic_manager *diag_mgr,
+				  const char *value)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (value);
+
+  diag_mgr->get_client_version_info ()->m_full_name.set (value);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_set_version_string (diagnostic_manager *diag_mgr,
+				       const char *value)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (value);
+
+  diag_mgr->get_client_version_info ()->m_version.set (value);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_set_version_url (diagnostic_manager *diag_mgr,
+				    const char *value)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (value);
+
+  diag_mgr->get_client_version_info ()->m_version_url.set (value);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_add_text_sink (diagnostic_manager *diag_mgr,
+				  FILE *dst_stream,
+				  enum diagnostic_colorize colorize)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (dst_stream);
+
+  diag_mgr->add_sink (make_unique<text_sink> (*diag_mgr, dst_stream, colorize));
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_add_sarif_sink (diagnostic_manager *diag_mgr,
+				   FILE *dst_stream,
+				   enum diagnostic_sarif_version version)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (dst_stream);
+
+  diag_mgr->add_sink (make_unique<sarif_sink> (*diag_mgr, dst_stream, version));
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_write_patch (diagnostic_manager *diag_mgr,
+				FILE *dst_stream)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (dst_stream);
+
+  diag_mgr->write_patch (dst_stream);
+}
+
+/* Public entrypoint.  */
+
+const diagnostic_file *
+diagnostic_manager_new_file (diagnostic_manager *diag_mgr,
+			     const char *name,
+			     const char *sarif_source_language)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (name);
+
+  return diag_mgr->new_file (name, sarif_source_language);
+}
+
+/* Public entrypoint.  */
+
+diagnostic_location_t
+diagnostic_manager_new_location_from_file_and_line (diagnostic_manager *diag_mgr,
+						    const diagnostic_file *file,
+						    diagnostic_line_num_t linenum)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (file);
+
+  return diag_mgr->new_location_from_file_and_line (file, linenum);
+}
+
+/* Public entrypoint.  */
+
+diagnostic_location_t
+diagnostic_manager_new_location_from_file_line_column (diagnostic_manager *diag_mgr,
+						       const diagnostic_file *file,
+						       diagnostic_line_num_t line_num,
+					       diagnostic_column_num_t column_num)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (file);
+
+  return diag_mgr->new_location_from_file_line_column (file,
+						       line_num,
+						       column_num);
+}
+
+/* Public entrypoint.  */
+
+diagnostic_location_t
+diagnostic_manager_new_location_from_range (diagnostic_manager *diag_mgr,
+					    diagnostic_location_t loc_caret,
+					    diagnostic_location_t loc_start,
+					    diagnostic_location_t loc_end)
+{
+  gcc_assert (diag_mgr);
+
+  return diag_mgr->new_location_from_range (loc_caret,
+					    loc_start,
+					    loc_end);
+}
+
+
+// FIXME: emit text to stderr
+
+#if 0
+void
+diagnostic_location_debug (diagnostic_manager *diag_mgr,
+			   diagnostic_location_t loc,
+			   FILE *stream)
+{
+  text_sink sink (stream);
+  diagnostic d (*diag_mgr, DIAGNOSTIC_LEVEL_NOTE);
+}
+#endif
+
+/* Public entrypoint.  */
+
+const diagnostic_logical_location *
+diagnostic_manager_new_logical_location (diagnostic_manager *diag_mgr,
+					 enum diagnostic_logical_location_kind_t kind,
+					 const diagnostic_logical_location *parent,
+					 const char *short_name,
+					 const char *fully_qualified_name,
+					 const char *decorated_name)
+{
+  gcc_assert (diag_mgr);
+
+  return diag_mgr->new_logical_location (kind,
+					 parent,
+					 short_name,
+					 fully_qualified_name,
+					 decorated_name);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_begin_group (diagnostic_manager *diag_mgr)
+{
+  gcc_assert (diag_mgr);
+  diag_mgr->begin_group ();
+}
+
+/* Public entrypoint.  */
+
+extern void
+diagnostic_manager_end_group (diagnostic_manager *diag_mgr)
+{
+  gcc_assert (diag_mgr);
+  diag_mgr->end_group ();
+}
+
+/* Public entrypoint.  */
+
+diagnostic *
+diagnostic_begin (diagnostic_manager *diag_mgr,
+		  enum diagnostic_level level)
+{
+  gcc_assert (diag_mgr);
+
+  return new diagnostic (*diag_mgr, level);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_set_cwe (diagnostic *diag,
+		    unsigned cwe_id)
+{
+  gcc_assert (diag);
+
+  diag->set_cwe (cwe_id);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_rule (diagnostic *diag,
+		     const char *title,
+		     const char *url)
+{
+  gcc_assert (diag);
+
+  diag->add_rule (title, url);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_set_location (diagnostic *diag,
+			 diagnostic_location_t loc)
+{
+  gcc_assert (diag);
+  diag->get_manager ().assert_valid_diagnostic_location_t (loc);
+
+  diag->set_location (loc);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_location (diagnostic *diag,
+			 diagnostic_location_t loc)
+{
+  gcc_assert (diag);
+  diag->get_manager ().assert_valid_diagnostic_location_t (loc);
+
+  diag->add_location (loc);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_location_with_label (diagnostic *diag,
+				    diagnostic_location_t loc,
+				    const char *text)
+{
+  gcc_assert (diag);
+  diag->get_manager ().assert_valid_diagnostic_location_t (loc);
+  gcc_assert (text);
+
+  diag->add_location_with_label (loc, text);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_set_logical_location (diagnostic *diag,
+				 const diagnostic_logical_location *logical_loc)
+{
+  gcc_assert (diag);
+  gcc_assert (logical_loc);
+
+  diag->set_logical_location (logical_loc);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_fix_it_hint_insert_before (diagnostic *diag,
+					  diagnostic_location_t loc,
+					  const char *addition)
+{
+  gcc_assert (diag);
+  diag->get_manager ().assert_valid_diagnostic_location_t (loc);
+  gcc_assert (addition);
+
+  diag->get_manager ().set_line_table_global ();
+  diag->get_rich_location ()->add_fixit_insert_before (loc, addition);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_fix_it_hint_insert_after (diagnostic *diag,
+					 diagnostic_location_t loc,
+					 const char *addition)
+{
+  gcc_assert (diag);
+  diag->get_manager ().assert_valid_diagnostic_location_t (loc);
+  gcc_assert (addition);
+
+  diag->get_manager ().set_line_table_global ();
+  diag->get_rich_location ()->add_fixit_insert_after (loc, addition);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_fix_it_hint_replace (diagnostic *diag,
+				    diagnostic_location_t loc,
+				    const char *replacement)
+{
+  gcc_assert (diag);
+  diag->get_manager ().assert_valid_diagnostic_location_t (loc);
+  gcc_assert (replacement);
+
+  diag->get_manager ().set_line_table_global ();
+  diag->get_rich_location ()->add_fixit_replace (loc, replacement);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_fix_it_hint_delete (diagnostic *diag,
+				   diagnostic_location_t loc)
+{
+  gcc_assert (diag);
+  diag->get_manager ().assert_valid_diagnostic_location_t (loc);
+
+  diag->get_manager ().set_line_table_global ();
+  diag->get_rich_location ()->add_fixit_remove (loc);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_finish (diagnostic *diag, const char *gmsgid, ...)
+{
+  gcc_assert (diag);
+
+  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;
+  va_list args;
+  va_start (args, gmsgid);
+  gcc_assert (diag);
+  diag->get_manager ().emit (*diag, gmsgid, &args);
+  va_end (args);
+  delete diag;
+}
diff --git a/gcc/libdiagnostics.map b/gcc/libdiagnostics.map
new file mode 100644
index 00000000000..06da1d5c9c2
--- /dev/null
+++ b/gcc/libdiagnostics.map
@@ -0,0 +1,57 @@
+# Linker script for libdiagnostics.so
+#   Copyright (C) 2023 Free Software Foundation, Inc.
+#   Contributed by David Malcolm <dmalcolm@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/>.  */
+
+# The initial release of the library.
+LIBDIAGNOSTICS_ABI_0
+{
+  global:
+    # Keep this list in order of decls in header file.
+    diagnostic_manager_new;
+    diagnostic_manager_release;
+    diagnostic_manager_set_tool_name;
+    diagnostic_manager_set_full_name;
+    diagnostic_manager_set_version_string;
+    diagnostic_manager_set_version_url;
+    diagnostic_manager_add_text_sink;
+    diagnostic_manager_add_sarif_sink;
+    diagnostic_manager_write_patch;
+    diagnostic_manager_new_file;
+    diagnostic_manager_new_location_from_file_and_line;
+    diagnostic_manager_new_location_from_file_line_column;
+    diagnostic_manager_new_location_from_range;
+    diagnostic_manager_new_logical_location;
+    diagnostic_manager_begin_group;
+    diagnostic_manager_end_group;
+    diagnostic_begin;
+    diagnostic_set_cwe;
+    diagnostic_add_rule;
+    diagnostic_set_location;
+    diagnostic_set_location_with_label;
+    diagnostic_add_location;
+    diagnostic_add_location_with_label;
+    diagnostic_set_logical_location;
+    diagnostic_add_fix_it_hint_insert_before;
+    diagnostic_add_fix_it_hint_insert_after;
+    diagnostic_add_fix_it_hint_replace;
+    diagnostic_add_fix_it_hint_delete;
+    diagnostic_finish;
+
+  local: *;
+};
diff --git a/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp b/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp
new file mode 100644
index 00000000000..bd50a5568a7
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp
@@ -0,0 +1,544 @@
+# Test code for libdiagnostics.so
+#
+# FIXME:
+#
+# Test code for libdiagnostics.so
+# We will compile each of jit.dg/test-*.c into an executable
+# dynamically linked against libgccjit.so, and then run each
+# such executable.
+#
+# These executables call into the libgccjit.so API to create
+# code, compile it, and run it, verifying that the results
+# are as expected.  See harness.h for shared code used by all
+# such executables.
+#
+# The executables call into DejaGnu's unit testing C API to
+# report PASS/FAIL results, which this script gathers back
+# up into the Tcl world, reporting a summary of all results
+# across all of the executables.
+
+# Kludge alert:
+# We need g++_init so that it can find the stdlib include path.
+#
+# g++_init (in lib/g++.exp) uses g++_maybe_build_wrapper,
+# which normally comes from the definition of
+# ${tool}_maybe_build_wrapper within lib/wrapper.exp.
+#
+# However, for us, ${tool} is "jit".
+# Hence we load wrapper.exp with tool == "g++", so that
+# g++_maybe_build_wrapper is defined.
+set tool g++
+load_lib wrapper.exp
+set tool diagnostics
+
+load_lib dg.exp
+load_lib prune.exp
+load_lib target-supports.exp
+load_lib gcc-defs.exp
+load_lib timeout.exp
+load_lib target-libpath.exp
+load_lib gcc.exp
+load_lib g++.exp
+load_lib dejagnu.exp
+load_lib target-supports-dg.exp
+
+# # Skip these tests for targets that don't support -ldiagnostics
+# if { ![check_effective_target_ldiagnostics] } {
+#     return
+# }
+
+# The default do-what keyword.
+set dg-do-what-default compile
+
+# Look for lines of the form:
+#   definitely lost: 11,316 bytes in 235 blocks
+#   indirectly lost: 352 bytes in 4 blocks
+# Ideally these would report zero bytes lost (which is a PASS);
+# for now, report non-zero leaks as XFAILs.
+proc report_leak {kind name logfile line} {
+    set match [regexp "$kind lost: .*" $line result]
+    if $match {
+	verbose "Saw \"$result\" within \"$line\"" 4
+	# Extract bytes and blocks.
+	# These can contain commas as well as numerals,
+	# but we only care about whether we have zero.
+	regexp "$kind lost: (.+) bytes in (.+) blocks" \
+	    $result -> bytes blocks
+	verbose "bytes: '$bytes'" 4
+	verbose "blocks: '$blocks'" 4
+	if { $bytes == 0 } {
+	    pass "$name: $logfile: $result"
+	} else {
+	    xfail "$name: $logfile: $result"
+	}
+    }
+}
+
+proc parse_valgrind_logfile {name logfile} {
+    verbose "parse_valgrind_logfile: $logfile" 2
+    if [catch {set f [open $logfile]}] {
+	fail "$name: unable to read $logfile"
+	return
+    }
+
+    while { [gets $f line] >= 0 } {
+	# Strip off the PID prefix e.g. ==7675==
+	set line [regsub "==\[0-9\]*== " $line ""]
+	verbose $line 2
+
+	report_leak "definitely" $name $logfile $line
+	report_leak "indirectly" $name $logfile $line
+    }
+    close $f
+}
+
+# Given WRES, the result from "wait", issue a PASS
+# if the spawnee exited cleanly, or a FAIL for various kinds of
+# unexpected exits.
+
+proc verify_exit_status { executable wres } {
+    lassign $wres pid spawnid os_error_flag value
+    verbose "pid: $pid" 3
+    verbose "spawnid: $spawnid" 3
+    verbose "os_error_flag: $os_error_flag" 3
+    verbose "value: $value" 3
+
+    # Detect segfaults etc:
+    if { [llength $wres] > 4 } {
+	if { [lindex $wres 4] == "CHILDKILLED" } {
+	    fail "$executable killed: $wres"
+	    return
+	}
+    }
+    if { $os_error_flag != 0 } {
+	fail "$executable: OS error: $wres"
+	return
+    }
+    if { $value != 0 } {
+	fail "$executable: non-zero exit code: $wres"
+	return
+    }
+    pass "$executable exited cleanly"
+}
+
+# This is host_execute from dejagnu.exp commit
+#   126a089777158a7891ff975473939f08c0e31a1c
+# with the following patch applied, and renaming to "fixed_host_execute".
+# See the discussion at
+#  http://lists.gnu.org/archive/html/dejagnu/2014-10/msg00000.html
+#
+#  --- /usr/share/dejagnu/dejagnu.exp.old  2014-10-08 13:38:57.274068541 -0400
+#  +++ /usr/share/dejagnu/dejagnu.exp      2014-10-10 12:27:51.113813659 -0400
+#  @@ -113,8 +113,6 @@ proc host_execute {args} {
+#       set timetol 0
+#       set arguments ""
+#   
+#  -    expect_before buffer_full { perror "Buffer full" }
+#  -
+#       if { [llength $args] == 0} {
+#          set executable $args
+#       } else {
+
+
+# Execute the executable file, and anaylyse the output for the
+# test state keywords.
+#    Returns:
+#	A "" (empty) string if everything worked, or an error message
+#	if there was a problem.
+#
+proc fixed_host_execute {args} {
+    global env
+    global text
+    global spawn_id
+
+    verbose "fixed_host_execute: $args"
+
+    set timeoutmsg "Timed out: Never got started, "
+    set timeout 100
+    set file all
+    set timetol 0
+    set arguments ""
+
+    if { [llength $args] == 0} {
+	set executable $args
+    } else {
+	set executable [lindex $args 0]
+	set params [lindex $args 1]
+    }
+
+    verbose "The executable is $executable" 2
+    if {![file exists ${executable}]} {
+	perror "The executable, \"$executable\" is missing" 0
+	return "No source file found"
+    } elseif {![file executable ${executable}]} {
+	perror "The executable, \"$executable\" is not usable" 0
+	return "Bad executable found"
+    }
+
+    verbose "params: $params" 2
+
+    # spawn the executable and look for the DejaGnu output messages from the
+    # test case.
+    # spawn -noecho -open [open "|./${executable}" "r"]
+
+    # Run under valgrind if RUN_UNDER_VALGRIND is present in the environment.
+    # Note that it's best to configure gcc with --enable-valgrind-annotations
+    # when testing under valgrind.
+    set run_under_valgrind [info exists env(RUN_UNDER_VALGRIND)]
+    if $run_under_valgrind {
+	set valgrind_logfile "${executable}.valgrind.txt"
+	set valgrind_params {"valgrind"}
+	lappend valgrind_params "--leak-check=full"
+	lappend valgrind_params "--log-file=${valgrind_logfile}"
+    } else {
+	set valgrind_params {}
+    }
+    verbose "valgrind_params: $valgrind_params" 2
+
+    set args ${valgrind_params}
+    lappend args "./${executable}"
+    set args [concat $args ${params}]
+    verbose "args: $args" 2
+
+    # We checked that the executable exists above, and can be executed, but
+    # that does not cover other reasons that the launch could fail (e.g.
+    # missing or malformed params); catch such cases here and report them.
+    set err [catch "spawn -noecho $args" pid]
+    set sub_proc_id $spawn_id
+    if { $pid <= 0 || $err != 0 || $sub_proc_id < 0 } {
+        warning "failed to spawn : $args : err = $err"
+    }
+
+    # Increase the buffer size, if needed to avoid spurious buffer-full
+    # events; GCC uses 10000; chose a power of two here.
+    set current_max_match [match_max -i $sub_proc_id]
+    if { $current_max_match < 8192 } {
+        match_max -i $sub_proc_id 8192
+        set used [match_max -i $sub_proc_id]
+    }
+
+    # If we get a buffer-full error, that seems to be unrecoverable so try to
+    # exit in a reasonable manner to avoid wedged processes.
+    expect_after full_buffer {
+        verbose -log "fixed_host_execute: $args FULL BUFFER"
+        # FIXME: this all assumes that closing the connection will cause the
+        # sub-process to terminate (but that is not going to be the case if,
+        # for example, there is something started with -nohup somewhere).
+        # We should explicitly kill it here.
+        # Set the process to be a nowait exit.
+        wait -nowait -i $sub_proc_id
+        catch close
+        perror "${executable} got full_buffer"
+        return "${executable} got full_buffer"
+    }
+
+    set prefix "\[^\r\n\]*"
+    # Work around a Darwin tcl or termios bug that sometimes inserts extra
+    # CR characters into the cooked tty stream
+    set endline "\r\n"
+    if { [istarget *-*-darwin*] } {
+        set endline "\r(\r)*\n"
+    }
+    
+    # Note that the logic here assumes that we cannot (validly) get single
+    # carriage return or line feed characters in the stream.  If those occur,
+    # it will stop any further matching.  We arange for the matching to be
+    # at the start of the buffer - so that if there is any spurious output
+    # to be discarded, it must be done explicitly - not by matching part-way
+    # through the buffer.
+    expect {
+	-re "^$prefix\[0-9\]\[0-9\]:..:..:${text}*$endline" {
+	    regsub "\[\n\r\t\]*NOTE: $text\r\n" $expect_out(0,string) "" output
+	    verbose "$output" 3
+	    set timetol 0
+	    exp_continue
+	}
+	-re "^\tNOTE: (\[^\r\n\]+)$endline" {
+	    # discard notes.
+	    verbose "Ignored note: $expect_out(1,string)" 2
+	    set timetol 0
+	    exp_continue
+	}
+	-re "^\tPASSED: (\[^\r\n\]+)$endline" {
+	    pass "$expect_out(1,string)"
+	    set timetol 0
+	    exp_continue
+	}
+	-re "^\tFAILED: (\[^\r\n\]+)$endline" {
+	    fail "$expect_out(1,string)"
+	    set timetol 0
+	    exp_continue
+	}
+	-re "^\tUNTESTED: (\[^\r\n\]+)$endline" {
+	    untested "$expect_out(1,string)"
+	    set timetol 0
+	    exp_continue
+	}
+	-re "^\tUNRESOLVED: (\[^\r\n\]+)$endline" {
+	    unresolved "$expect_out(1,string)"
+	    set timetol 0
+	    exp_continue
+	}
+	-re "^$prefix$endline" {
+            # This matches and discards any other lines (including blank ones).
+            if { [string length $expect_out(buffer)] <= 2 } {
+                set output "blank line"
+            } else {
+	        set output [string range $expect_out(buffer) 0 end-2]
+	    }
+	    verbose -log "DISCARDED $expect_out(spawn_id) : $output"
+	    exp_continue
+	}
+	eof {
+	    # This seems to be the only way that we can reliably know that the
+	    # output is finished since there are cases where further output
+	    # follows the dejagnu test harness totals.
+	    verbose "saw eof" 2
+	}
+	timeout {
+	    if { $timetol <= 2 } {
+	        verbose -log "Timed out with retry (timeout = $timeout)"
+		incr timetol
+		exp_continue
+	    } else {
+	        warning "Timed out executing testcase (timeout = $timeout)"
+		catch close
+		return "Timed out executing test case"
+	    }
+	}
+    }
+
+    # Use "wait" to pick up the sub-process exit state.  If the sub-process is
+    # writing to a file (perhaps under valgrind) then that also needs to be
+    # complete; only attempt this on a valid spawn.
+    if { $sub_proc_id > 0 } {
+        verbose "waiting for $sub_proc_id" 1
+        # Be explicit about what we are waiting for.
+        catch "wait -i $sub_proc_id" wres
+        verbose "wres: $wres" 2
+        verify_exit_status $executable $wres
+    }
+ 
+    if $run_under_valgrind {
+	upvar 2 name name
+	parse_valgrind_logfile $name $valgrind_logfile
+    }
+
+    # force a close of the executable to be safe.
+    catch close
+
+    return ""
+}
+
+# (end of code from dejagnu.exp)
+
+# GCC_UNDER_TEST is needed by gcc_target_compile
+global GCC_UNDER_TEST
+if ![info exists GCC_UNDER_TEST] {
+    set GCC_UNDER_TEST "[find_gcc]"
+}
+
+g++_init
+
+# Initialize dg.
+dg-init
+
+# Gather a list of all tests.
+
+# C tests within the testsuite: gcc/testsuite/libdiagnostics.dg/test-*.c
+set tests [find $srcdir/$subdir test-*.c]
+
+set tests [lsort $tests]
+
+verbose "tests: $tests"
+
+# libgloss has found the driver (as "xgcc" or "gcc) and stored
+# its full path as GCC_UNDER_TEST.
+proc get_path_of_driver {} {
+    global GCC_UNDER_TEST
+
+    verbose "GCC_UNDER_TEST: $GCC_UNDER_TEST"
+    set binary [lindex $GCC_UNDER_TEST 0]
+    verbose "binary: $binary"
+
+    return [file dirname $binary]
+}
+
+# Expand "SRCDIR" within ARG to the location of the top-level
+# src directory
+
+proc diagnostics-expand-vars {arg} {
+    verbose "diagnostics-expand-vars: $arg"
+    global srcdir
+    verbose " srcdir: $srcdir"
+    # "srcdir" is that of the gcc/testsuite directory, so
+    # we need to go up two levels.
+    set arg [string map [list "SRCDIR" $srcdir/../..] $arg]
+    verbose " new arg: $arg"
+    return $arg
+}
+
+# Parameters used when invoking the executables built from the test cases.
+
+global diagnostics-exe-params
+set diagnostics-exe-params {}
+
+# Set "diagnostics-exe-params", expanding "SRCDIR" in each arg to the location of
+# the top-level srcdir.
+
+proc dg-diagnostics-set-exe-params { args } {
+    verbose "dg-diagnostics-set-exe-params: $args"
+
+    global diagnostics-exe-params
+    set diagnostics-exe-params {}
+    # Skip initial arg (line number)
+    foreach arg [lrange $args 1 [llength $args] ] {
+	lappend diagnostics-exe-params [diagnostics-expand-vars $arg]
+    }
+}
+
+proc diagnostics-dg-test { prog do_what extra_tool_flags } {
+    verbose "within diagnostics-dg-test..."
+    verbose "  prog: $prog"
+    verbose "  do_what: $do_what"
+    verbose "  extra_tool_flags: $extra_tool_flags"
+
+    global dg-do-what-default
+    set dg-do-what [list ${dg-do-what-default} "" P]
+
+    set tmp [dg-get-options $prog]
+    foreach op $tmp {
+	verbose "Processing option: $op" 3
+	set status [catch "$op" errmsg]
+	if { $status != 0 } {
+	    if { 0 && [info exists errorInfo] } {
+		# This also prints a backtrace which will just confuse
+		# testcase writers, so it's disabled.
+		perror "$name: $errorInfo\n"
+	    } else {
+		perror "$name: $errmsg for \"$op\"\n"
+	    }
+	    perror "$name: $errmsg for \"$op\"" 0
+	    return
+	}
+    }
+
+    # If we're not supposed to try this test on this target, we're done.
+    if { [lindex ${dg-do-what} 1] == "N" } {
+	unsupported "$name"
+	verbose "$name not supported on this target, skipping it" 3
+	return
+    }
+
+    # Determine what to name the built executable.
+    #
+    # We simply append .exe to the filename, e.g.
+    #  "test-foo.c.exe"
+    # since some testcases exist in both
+    #  "test-foo.c" and
+    #  "test-foo.cc"
+    # variants, and we don't want them to clobber each other's
+    # executables.
+    #
+    # This also ensures that the source name makes it into the
+    # pass/fail output, so that we can distinguish e.g. which test-foo
+    # is failing.
+    set output_file "[file tail $prog].exe"
+    verbose "output_file: $output_file"
+
+    # Create the test executable:
+    set extension [file extension $prog]
+    if {$extension == ".cc"} {
+	set compilation_function "g++_target_compile"
+	set options "{additional_flags=$extra_tool_flags}"
+    } else {
+	set compilation_function "gcc_target_compile"
+	# Until recently, <dejagnu.h> assumed -fgnu89-inline
+	# Ideally we should fixincludes it (PR other/63613), but
+	# for now add -fgnu89-inline when compiling C JIT testcases.
+	# See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63613
+	# and http://lists.gnu.org/archive/html/dejagnu/2014-10/msg00011.html
+	set options "{additional_flags=$extra_tool_flags -fgnu89-inline}"
+    }
+    verbose "compilation_function=$compilation_function"
+    verbose "options=$options"
+
+    set comp_output [$compilation_function $prog $output_file \
+			 "executable" $options]
+    upvar 1 name name
+    if ![diagnostics_check_compile "$name" "initial compilation" \
+	    $output_file $comp_output] then {
+      return
+    }
+
+    # Run the test executable, capturing the PASS/FAIL textual output
+    # from the C API, converting it into the Tcl API.
+
+    # We need to set LD_LIBRARY_PATH so that the test files can find
+    # libdiagnostics.so
+    # Do this using set_ld_library_path_env_vars from target-libpath.exp
+    # We will restore the old value later using
+    # restore_ld_library_path_env_vars.
+
+    # Unfortunately this API only supports a single saved value, rather
+    # than a stack, and g++_init has already called into this API,
+    # injecting the appropriate value for LD_LIBRARY_PATH for finding
+    # the built copy of libstdc++.
+    # Hence the call to restore_ld_library_path_env_vars would restore
+    # the *initial* value of LD_LIBRARY_PATH, and attempts to run
+    # a C++ testcase after running any prior testcases would thus look
+    # in the wrong place for libstdc++.  This led to failures at startup
+    # of the form:
+    #   ./tut01-hello-world.cc.exe: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by ./tut01-hello-world.cc.exe)
+    # when the built libstdc++ is more recent that the system libstdc++.
+    #
+    # As a workaround, reset the variable "orig_environment_saved" within
+    # target-libpath.exp, so that the {set|restore}_ld_library_path_env_vars
+    # API saves/restores the current value of LD_LIBRARY_PATH (as set up
+    # by g++_init).
+    global orig_environment_saved
+    set orig_environment_saved 0
+
+    global ld_library_path
+    global base_dir
+    set ld_library_path "$base_dir/../../"
+    set_ld_library_path_env_vars
+
+    # dejagnu.exp's host_execute has code to scrape out test results
+    # from the DejaGnu C API and bring back into the tcl world, so we
+    # use that to invoke the built code.
+    # However, it appears to be buggy; see:
+    #  http://lists.gnu.org/archive/html/dejagnu/2014-10/msg00000.html
+    # We instead call a patched local copy, "fixed_host_execute", defined
+    # above.
+
+    global diagnostics-exe-params
+    set args ${diagnostics-exe-params}
+    set diagnostics-exe-params {}
+
+    set result [fixed_host_execute $output_file $args ]
+    verbose "result: $result"
+
+    restore_ld_library_path_env_vars
+
+    # Normally we would return $comp_output and $output_file to the
+    # caller, which would delete $output_file, the generated executable.
+    # If we need to debug, it's handy to be able to suppress this behavior,
+    # keeping the executable around.
+    
+    global env
+    set preserve_executables [info exists env(PRESERVE_EXECUTABLES)]
+    if $preserve_executables {
+	set output_file ""
+    }
+    
+    return [list $comp_output $output_file]
+}
+
+set DEFAULT_CFLAGS "-I$srcdir/.. -ldiagnostics -g -Wall -Werror"
+
+# Main loop.  This will invoke jig-dg-test on each test-*.c file.
+dg-runtest $tests "" $DEFAULT_CFLAGS
+
+# All done.
+dg-finish
-- 
2.26.3


^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH] binutils: experimental use of libdiagnostics in gas
  2023-11-06 22:29 [PATCH/RFC] libdiagnostics: a shared library for emitting diagnostics David Malcolm
  2023-11-06 22:29 ` [PATCH 1/2] libdiagnostics: header and examples David Malcolm
  2023-11-06 22:29 ` [PATCH 2/2] libdiagnostics: work-in-progress implementation David Malcolm
@ 2023-11-06 22:29 ` David Malcolm
  2023-11-07  7:04   ` Simon Sobisch
                     ` (2 more replies)
  2023-11-21 22:20 ` [PATCH 0/6] v2 of libdiagnostics David Malcolm
  3 siblings, 3 replies; 29+ messages in thread
From: David Malcolm @ 2023-11-06 22:29 UTC (permalink / raw)
  To: gcc-patches, binutils; +Cc: Nick Clifton, Simon Sobisch, David Malcolm

Here's a patch for gas in binutils that makes it use libdiagnostics
(with some nasty hardcoded paths to specific places on my hard drive
to make it easier to develop the API).

For now this hardcodes adding two sinks: a text sink on stderr, and
also a SARIF output to stderr (which happens after all regular output).

For example, without this patch:

   gas testsuite/gas/all/warn-1.s

emits:
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
testsuite/gas/all/warn-1.s: Assembler messages:
testsuite/gas/all/warn-1.s:3: Warning: a warning message
testsuite/gas/all/warn-1.s:4: Error: .warning argument must be a string
testsuite/gas/all/warn-1.s:5: Warning: .warning directive invoked in source file
testsuite/gas/all/warn-1.s:6: Warning: .warning directive invoked in source file
testsuite/gas/all/warn-1.s:7: Warning:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

whereas with this patch:
  LD_LIBRARY_PATH=/home/david/coding-3/gcc-newgit-canvas-2023/build/gcc ./as-new testsuite/gas/all/warn-1.s
emits:

VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
testsuite/gas/all/warn-1.s:3: warning: a warning message
    3 |  .warning "a warning message"   ;# { dg-warning "Warning: a warning message" }
      |
testsuite/gas/all/warn-1.s:4: error: .warning argument must be a string
    4 |  .warning a warning message     ;# { dg-error "Error: .warning argument must be a string" }
      |
testsuite/gas/all/warn-1.s:5: warning: .warning directive invoked in source file
    5 |  .warning                       ;# { dg-warning "Warning: .warning directive invoked in source file" }
      |
testsuite/gas/all/warn-1.s:6: warning: .warning directive invoked in source file
    6 |  .warning ".warning directive invoked in source file"   ;# { dg-warning "Warning: .warning directive invoked in source file" }
      |
testsuite/gas/all/warn-1.s:7: warning:
    7 |  .warning ""                    ;# { dg-warning "Warning: " }
      |
	{"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", "version": "2.1.0", "runs": [{"tool": {"driver": {"rules": []}}, "invocations": [{"executionSuccessful": true, "toolExecutionNotifications": []}], "originalUriBaseIds": {"PWD": {"uri": "file:///home/david/coding-3/binutils-gdb/gas/"}}, "artifacts": [{"location": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "contents": {"text": ";# Test .warning directive.\n;# { dg-do assemble }\n .warning \"a warning message\"\t;# { dg-warning \"Warning: a warning message\" }\n .warning a warning message\t;# { dg-error \"Error: .warning argument must be a string\" }\n .warning\t\t\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n .warning \".warning directive invoked in source file\"\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n .warning \"\"\t\t\t;# { dg-warning \"Warning: \" }\n"}}], "results": [{"ruleId": "warning", "level": "warning", "message": {"text": "a warning message"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 3, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 3, "snippet": {"text": " .warning \"a warning message\"\t;# { dg-warning \"Warning: a warning message\" }\n"}}}}], "relatedLocations": [{"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 4, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 4, "snippet": {"text": " .warning a warning message\t;# { dg-error \"Error: .warning argument must be a string\" }\n"}}}, "message": {"text": ".warning argument must be a string"}}, {"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 5, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 5, "snippet": {"text": " .warning\t\t\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n"}}}, "message": {"text": ".warning directive invoked in source file"}}, {"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 6, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 6, "snippet": {"text": " .warning \".warning directive invoked in source file\"\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n"}}}, "message": {"text": ".warning directive invoked in source file"}}, {"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 7, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 7, "snippet": {"text": " .warning \"\"\t\t\t;# { dg-warning \"Warning: \" }\n"}}}, "message": {"text": ""}}]}]}]}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

which I see:
- drops the leading "Assembler messages" warning,
- changes the capitalization of the "Warning" -> "warning" etc
- quotes the pertinent line in the .s file

All of the locations are just lines; does gas do column numbers at all?
(or ranges?)

For reference, running the above SARIF through "python -m json.tool" to
prettyify it gives:

VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
{
    "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
    "version": "2.1.0",
    "runs": [
        {
            "tool": {
                "driver": {
                    "rules": []
                }
            },
            "invocations": [
                {
                    "executionSuccessful": true,
                    "toolExecutionNotifications": []
                }
            ],
            "originalUriBaseIds": {
                "PWD": {
                    "uri": "file:///home/david/coding-3/binutils-gdb/gas/"
                }
            },
            "artifacts": [
                {
                    "location": {
                        "uri": "testsuite/gas/all/warn-1.s",
                        "uriBaseId": "PWD"
                    },
                    "contents": {
                        "text": ";# Test .warning directive.\n;# { dg-do assemble }\n .warning \"a warning message\"\t;# { dg-warning \"Warning: a warning message\" }\n .warning a warning message\t;# { dg-error \"Error: .warning argument must be a string\" }\n .warning\t\t\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n .warning \".warning directive invoked in source file\"\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n .warning \"\"\t\t\t;# { dg-warning \"Warning: \" }\n"
                    }
                }
            ],
            "results": [
                {
                    "ruleId": "warning",
                    "level": "warning",
                    "message": {
                        "text": "a warning message"
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "testsuite/gas/all/warn-1.s",
                                    "uriBaseId": "PWD"
                                },
                                "region": {
                                    "startLine": 3,
                                    "startColumn": 0,
                                    "endColumn": 1
                                },
                                "contextRegion": {
                                    "startLine": 3,
                                    "snippet": {
                                        "text": " .warning \"a warning message\"\t;# { dg-warning \"Warning: a warning message\" }\n"
                                    }
                                }
                            }
                        }
                    ],
                    "relatedLocations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "testsuite/gas/all/warn-1.s",
                                    "uriBaseId": "PWD"
                                },
                                "region": {
                                    "startLine": 4,
                                    "startColumn": 0,
                                    "endColumn": 1
                                },
                                "contextRegion": {
                                    "startLine": 4,
                                    "snippet": {
                                        "text": " .warning a warning message\t;# { dg-error \"Error: .warning argument must be a string\" }\n"
                                    }
                                }
                            },
                            "message": {
                                "text": ".warning argument must be a string"
                            }
                        },
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "testsuite/gas/all/warn-1.s",
                                    "uriBaseId": "PWD"
                                },
                                "region": {
                                    "startLine": 5,
                                    "startColumn": 0,
                                    "endColumn": 1
                                },
                                "contextRegion": {
                                    "startLine": 5,
                                    "snippet": {
                                        "text": " .warning\t\t\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n"
                                    }
                                }
                            },
                            "message": {
                                "text": ".warning directive invoked in source file"
                            }
                        },
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "testsuite/gas/all/warn-1.s",
                                    "uriBaseId": "PWD"
                                },
                                "region": {
                                    "startLine": 6,
                                    "startColumn": 0,
                                    "endColumn": 1
                                },
                                "contextRegion": {
                                    "startLine": 6,
                                    "snippet": {
                                        "text": " .warning \".warning directive invoked in source file\"\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n"
                                    }
                                }
                            },
                            "message": {
                                "text": ".warning directive invoked in source file"
                            }
                        },
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "testsuite/gas/all/warn-1.s",
                                    "uriBaseId": "PWD"
                                },
                                "region": {
                                    "startLine": 7,
                                    "startColumn": 0,
                                    "endColumn": 1
                                },
                                "contextRegion": {
                                    "startLine": 7,
                                    "snippet": {
                                        "text": " .warning \"\"\t\t\t;# { dg-warning \"Warning: \" }\n"
                                    }
                                }
                            },
                            "message": {
                                "text": ""
                            }
                        }
                    ]
                }
            ]
        }
    ]
}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Thoughts?

gas/ChangeLog:
	* Makefile.am (GASLIBS): Add nasty hack with hardcoded path
	on my hard drive.
	* Makefile.in (GASLIBS): Likewise.
	* as.c (gas_init): Call messages_init.
	(main): Call messages_end.
	* as.h (messages_init): New decl.
	(messages_end): New decl.
	* messages.c (USE_LIBDIAGNOSTICS): New define.
	Add #include with nasty hardcoded path.
	(diag_mgr): New.
	(messages_init): New.
	(messages_end): New.
	(as_warn_internal): Add support for libdiagnostics.
	(as_bad_internal): Likewise.
---
 gas/Makefile.am |  3 ++-
 gas/Makefile.in |  4 ++-
 gas/as.c        |  3 +++
 gas/as.h        |  3 +++
 gas/messages.c  | 68 +++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 79 insertions(+), 2 deletions(-)

diff --git a/gas/Makefile.am b/gas/Makefile.am
index 0e98ca3ec85..fe92997129c 100644
--- a/gas/Makefile.am
+++ b/gas/Makefile.am
@@ -408,7 +408,8 @@ AM_CPPFLAGS = -I. -I$(srcdir) -I../bfd -I$(srcdir)/config \
 # How to link with both our special library facilities
 # and the system's installed libraries.
 
-GASLIBS = @OPCODES_LIB@ ../bfd/libbfd.la ../libiberty/libiberty.a
+# FIXME:
+GASLIBS = @OPCODES_LIB@ ../bfd/libbfd.la ../libiberty/libiberty.a /home/david/coding-3/gcc-newgit-canvas-2023/build/gcc/libdiagnostics.so
 
 # Files to be copied away after each stage in building.
 STAGESTUFF = *.@OBJEXT@ $(noinst_PROGRAMS)
diff --git a/gas/Makefile.in b/gas/Makefile.in
index fae3a47c144..2161d68f9c7 100644
--- a/gas/Makefile.in
+++ b/gas/Makefile.in
@@ -885,7 +885,9 @@ AM_CPPFLAGS = -I. -I$(srcdir) -I../bfd -I$(srcdir)/config \
 
 # How to link with both our special library facilities
 # and the system's installed libraries.
-GASLIBS = @OPCODES_LIB@ ../bfd/libbfd.la ../libiberty/libiberty.a
+
+# FIXME:
+GASLIBS = @OPCODES_LIB@ ../bfd/libbfd.la ../libiberty/libiberty.a /home/david/coding-3/gcc-newgit-canvas-2023/build/gcc/libdiagnostics.so
 
 # Files to be copied away after each stage in building.
 STAGESTUFF = *.@OBJEXT@ $(noinst_PROGRAMS)
diff --git a/gas/as.c b/gas/as.c
index 6839c841588..2a8ce3734a0 100644
--- a/gas/as.c
+++ b/gas/as.c
@@ -1300,6 +1300,7 @@ gas_early_init (int *argcp, char ***argvp)
 static void
 gas_init (void)
 {
+  messages_init ();
   symbol_begin ();
   frag_init ();
   subsegs_begin ();
@@ -1486,6 +1487,8 @@ main (int argc, char ** argv)
 
   input_scrub_end ();
 
+  messages_end ();
+
   /* Use xexit instead of return, because under VMS environments they
      may not place the same interpretation on the value given.  */
   if (had_errors () != 0)
diff --git a/gas/as.h b/gas/as.h
index 99ffe77afd2..9d878a87df5 100644
--- a/gas/as.h
+++ b/gas/as.h
@@ -474,6 +474,9 @@ void   as_bad_value_out_of_range (const char *, offsetT, offsetT, offsetT,
 void   print_version_id (void);
 char * app_push (void);
 
+void messages_init (void);
+void messages_end (void);
+
 /* Number of littlenums required to hold an extended precision number.	*/
 #define MAX_LITTLENUMS 6
 
diff --git a/gas/messages.c b/gas/messages.c
index 7c018acf69f..3cb8525fad9 100644
--- a/gas/messages.c
+++ b/gas/messages.c
@@ -21,6 +21,14 @@
 #include <limits.h>
 #include <signal.h>
 
+// FIXME: do some configury thing for this
+#define USE_LIBDIAGNOSTICS 1
+
+#if USE_LIBDIAGNOSTICS
+// FIXME: need to fix this path, obviously
+#include "/home/david/coding-3/gcc-newgit-canvas-2023/src/gcc/libdiagnostics.h"
+#endif
+
 /* If the system doesn't provide strsignal, we get it defined in
    libiberty but no declaration is supplied.  Because, reasons. */
 #if !defined (HAVE_STRSIGNAL) && !defined (strsignal)
@@ -101,6 +109,29 @@ had_warnings (void)
   return warning_count;
 }
 
+#if USE_LIBDIAGNOSTICS
+static diagnostic_manager *diag_mgr;
+#endif
+
+void messages_init (void)
+{
+#if USE_LIBDIAGNOSTICS
+  diag_mgr = diagnostic_manager_new ();
+  diagnostic_manager_add_text_sink (diag_mgr, stderr,
+				    DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
+				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
+#endif
+}
+
+void messages_end (void)
+{
+#if USE_LIBDIAGNOSTICS
+  diagnostic_manager_release (diag_mgr);
+  diag_mgr = NULL;
+#endif
+}
+
 /* Nonzero if we've hit a 'bad error', and should not write an obj file,
    and exit with a nonzero error code.  */
 
@@ -172,16 +203,34 @@ as_tsktsk (const char *format, ...)
 static void
 as_warn_internal (const char *file, unsigned int line, char *buffer)
 {
+#if !USE_LIBDIAGNOSTICS
   bool context = false;
+#endif
 
   ++warning_count;
 
   if (file == NULL)
     {
       file = as_where_top (&line);
+#if !USE_LIBDIAGNOSTICS
       context = true;
+#endif
     }
 
+#if USE_LIBDIAGNOSTICS
+  const diagnostic_file *file_obj
+    = diagnostic_manager_new_file (diag_mgr, file, NULL);
+
+  diagnostic_location_t loc
+    = diagnostic_manager_new_location_from_file_and_line (diag_mgr,
+							  file_obj,
+							  line);
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_WARNING);
+  diagnostic_set_location (d, loc);
+  diagnostic_finish (d, "%s", buffer);
+#else
   identify (file);
   if (file)
     {
@@ -199,6 +248,7 @@ as_warn_internal (const char *file, unsigned int line, char *buffer)
 #ifndef NO_LISTING
   listing_warning (buffer);
 #endif
+#endif /* #else clause of #if USE_LIBDIAGNOSTICS */
 }
 
 /* Send to stderr a string as a warning, and locate warning
@@ -246,16 +296,33 @@ as_warn_where (const char *file, unsigned int line, const char *format, ...)
 static void
 as_bad_internal (const char *file, unsigned int line, char *buffer)
 {
+#if !USE_LIBDIAGNOSTICS
   bool context = false;
+#endif
 
   ++error_count;
 
   if (file == NULL)
     {
       file = as_where_top (&line);
+#if !USE_LIBDIAGNOSTICS
       context = true;
+#endif
     }
 
+#if USE_LIBDIAGNOSTICS
+  const diagnostic_file *file_obj
+    = diagnostic_manager_new_file (diag_mgr, file, NULL);
+  diagnostic_location_t loc
+    = diagnostic_manager_new_location_from_file_and_line (diag_mgr,
+							  file_obj,
+							  line);
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_ERROR);
+  diagnostic_set_location (d, loc);
+  diagnostic_finish (d, "%s", buffer);
+#else
   identify (file);
   if (file)
     {
@@ -269,6 +336,7 @@ as_bad_internal (const char *file, unsigned int line, char *buffer)
 
   if (context)
     as_report_context ();
+#endif /* #else clause of #if USE_LIBDIAGNOSTICS */
 
 #ifndef NO_LISTING
   listing_error (buffer);
-- 
2.26.3


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH] binutils: experimental use of libdiagnostics in gas
  2023-11-06 22:29 ` [PATCH] binutils: experimental use of libdiagnostics in gas David Malcolm
@ 2023-11-07  7:04   ` Simon Sobisch
  2023-11-07 14:51     ` David Malcolm
  2023-11-07  9:21   ` Clément Chigot
  2023-11-07 10:03   ` Jan Beulich
  2 siblings, 1 reply; 29+ messages in thread
From: Simon Sobisch @ 2023-11-07  7:04 UTC (permalink / raw)
  To: David Malcolm, gcc-patches, binutils; +Cc: Nick Clifton

Thank you very much for this proof-of-concept use!

Inspecting it raises the following questions to me, both for a possible 
binutils implementation and for the library use in general:

* How should the application set the relevant context (often lines are
   shown before/after)?
* Should it be possible to override msgid used to display the
   warning/error type?
   If this would be possible then the text sink in messages_init may be
   adjusted to replace the label with _("Warning") and _("Error"), which
   would leave the text output "as-is" (if the text sink is configured to
   not output the source line); this would make it usable without
   adjusting the testsuite and to adopt to a standard later.


Notes for the SARIF output:
* the region contains an error, according to the linked JSON spec
   startColumn has a minimum of 1 (I guess you'd just leave it away if
   the application did not set it)
* the application should have the option to pre-set the sourceLanguage
   for the diagnostic_manager (maybe even make that a positional argument
   that needs to be passed but can be NULL) and override it when
   specifying a region

Thanks,
Simon

Am 06.11.2023 um 23:29 schrieb David Malcolm:
> Here's a patch for gas in binutils that makes it use libdiagnostics
> (with some nasty hardcoded paths to specific places on my hard drive
> to make it easier to develop the API).
> 
> For now this hardcodes adding two sinks: a text sink on stderr, and
> also a SARIF output to stderr (which happens after all regular output).
> 
> For example, without this patch:
> 
>     gas testsuite/gas/all/warn-1.s
> 
> emits:
> VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
> testsuite/gas/all/warn-1.s: Assembler messages:
> testsuite/gas/all/warn-1.s:3: Warning: a warning message
> testsuite/gas/all/warn-1.s:4: Error: .warning argument must be a string
> testsuite/gas/all/warn-1.s:5: Warning: .warning directive invoked in source file
> testsuite/gas/all/warn-1.s:6: Warning: .warning directive invoked in source file
> testsuite/gas/all/warn-1.s:7: Warning:
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 
> whereas with this patch:
>    LD_LIBRARY_PATH=/home/david/coding-3/gcc-newgit-canvas-2023/build/gcc ./as-new testsuite/gas/all/warn-1.s
> emits:
> 
> VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
> testsuite/gas/all/warn-1.s:3: warning: a warning message
>      3 |  .warning "a warning message"   ;# { dg-warning "Warning: a warning message" }
>        |
> testsuite/gas/all/warn-1.s:4: error: .warning argument must be a string
>      4 |  .warning a warning message     ;# { dg-error "Error: .warning argument must be a string" }
>        |
> testsuite/gas/all/warn-1.s:5: warning: .warning directive invoked in source file
>      5 |  .warning                       ;# { dg-warning "Warning: .warning directive invoked in source file" }
>        |
> testsuite/gas/all/warn-1.s:6: warning: .warning directive invoked in source file
>      6 |  .warning ".warning directive invoked in source file"   ;# { dg-warning "Warning: .warning directive invoked in source file" }
>        |
> testsuite/gas/all/warn-1.s:7: warning:
>      7 |  .warning ""                    ;# { dg-warning "Warning: " }
>        |
> 	{"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", "version": "2.1.0", "runs": [{"tool": {"driver": {"rules": []}}, "invocations": [{"executionSuccessful": true, "toolExecutionNotifications": []}], "originalUriBaseIds": {"PWD": {"uri": "file:///home/david/coding-3/binutils-gdb/gas/"}}, "artifacts": [{"location": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "contents": {"text": ";# Test .warning directive.\n;# { dg-do assemble }\n .warning \"a warning message\"\t;# { dg-warning \"Warning: a warning message\" }\n .warning a warning message\t;# { dg-error \"Error: .warning argument must be a string\" }\n .warning\t\t\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n .warning \".warning directive invoked in source file\"\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n .warning \"\"\t\t\t;# { dg-warning \"Warning: \" }\n"}}], "results": [{"ruleId": "warning", "level": "warning", "message": {"text": "a warning message"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 3, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 3, "snippet": {"text": " .warning \"a warning message\"\t;# { dg-warning \"Warning: a warning message\" }\n"}}}}], "relatedLocations": [{"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 4, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 4, "snippet": {"text": " .warning a warning message\t;# { dg-error \"Error: .warning argument must be a string\" }\n"}}}, "message": {"text": ".warning argument must be a string"}}, {"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 5, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 5, "snippet": {"text": " .warning\t\t\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n"}}}, "message": {"text": ".warning directive invoked in source file"}}, {"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 6, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 6, "snippet": {"text": " .warning \".warning directive invoked in source file\"\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n"}}}, "message": {"text": ".warning directive invoked in source file"}}, {"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 7, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 7, "snippet": {"text": " .warning \"\"\t\t\t;# { dg-warning \"Warning: \" }\n"}}}, "message": {"text": ""}}]}]}]}
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 
> which I see:
> - drops the leading "Assembler messages" warning,
> - changes the capitalization of the "Warning" -> "warning" etc
> - quotes the pertinent line in the .s file
> 
> All of the locations are just lines; does gas do column numbers at all?
> (or ranges?)
> 
> For reference, running the above SARIF through "python -m json.tool" to
> prettyify it gives:
> 
> VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
> {
>      "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
>      "version": "2.1.0",
>      "runs": [
>          {
>              "tool": {
>                  "driver": {
>                      "rules": []
>                  }
>              },
>              "invocations": [
>                  {
>                      "executionSuccessful": true,
>                      "toolExecutionNotifications": []
>                  }
>              ],
>              "originalUriBaseIds": {
>                  "PWD": {
>                      "uri": "file:///home/david/coding-3/binutils-gdb/gas/"
>                  }
>              },
>              "artifacts": [
>                  {
>                      "location": {
>                          "uri": "testsuite/gas/all/warn-1.s",
>                          "uriBaseId": "PWD"
>                      },
>                      "contents": {
>                          "text": ";# Test .warning directive.\n;# { dg-do assemble }\n .warning \"a warning message\"\t;# { dg-warning \"Warning: a warning message\" }\n .warning a warning message\t;# { dg-error \"Error: .warning argument must be a string\" }\n .warning\t\t\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n .warning \".warning directive invoked in source file\"\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n .warning \"\"\t\t\t;# { dg-warning \"Warning: \" }\n"
>                      }
>                  }
>              ],
>              "results": [
>                  {
>                      "ruleId": "warning",
>                      "level": "warning",
>                      "message": {
>                          "text": "a warning message"
>                      },
>                      "locations": [
>                          {
>                              "physicalLocation": {
>                                  "artifactLocation": {
>                                      "uri": "testsuite/gas/all/warn-1.s",
>                                      "uriBaseId": "PWD"
>                                  },
>                                  "region": {
>                                      "startLine": 3,
>                                      "startColumn": 0,
>                                      "endColumn": 1
>                                  },
>                                  "contextRegion": {
>                                      "startLine": 3,
>                                      "snippet": {
>                                          "text": " .warning \"a warning message\"\t;# { dg-warning \"Warning: a warning message\" }\n"
>                                      }
>                                  }
>                              }
>                          }
>                      ],
>                      "relatedLocations": [
>                          {
>                              "physicalLocation": {
>                                  "artifactLocation": {
>                                      "uri": "testsuite/gas/all/warn-1.s",
>                                      "uriBaseId": "PWD"
>                                  },
>                                  "region": {
>                                      "startLine": 4,
>                                      "startColumn": 0,
>                                      "endColumn": 1
>                                  },
>                                  "contextRegion": {
>                                      "startLine": 4,
>                                      "snippet": {
>                                          "text": " .warning a warning message\t;# { dg-error \"Error: .warning argument must be a string\" }\n"
>                                      }
>                                  }
>                              },
>                              "message": {
>                                  "text": ".warning argument must be a string"
>                              }
>                          },
>                          {
>                              "physicalLocation": {
>                                  "artifactLocation": {
>                                      "uri": "testsuite/gas/all/warn-1.s",
>                                      "uriBaseId": "PWD"
>                                  },
>                                  "region": {
>                                      "startLine": 5,
>                                      "startColumn": 0,
>                                      "endColumn": 1
>                                  },
>                                  "contextRegion": {
>                                      "startLine": 5,
>                                      "snippet": {
>                                          "text": " .warning\t\t\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n"
>                                      }
>                                  }
>                              },
>                              "message": {
>                                  "text": ".warning directive invoked in source file"
>                              }
>                          },
>                          {
>                              "physicalLocation": {
>                                  "artifactLocation": {
>                                      "uri": "testsuite/gas/all/warn-1.s",
>                                      "uriBaseId": "PWD"
>                                  },
>                                  "region": {
>                                      "startLine": 6,
>                                      "startColumn": 0,
>                                      "endColumn": 1
>                                  },
>                                  "contextRegion": {
>                                      "startLine": 6,
>                                      "snippet": {
>                                          "text": " .warning \".warning directive invoked in source file\"\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n"
>                                      }
>                                  }
>                              },
>                              "message": {
>                                  "text": ".warning directive invoked in source file"
>                              }
>                          },
>                          {
>                              "physicalLocation": {
>                                  "artifactLocation": {
>                                      "uri": "testsuite/gas/all/warn-1.s",
>                                      "uriBaseId": "PWD"
>                                  },
>                                  "region": {
>                                      "startLine": 7,
>                                      "startColumn": 0,
>                                      "endColumn": 1
>                                  },
>                                  "contextRegion": {
>                                      "startLine": 7,
>                                      "snippet": {
>                                          "text": " .warning \"\"\t\t\t;# { dg-warning \"Warning: \" }\n"
>                                      }
>                                  }
>                              },
>                              "message": {
>                                  "text": ""
>                              }
>                          }
>                      ]
>                  }
>              ]
>          }
>      ]
> }
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 
> Thoughts?
> 
> gas/ChangeLog:
> 	* Makefile.am (GASLIBS): Add nasty hack with hardcoded path
> 	on my hard drive.
> 	* Makefile.in (GASLIBS): Likewise.
> 	* as.c (gas_init): Call messages_init.
> 	(main): Call messages_end.
> 	* as.h (messages_init): New decl.
> 	(messages_end): New decl.
> 	* messages.c (USE_LIBDIAGNOSTICS): New define.
> 	Add #include with nasty hardcoded path.
> 	(diag_mgr): New.
> 	(messages_init): New.
> 	(messages_end): New.
> 	(as_warn_internal): Add support for libdiagnostics.
> 	(as_bad_internal): Likewise.
> ---
>   gas/Makefile.am |  3 ++-
>   gas/Makefile.in |  4 ++-
>   gas/as.c        |  3 +++
>   gas/as.h        |  3 +++
>   gas/messages.c  | 68 +++++++++++++++++++++++++++++++++++++++++++++++++
>   5 files changed, 79 insertions(+), 2 deletions(-)
> 
> diff --git a/gas/Makefile.am b/gas/Makefile.am
> index 0e98ca3ec85..fe92997129c 100644
> --- a/gas/Makefile.am
> +++ b/gas/Makefile.am
> @@ -408,7 +408,8 @@ AM_CPPFLAGS = -I. -I$(srcdir) -I../bfd -I$(srcdir)/config \
>   # How to link with both our special library facilities
>   # and the system's installed libraries.
>   
> -GASLIBS = @OPCODES_LIB@ ../bfd/libbfd.la ../libiberty/libiberty.a
> +# FIXME:
> +GASLIBS = @OPCODES_LIB@ ../bfd/libbfd.la ../libiberty/libiberty.a /home/david/coding-3/gcc-newgit-canvas-2023/build/gcc/libdiagnostics.so
>   
>   # Files to be copied away after each stage in building.
>   STAGESTUFF = *.@OBJEXT@ $(noinst_PROGRAMS)
> diff --git a/gas/Makefile.in b/gas/Makefile.in
> index fae3a47c144..2161d68f9c7 100644
> --- a/gas/Makefile.in
> +++ b/gas/Makefile.in
> @@ -885,7 +885,9 @@ AM_CPPFLAGS = -I. -I$(srcdir) -I../bfd -I$(srcdir)/config \
>   
>   # How to link with both our special library facilities
>   # and the system's installed libraries.
> -GASLIBS = @OPCODES_LIB@ ../bfd/libbfd.la ../libiberty/libiberty.a
> +
> +# FIXME:
> +GASLIBS = @OPCODES_LIB@ ../bfd/libbfd.la ../libiberty/libiberty.a /home/david/coding-3/gcc-newgit-canvas-2023/build/gcc/libdiagnostics.so
>   
>   # Files to be copied away after each stage in building.
>   STAGESTUFF = *.@OBJEXT@ $(noinst_PROGRAMS)
> diff --git a/gas/as.c b/gas/as.c
> index 6839c841588..2a8ce3734a0 100644
> --- a/gas/as.c
> +++ b/gas/as.c
> @@ -1300,6 +1300,7 @@ gas_early_init (int *argcp, char ***argvp)
>   static void
>   gas_init (void)
>   {
> +  messages_init ();
>     symbol_begin ();
>     frag_init ();
>     subsegs_begin ();
> @@ -1486,6 +1487,8 @@ main (int argc, char ** argv)
>   
>     input_scrub_end ();
>   
> +  messages_end ();
> +
>     /* Use xexit instead of return, because under VMS environments they
>        may not place the same interpretation on the value given.  */
>     if (had_errors () != 0)
> diff --git a/gas/as.h b/gas/as.h
> index 99ffe77afd2..9d878a87df5 100644
> --- a/gas/as.h
> +++ b/gas/as.h
> @@ -474,6 +474,9 @@ void   as_bad_value_out_of_range (const char *, offsetT, offsetT, offsetT,
>   void   print_version_id (void);
>   char * app_push (void);
>   
> +void messages_init (void);
> +void messages_end (void);
> +
>   /* Number of littlenums required to hold an extended precision number.	*/
>   #define MAX_LITTLENUMS 6
>   
> diff --git a/gas/messages.c b/gas/messages.c
> index 7c018acf69f..3cb8525fad9 100644
> --- a/gas/messages.c
> +++ b/gas/messages.c
> @@ -21,6 +21,14 @@
>   #include <limits.h>
>   #include <signal.h>
>   
> +// FIXME: do some configury thing for this
> +#define USE_LIBDIAGNOSTICS 1
> +
> +#if USE_LIBDIAGNOSTICS
> +// FIXME: need to fix this path, obviously
> +#include "/home/david/coding-3/gcc-newgit-canvas-2023/src/gcc/libdiagnostics.h"
> +#endif
> +
>   /* If the system doesn't provide strsignal, we get it defined in
>      libiberty but no declaration is supplied.  Because, reasons. */
>   #if !defined (HAVE_STRSIGNAL) && !defined (strsignal)
> @@ -101,6 +109,29 @@ had_warnings (void)
>     return warning_count;
>   }
>   
> +#if USE_LIBDIAGNOSTICS
> +static diagnostic_manager *diag_mgr;
> +#endif
> +
> +void messages_init (void)
> +{
> +#if USE_LIBDIAGNOSTICS
> +  diag_mgr = diagnostic_manager_new ();
> +  diagnostic_manager_add_text_sink (diag_mgr, stderr,
> +				    DIAGNOSTIC_COLORIZE_IF_TTY);
> +  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
> +				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
> +#endif
> +}
> +
> +void messages_end (void)
> +{
> +#if USE_LIBDIAGNOSTICS
> +  diagnostic_manager_release (diag_mgr);
> +  diag_mgr = NULL;
> +#endif
> +}
> +
>   /* Nonzero if we've hit a 'bad error', and should not write an obj file,
>      and exit with a nonzero error code.  */
>   
> @@ -172,16 +203,34 @@ as_tsktsk (const char *format, ...)
>   static void
>   as_warn_internal (const char *file, unsigned int line, char *buffer)
>   {
> +#if !USE_LIBDIAGNOSTICS
>     bool context = false;
> +#endif
>   
>     ++warning_count;
>   
>     if (file == NULL)
>       {
>         file = as_where_top (&line);
> +#if !USE_LIBDIAGNOSTICS
>         context = true;
> +#endif
>       }
>   
> +#if USE_LIBDIAGNOSTICS
> +  const diagnostic_file *file_obj
> +    = diagnostic_manager_new_file (diag_mgr, file, NULL);
> +
> +  diagnostic_location_t loc
> +    = diagnostic_manager_new_location_from_file_and_line (diag_mgr,
> +							  file_obj,
> +							  line);
> +
> +  diagnostic *d = diagnostic_begin (diag_mgr,
> +				    DIAGNOSTIC_LEVEL_WARNING);
> +  diagnostic_set_location (d, loc);
> +  diagnostic_finish (d, "%s", buffer);
> +#else
>     identify (file);
>     if (file)
>       {
> @@ -199,6 +248,7 @@ as_warn_internal (const char *file, unsigned int line, char *buffer)
>   #ifndef NO_LISTING
>     listing_warning (buffer);
>   #endif
> +#endif /* #else clause of #if USE_LIBDIAGNOSTICS */
>   }
>   
>   /* Send to stderr a string as a warning, and locate warning
> @@ -246,16 +296,33 @@ as_warn_where (const char *file, unsigned int line, const char *format, ...)
>   static void
>   as_bad_internal (const char *file, unsigned int line, char *buffer)
>   {
> +#if !USE_LIBDIAGNOSTICS
>     bool context = false;
> +#endif
>   
>     ++error_count;
>   
>     if (file == NULL)
>       {
>         file = as_where_top (&line);
> +#if !USE_LIBDIAGNOSTICS
>         context = true;
> +#endif
>       }
>   
> +#if USE_LIBDIAGNOSTICS
> +  const diagnostic_file *file_obj
> +    = diagnostic_manager_new_file (diag_mgr, file, NULL);
> +  diagnostic_location_t loc
> +    = diagnostic_manager_new_location_from_file_and_line (diag_mgr,
> +							  file_obj,
> +							  line);
> +
> +  diagnostic *d = diagnostic_begin (diag_mgr,
> +				    DIAGNOSTIC_LEVEL_ERROR);
> +  diagnostic_set_location (d, loc);
> +  diagnostic_finish (d, "%s", buffer);
> +#else
>     identify (file);
>     if (file)
>       {
> @@ -269,6 +336,7 @@ as_bad_internal (const char *file, unsigned int line, char *buffer)
>   
>     if (context)
>       as_report_context ();
> +#endif /* #else clause of #if USE_LIBDIAGNOSTICS */
>   
>   #ifndef NO_LISTING
>     listing_error (buffer);

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 2/2] libdiagnostics: work-in-progress implementation
  2023-11-06 22:29 ` [PATCH 2/2] libdiagnostics: work-in-progress implementation David Malcolm
@ 2023-11-07  7:54   ` Simon Sobisch
  2023-11-07 14:59     ` David Malcolm
  0 siblings, 1 reply; 29+ messages in thread
From: Simon Sobisch @ 2023-11-07  7:54 UTC (permalink / raw)
  To: David Malcolm, gcc-patches, binutils; +Cc: Nick Clifton

Thank you for our work and providing this patch.

GCC related questions:

Is it planned to change GCC diagnostics to use libdiagnostic itself?

Is it planned to "directly" add features or would the result for GCC be 
identical (apart from build changes)?

So far it looks like it wouldn't be possible to "just build 
libdiagnostics", and much less to "just distrubute its source" for that 
purpose, is it?
As building GCC does take a significant amount of resources and 
system-wide switching to a new GCC version is considered a serious task 
(distributions commonly stay with their major GCC version for quite some 
time), I'd search for an option to building a "self-contained" version 
that does not need the complete necessary toolset and may also be 
distributed separately.

This definitely can come later, too; I _guess_ this would mean moving 
part of GCCs code in a sub-folder libdiagnostics and use it as 
subproject for configure/make, with then option to run "make dist" in 
that subfolder alone, too.

The main reason for that would be to allow applications move from their 
previous own diagnostic to libdiagnostics, if it isn't available on the 
system they can build and install it as subproject, too; and to be able 
to build libdiagnostics with a much reduced dependency list.


Thank you for considering that,
Simon

Am 06.11.2023 um 23:29 schrieb David Malcolm:
> Here's a work-in-progress patch for GCC that adds the implementation
> of libdiagnostics.  Various aspects of this need work; posting now
> for early feedback on overall direction.  For example, the testsuite
> doesn't yet check the output from the test client programs (and I'm
> not quite sure of the best way to express that in DejaGnu).
> 
> gcc/ChangeLog:
> 	* Makefile.in (lang_checks): Add check-libdiagnostics.
> 	(start.encap): Add libdiagnostics.
> 	(libdiagnostics_OBJS): New.
> 	...plus a bunch of stuff hacked up from jit/Make-lang.in.
> 	* configure: Regenerate.
> 	* configure.ac (check_languages): Add check-libdiagnostics.
> 	* input.h: Add FIXME.
> 	* libdiagnostics.cc: New file.
> 	* libdiagnostics.map: New file.
> 
> gcc/testsuite/ChangeLog:
> 	* libdiagnostics.dg/libdiagnostics.exp: New, based on jit.exp.
> ---
>   gcc/Makefile.in                               |  134 +-
>   gcc/configure                                 |    2 +-
>   gcc/configure.ac                              |    2 +-
>   gcc/input.h                                   |    2 +-
>   gcc/libdiagnostics.cc                         | 1124 +++++++++++++++++
>   gcc/libdiagnostics.map                        |   57 +
>   .../libdiagnostics.dg/libdiagnostics.exp      |  544 ++++++++
>   7 files changed, 1860 insertions(+), 5 deletions(-)
>   create mode 100644 gcc/libdiagnostics.cc
>   create mode 100644 gcc/libdiagnostics.map
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp
> 
> diff --git a/gcc/Makefile.in b/gcc/Makefile.in
> index ff77d3cdc64..8f93ae48024 100644
> --- a/gcc/Makefile.in
> +++ b/gcc/Makefile.in
> @@ -611,7 +611,7 @@ host_xm_defines=@host_xm_defines@
>   xm_file_list=@xm_file_list@
>   xm_include_list=@xm_include_list@
>   xm_defines=@xm_defines@
> -lang_checks=
> +lang_checks=check-libdiagnostics
>   lang_checks_parallelized=
>   lang_opt_files=@lang_opt_files@ $(srcdir)/c-family/c.opt $(srcdir)/common.opt $(srcdir)/params.opt $(srcdir)/analyzer/analyzer.opt
>   lang_specs_files=@lang_specs_files@
> @@ -2153,7 +2153,7 @@ all.cross: native gcc-cross$(exeext) cpp$(exeext) specs \
>   	libgcc-support lang.all.cross doc selftest @GENINSRC@ srcextra
>   # This is what must be made before installing GCC and converting libraries.
>   start.encap: native xgcc$(exeext) cpp$(exeext) specs \
> -	libgcc-support lang.start.encap @GENINSRC@ srcextra
> +	libgcc-support lang.start.encap libdiagnostics @GENINSRC@ srcextra
>   # These can't be made until after GCC can run.
>   rest.encap: lang.rest.encap
>   # This is what is made with the host's compiler
> @@ -2242,6 +2242,136 @@ cpp$(exeext): $(GCC_OBJS) c-family/cppspec.o libcommon-target.a $(LIBDEPS) \
>   	  c-family/cppspec.o $(EXTRA_GCC_OBJS) libcommon-target.a \
>   	  $(EXTRA_GCC_LIBS) $(LIBS)
>   
> +
> +libdiagnostics_OBJS = libdiagnostics.o \
> +	libcommon.a
> +
> +# FIXME:
> +# Define the names for selecting jit in LANGUAGES.
> +# Note that it would be nice to move the dependency on g++
> +# into the jit rule, but that needs a little bit of work
> +# to do the right thing within all.cross.
> +
> +LIBDIAGNOSTICS_VERSION_NUM = 0
> +LIBDIAGNOSTICS_MINOR_NUM = 0
> +LIBDIAGNOSTICS_RELEASE_NUM = 1
> +
> +ifneq (,$(findstring mingw,$(target)))
> +LIBDIAGNOSTICS_FILENAME = libdiagnostics-$(LIBDIAGNOSTICS_VERSION_NUM).dll
> +LIBDIAGNOSTICS_IMPORT_LIB = libdiagnostics.dll.a
> +
> +libdiagnostics: $(LIBDIAGNOSTICS_FILENAME) \
> +	$(FULL_DRIVER_NAME)
> +
> +else
> +
> +ifneq (,$(findstring darwin,$(host)))
> +
> +LIBDIAGNOSTICS_AGE = 1
> +LIBDIAGNOSTICS_BASENAME = libdiagnostics
> +
> +LIBDIAGNOSTICS_SONAME = \
> +  ${libdir}/$(LIBDIAGNOSTICS_BASENAME).$(LIBDIAGNOSTICS_VERSION_NUM).dylib
> +LIBDIAGNOSTICS_FILENAME = $(LIBDIAGNOSTICS_BASENAME).$(LIBDIAGNOSTICS_VERSION_NUM).dylib
> +LIBDIAGNOSTICS_LINKER_NAME = $(LIBDIAGNOSTICS_BASENAME).dylib
> +
> +# Conditionalize the use of the LD_VERSION_SCRIPT_OPTION and
> +# LD_SONAME_OPTION depending if configure found them, using $(if)
> +# We have to define a LIBDIAGNOSTICS_COMMA here, otherwise the commas in the "true"
> +# result are treated as separators by the $(if).
> +LIBDIAGNOSTICS_COMMA := ,
> +LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION = \
> +	$(if $(LD_VERSION_SCRIPT_OPTION),\
> +	  -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_VERSION_SCRIPT_OPTION)$(LIBDIAGNOSTICS_COMMA)$(srcdir)/libdiagnostics.map)
> +
> +LIBDIAGNOSTICS_SONAME_OPTION = \
> +	$(if $(LD_SONAME_OPTION), \
> +	     -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_SONAME_OPTION)$(LIBDIAGNOSTICS_COMMA)$(LIBDIAGNOSTICS_SONAME))
> +
> +LIBDIAGNOSTICS_SONAME_SYMLINK = $(LIBDIAGNOSTICS_FILENAME)
> +LIBDIAGNOSTICS_LINKER_NAME_SYMLINK = $(LIBDIAGNOSTICS_LINKER_NAME)
> +
> +libdiagnostics: $(LIBDIAGNOSTICS_FILENAME) \
> +	$(LIBDIAGNOSTICS_SYMLINK) \
> +	$(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK) \
> +	$(FULL_DRIVER_NAME)
> +
> +else
> +
> +LIBDIAGNOSTICS_LINKER_NAME = libdiagnostics.so
> +LIBDIAGNOSTICS_SONAME = $(LIBDIAGNOSTICS_LINKER_NAME).$(LIBDIAGNOSTICS_VERSION_NUM)
> +LIBDIAGNOSTICS_FILENAME = \
> +  $(LIBDIAGNOSTICS_SONAME).$(LIBDIAGNOSTICS_MINOR_NUM).$(LIBDIAGNOSTICS_RELEASE_NUM)
> +
> +LIBDIAGNOSTICS_LINKER_NAME_SYMLINK = $(LIBDIAGNOSTICS_LINKER_NAME)
> +LIBDIAGNOSTICS_SONAME_SYMLINK = $(LIBDIAGNOSTICS_SONAME)
> +
> +# Conditionalize the use of the LD_VERSION_SCRIPT_OPTION and
> +# LD_SONAME_OPTION depending if configure found them, using $(if)
> +# We have to define a LIBDIAGNOSTICS_COMMA here, otherwise the commas in the "true"
> +# result are treated as separators by the $(if).
> +LIBDIAGNOSTICS_COMMA := ,
> +LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION = \
> +	$(if $(LD_VERSION_SCRIPT_OPTION),\
> +	  -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_VERSION_SCRIPT_OPTION)$(LIBDIAGNOSTICS_COMMA)$(srcdir)/libdiagnostics.map)
> +
> +LIBDIAGNOSTICS_SONAME_OPTION = \
> +	$(if $(LD_SONAME_OPTION), \
> +	     -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_SONAME_OPTION)$(LIBDIAGNOSTICS_COMMA)$(LIBDIAGNOSTICS_SONAME))
> +
> +libdiagnostics: $(LIBDIAGNOSTICS_FILENAME) \
> +	$(LIBDIAGNOSTICS_SYMLINK) \
> +	$(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK) \
> +	$(FULL_DRIVER_NAME)
> +
> +endif
> +endif
> +
> +libdiagnostics.serial = $(LIBDIAGNOSTICS_FILENAME)
> +
> +# Tell GNU make to ignore these if they exist.
> +.PHONY: libdiagnostics
> +
> +ifneq (,$(findstring mingw,$(target)))
> +# Create import library
> +LIBDIAGNOSTICS_EXTRA_OPTS = -Wl,--out-implib,$(LIBDIAGNOSTICS_IMPORT_LIB)
> +else
> +
> +ifneq (,$(findstring darwin,$(host)))
> +# TODO : Construct a Darwin-style symbol export file.
> +LIBDIAGNOSTICS_EXTRA_OPTS = -Wl,-compatibility_version,$(LIBDIAGNOSTICS_VERSION_NUM) \
> +	-Wl,-current_version,$(LIBDIAGNOSTICS_VERSION_NUM).$(LIBDIAGNOSTICS_MINOR_NUM).$(LIBDIAGNOSTICS_AGE) \
> +	$(LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION) \
> +	$(LIBDIAGNOSTICS_SONAME_OPTION)
> +else
> +
> +LIBDIAGNOSTICS_EXTRA_OPTS = $(LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION) \
> +	$(LIBDIAGNOSTICS_SONAME_OPTION)
> +endif
> +endif
> +
> +$(LIBDIAGNOSTICS_FILENAME): $(libdiagnostics_OBJS) $(CPPLIB) $(EXTRA_GCC_LIBS) $(LIBS) \
> +	$(LIBDEPS) $(srcdir)/libdiagnostics.map
> +	@$(call LINK_PROGRESS,$(INDEX.libdiagnostics),start)
> +	+$(LLINKER) $(ALL_LINKERFLAGS) $(LDFLAGS) -o $@ -shared \
> +	     $(libdiagnostics_OBJS) \
> +	     $(CPPLIB) $(EXTRA_GCC_LIBS) $(LIBS) \
> +	     $(LIBDIAGNOSTICS_EXTRA_OPTS)
> +	@$(call LINK_PROGRESS,$(INDEX.libdiagnostics),end)
> +
> +# Create symlinks when not building for Windows
> +ifeq (,$(findstring mingw,$(target)))
> +
> +ifeq (,$(findstring darwin,$(host)))
> +# but only one level for Darwin, version info is embedded.
> +$(LIBDIAGNOSTICS_SONAME_SYMLINK): $(LIBDIAGNOSTICS_FILENAME)
> +	ln -sf $(LIBDIAGNOSTICS_FILENAME) $(LIBDIAGNOSTICS_SONAME_SYMLINK)
> +endif
> +
> +$(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK): $(LIBDIAGNOSTICS_SONAME_SYMLINK)
> +	ln -sf $(LIBDIAGNOSTICS_SONAME_SYMLINK) $(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK)
> +endif
> +
>   # Dump a specs file to make -B./ read these specs over installed ones.
>   $(SPECS): xgcc$(exeext)
>   	$(GCC_FOR_TARGET) -dumpspecs > tmp-specs
> diff --git a/gcc/configure b/gcc/configure
> index 77f33ee4df6..abcb70b1b5f 100755
> --- a/gcc/configure
> +++ b/gcc/configure
> @@ -31997,7 +31997,7 @@ $as_echo "#define ENABLE_LTO 1" >>confdefs.h
>   	esac
>   done
>   
> -check_languages=
> +check_languages=check-libdiagnostics
>   for language in $all_selected_languages
>   do
>   	check_languages="$check_languages check-$language"
> diff --git a/gcc/configure.ac b/gcc/configure.ac
> index 10982cdfc09..5968670b2d6 100644
> --- a/gcc/configure.ac
> +++ b/gcc/configure.ac
> @@ -7281,7 +7281,7 @@ changequote([,])dnl
>   	esac
>   done
>   
> -check_languages=
> +check_languages=check-libdiagnostics
>   for language in $all_selected_languages
>   do
>   	check_languages="$check_languages check-$language"
> diff --git a/gcc/input.h b/gcc/input.h
> index 5eac1dc40a6..e4d230cc112 100644
> --- a/gcc/input.h
> +++ b/gcc/input.h
> @@ -113,10 +113,10 @@ class char_span
>     size_t m_n_elts;
>   };
>   
> +// FIXME: eliminate these; use global_dc->m_file_cache
>   extern char_span location_get_source_line (const char *file_path, int line);
>   extern char *get_source_text_between (location_t, location_t);
>   extern char_span get_source_file_content (const char *file_path);
> -
>   extern bool location_missing_trailing_newline (const char *file_path);
>   
>   /* Forward decl of slot within file_cache, so that the definition doesn't
> diff --git a/gcc/libdiagnostics.cc b/gcc/libdiagnostics.cc
> new file mode 100644
> index 00000000000..788b28edc53
> --- /dev/null
> +++ b/gcc/libdiagnostics.cc
> @@ -0,0 +1,1124 @@
> +/* C++ implementation of a pure C API for emitting diagnostics.
> +   Copyright (C) 2023 Free Software Foundation, Inc.
> +
> +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/>.  */
> +
> +#include "config.h"
> +#define INCLUDE_MEMORY
> +#define INCLUDE_VECTOR
> +#include "system.h"
> +#include "coretypes.h"
> +#include "intl.h"
> +#include "diagnostic.h"
> +#include "diagnostic-color.h"
> +#include "diagnostic-url.h"
> +#include "diagnostic-metadata.h"
> +#include "diagnostic-client-data-hooks.h"
> +#include "logical-location.h"
> +#include "edit-context.h"
> +#include "make-unique.h"
> +#include "libdiagnostics.h"
> +
> +class owned_nullable_string
> +{
> +public:
> +  owned_nullable_string () : m_str (nullptr) {}
> +  owned_nullable_string (const char *str)
> +  : m_str (str ? ::xstrdup (str) : nullptr)
> +  {
> +  }
> +
> +  ~owned_nullable_string ()
> +  {
> +    free (m_str);
> +  }
> +
> +  void set (const char *str)
> +  {
> +    free (m_str);
> +    m_str = str ? ::xstrdup (str) : nullptr;
> +  }
> +
> +  const char *get_str () const { return m_str; }
> +
> +  char *xstrdup () const
> +  {
> +    return m_str ? ::xstrdup (m_str) : nullptr;
> +  }
> +
> +private:
> +  char *m_str;
> +};
> +
> +/* This has to be a "struct" as it is exposed in the C API.  */
> +
> +struct diagnostic_file
> +{
> +  diagnostic_file (const char *name, const char *sarif_source_language)
> +  : m_name (name), m_sarif_source_language (sarif_source_language)
> +  {
> +  }
> +
> +  const char *get_name () const { return m_name.get_str (); }
> +  const char *get_sarif_source_language () const
> +  {
> +    return m_sarif_source_language.get_str ();
> +  }
> +
> +private:
> +  owned_nullable_string m_name;
> +  owned_nullable_string m_sarif_source_language;
> +};
> +
> +/* This has to be a "struct" as it is exposed in the C API.  */
> +
> +struct diagnostic_logical_location : public logical_location
> +{
> +  diagnostic_logical_location (enum diagnostic_logical_location_kind_t kind,
> +			       const diagnostic_logical_location *parent,
> +			       const char *short_name,
> +			       const char *fully_qualified_name,
> +			       const char *decorated_name)
> +  : m_kind (kind),
> +    m_parent (parent),
> +    m_short_name (short_name),
> +    m_fully_qualified_name (fully_qualified_name),
> +    m_decorated_name (decorated_name)
> +  {
> +  }
> +
> +  const char *get_short_name () const final override
> +  {
> +    return m_short_name.get_str ();
> +  }
> +  const char *get_name_with_scope () const final override
> +  {
> +    return m_fully_qualified_name.get_str ();
> +  }
> +  const char *get_internal_name () const final override
> +  {
> +    return m_decorated_name.get_str ();
> +  }
> +  enum logical_location_kind get_kind () const final override
> +  {
> +    switch (m_kind)
> +      {
> +      default:
> +	gcc_unreachable ();
> +      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION:
> +	return LOGICAL_LOCATION_KIND_FUNCTION;
> +      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_MEMBER:
> +	return LOGICAL_LOCATION_KIND_MEMBER;
> +      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_MODULE:
> +	return LOGICAL_LOCATION_KIND_MODULE;
> +      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_NAMESPACE:
> +	return LOGICAL_LOCATION_KIND_NAMESPACE;
> +      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_TYPE:
> +	return LOGICAL_LOCATION_KIND_TYPE;
> +      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_RETURN_TYPE:
> +	return LOGICAL_LOCATION_KIND_RETURN_TYPE;
> +      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_PARAMETER:
> +	return LOGICAL_LOCATION_KIND_PARAMETER;
> +      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_VARIABLE:
> +	return LOGICAL_LOCATION_KIND_VARIABLE;
> +      }
> +  }
> +
> +private:
> +  enum diagnostic_logical_location_kind_t m_kind;
> +  const diagnostic_logical_location *m_parent;
> +  owned_nullable_string m_short_name;
> +  owned_nullable_string m_fully_qualified_name;
> +  owned_nullable_string m_decorated_name;
> +};
> +
> +class sink
> +{
> +public:
> +  virtual ~sink ();
> +
> +  void begin_group ()
> +  {
> +    m_dc.begin_group ();
> +  }
> +  void end_group ()
> +  {
> +    m_dc.end_group ();
> +  }
> +
> +  void emit (diagnostic &diag, const char *msgid, va_list *args)
> +    LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING(3, 0);
> +
> +protected:
> +  sink (diagnostic_manager &mgr);
> +
> +  static char *
> +  get_option_name_cb (diagnostic_context *, int, diagnostic_t, diagnostic_t)
> +  {
> +    return nullptr; // FIXME
> +  }
> +
> +  diagnostic_manager &m_mgr;
> +
> +  /* One context per sink.  */
> +  diagnostic_context m_dc;
> +};
> +
> +class text_sink : public sink
> +{
> +public:
> +  text_sink (diagnostic_manager &mgr,
> +	     FILE *dst_stream,
> +	     enum diagnostic_colorize colorize);
> +
> +  void
> +  on_begin_text_diagnostic (diagnostic_info *info);
> +
> +private:
> +  const diagnostic_logical_location *m_current_logical_loc;
> +};
> +
> +class sarif_sink : public sink
> +{
> +public:
> +  sarif_sink (diagnostic_manager &mgr,
> +	      FILE *dst_stream,
> +	      enum diagnostic_sarif_version version);
> +};
> +
> +/* Helper for the linemap code.  */
> +
> +static size_t
> +round_alloc_size (size_t s)
> +{
> +  return s;
> +}
> +
> +class impl_diagnostic_client_data_hooks : public diagnostic_client_data_hooks
> +{
> +public:
> +  impl_diagnostic_client_data_hooks (diagnostic_manager &mgr)
> +  : m_mgr (mgr)
> +  {}
> +
> +  const client_version_info *get_any_version_info () const final override;
> +  const logical_location *get_current_logical_location () const final override;
> +  const char * maybe_get_sarif_source_language (const char *filename)
> +    const final override;
> +  void add_sarif_invocation_properties (sarif_object &invocation_obj)
> +    const final override;
> +
> +private:
> +  diagnostic_manager &m_mgr;
> +};
> +
> +class impl_client_version_info : public client_version_info
> +{
> +public:
> +  const char *get_tool_name () const final override
> +  {
> +    return m_name.get_str ();
> +  }
> +
> +  char *maybe_make_full_name () const final override
> +  {
> +    return m_full_name.xstrdup ();
> +  }
> +
> +  const char *get_version_string () const final override
> +  {
> +    return m_version.get_str ();
> +  }
> +
> +  char *maybe_make_version_url () const final override
> +  {
> +    return m_version_url.xstrdup ();
> +  }
> +
> +  void for_each_plugin (plugin_visitor &) const final override
> +  {
> +    // No-op.
> +  }
> +
> +  owned_nullable_string m_name;
> +  owned_nullable_string m_full_name;
> +  owned_nullable_string m_version;
> +  owned_nullable_string m_version_url;
> +};
> +
> +/* This has to be a "struct" as it is exposed in the C API.  */
> +
> +struct diagnostic_manager
> +{
> +public:
> +  diagnostic_manager ()
> +  : m_current_diag (nullptr)
> +  {
> +    linemap_init (&m_line_table, BUILTINS_LOCATION);
> +    m_line_table.m_reallocator = xrealloc;
> +    m_line_table.m_round_alloc_size = round_alloc_size;
> +    m_line_table.default_range_bits = 5;
> +  }
> +  ~diagnostic_manager ()
> +  {
> +    /* Clean up sinks first, as they can use other fields. */
> +    for (size_t i = 0; i < m_sinks.size (); i++)
> +      m_sinks[i] = nullptr;
> +
> +    for (auto iter : m_str_to_file_map)
> +      delete iter.second;
> +  }
> +
> +  line_maps *get_line_table () { return &m_line_table; }
> +  file_cache *get_file_cache () { return &m_file_cache; }
> +
> +  void write_patch (FILE *dst_stream);
> +
> +  void add_sink (std::unique_ptr<sink> sink)
> +  {
> +    m_sinks.push_back (std::move (sink));
> +  }
> +
> +  void emit (diagnostic &diag, const char *msgid, va_list *args)
> +    LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING(3, 0);
> +
> +  const diagnostic_file *
> +  new_file (const char *name,
> +	    const char *sarif_source_language)
> +  {
> +    if (diagnostic_file **slot = m_str_to_file_map.get (name))
> +      return *slot;
> +    diagnostic_file *file = new diagnostic_file (name, sarif_source_language);
> +    m_str_to_file_map.put (file->get_name (), file);
> +    return file;
> +  }
> +
> +  diagnostic_location_t
> +  new_location_from_file_and_line (const diagnostic_file *file,
> +				   diagnostic_line_num_t linenum)
> +  {
> +    // FIXME: this is a hack...
> +    /* Build a simple linemap describing some locations. */
> +    if (LINEMAPS_ORDINARY_USED (&m_line_table) == 0)
> +      linemap_add (&m_line_table, LC_ENTER, false, file->get_name (), 0);
> +    else
> +      {
> +	const line_map *map
> +	  = linemap_add (&m_line_table, LC_RENAME_VERBATIM, false,
> +			 file->get_name (), 0);
> +	// FIXME:
> +	((line_map_ordinary *)map)->included_from = UNKNOWN_LOCATION;
> +      }
> +    linemap_line_start (&m_line_table, linenum, 100);
> +    return linemap_position_for_column (&m_line_table, 0);
> +  }
> +
> +
> +  diagnostic_location_t
> +  new_location_from_file_line_column (const diagnostic_file *file,
> +				      diagnostic_line_num_t line_num,
> +				      diagnostic_column_num_t column_num)
> +  {
> +    // FIXME: this is a hack...
> +    /* Build a simple linemap describing some locations. */
> +    linemap_add (&m_line_table, LC_ENTER, false, file->get_name (), 0);
> +    linemap_line_start (&m_line_table, line_num, 100);
> +    return linemap_position_for_column (&m_line_table, column_num);
> +  }
> +
> +  diagnostic_location_t
> +  new_location_from_range (diagnostic_location_t loc_caret,
> +			   diagnostic_location_t loc_start,
> +			   diagnostic_location_t loc_end)
> +  {
> +    return m_line_table.make_location (loc_caret, loc_start, loc_end);
> +  }
> +
> +  const diagnostic_logical_location *
> +  new_logical_location (enum diagnostic_logical_location_kind_t kind,
> +			const diagnostic_logical_location *parent,
> +			const char *short_name,
> +			const char *fully_qualified_name,
> +			const char *decorated_name)
> +  {
> +    std::unique_ptr<diagnostic_logical_location> logical_loc
> +      = ::make_unique<diagnostic_logical_location> (kind,
> +						    parent,
> +						    short_name,
> +						    fully_qualified_name,
> +						    decorated_name);
> +    const diagnostic_logical_location *result = logical_loc.get ();
> +    m_logical_locs.push_back (std::move (logical_loc));
> +    return result;
> +  }
> +
> +  void begin_group ()
> +  {
> +    for (auto &sink : m_sinks)
> +      sink->begin_group ();
> +  }
> +
> +  void end_group ()
> +  {
> +    for (auto &sink : m_sinks)
> +      sink->end_group ();
> +  }
> +
> +  const char *
> +  maybe_get_sarif_source_language (const char *filename)
> +  {
> +    if (diagnostic_file **slot = m_str_to_file_map.get (filename))
> +      {
> +	gcc_assert (*slot);
> +	return (*slot)->get_sarif_source_language ();
> +      }
> +    return nullptr;
> +  }
> +
> +  const diagnostic *get_current_diag () { return m_current_diag; }
> +
> +  const client_version_info *get_client_version_info () const
> +  {
> +    return &m_client_version_info;
> +  }
> +  impl_client_version_info *get_client_version_info ()
> +  {
> +    return &m_client_version_info;
> +  }
> +
> +  void
> +  assert_valid_diagnostic_location_t (diagnostic_location_t loc) const
> +  {
> +    // TODO
> +    (void)loc;
> +  }
> +
> +  /* FIXME: Various things still used the "line_table" global variable.
> +     Set it to be this diagnostic_manager's m_line_table.
> +     Ideally we should eliminate this global (and this function).  */
> +  void set_line_table_global ()
> +  {
> +    line_table = &m_line_table;
> +  }
> +
> +private:
> +  line_maps m_line_table;
> +  file_cache m_file_cache;
> +  impl_client_version_info m_client_version_info;
> +  std::vector<std::unique_ptr<sink>> m_sinks;
> +  hash_map<nofree_string_hash, diagnostic_file *> m_str_to_file_map;
> +  std::vector<std::unique_ptr<diagnostic_logical_location>> m_logical_locs;
> +  const diagnostic *m_current_diag;
> +  edit_context m_edit_context;
> +};
> +
> +class impl_rich_location : public rich_location
> +{
> +public:
> +  impl_rich_location (line_maps *set)
> +  : rich_location (set, UNKNOWN_LOCATION)
> +  {}
> +};
> +
> +class impl_range_label : public range_label
> +{
> +public:
> +  impl_range_label (const char *text)
> +  : m_text (xstrdup (text))
> +  {}
> +
> +  ~impl_range_label () { free (m_text); }
> +
> +  label_text get_text (unsigned) const final override
> +  {
> +    return label_text::borrow (m_text);
> +  }
> +
> +private:
> +  char *m_text;
> +};
> +
> +class impl_rule : public diagnostic_metadata::rule
> +{
> +public:
> +  impl_rule (const char *title, const char *url)
> +  :  m_title (title),
> +     m_url (url)
> +  {
> +  }
> +
> +  char *make_description () const final override
> +  {
> +    return m_title.xstrdup ();
> +  }
> +
> +  char *make_url () const final override
> +  {
> +    return m_url.xstrdup ();
> +  }
> +
> +private:
> +  owned_nullable_string m_title;
> +  owned_nullable_string m_url;
> +};
> +
> +/* This has to be a "struct" as it is exposed in the C API.  */
> +
> +struct diagnostic
> +{
> +public:
> +  diagnostic (diagnostic_manager &diag_mgr,
> +	      enum diagnostic_level level)
> +  : m_diag_mgr (diag_mgr),
> +    m_level (level),
> +    m_rich_loc (diag_mgr.get_line_table ()),
> +    m_logical_loc (nullptr)
> +  {}
> +
> +  diagnostic_manager &get_manager () const
> +  {
> +    return m_diag_mgr;
> +  }
> +
> +  enum diagnostic_level get_level () const { return m_level; }
> +
> +  rich_location *get_rich_location () { return &m_rich_loc; }
> +  const diagnostic_metadata *get_metadata () { return &m_metadata; }
> +
> +#if 0
> +  diagnostic_location_t get_location () const { return m_loc; }
> +#endif
> +
> +  void set_cwe (unsigned cwe_id)
> +  {
> +    m_metadata.add_cwe (cwe_id);
> +  }
> +
> +  void add_rule (const char *title,
> +		 const char *url)
> +  {
> +    std::unique_ptr<impl_rule> rule = ::make_unique<impl_rule> (title, url);
> +    m_metadata.add_rule (*rule.get ());
> +    m_rules.push_back (std::move (rule));
> +  }
> +
> +  void set_location (diagnostic_location_t loc)
> +  {
> +    m_rich_loc.set_range (0, loc, SHOW_RANGE_WITH_CARET);
> +  }
> +
> +  void
> +  add_location (diagnostic_location_t loc)
> +  {
> +    m_rich_loc.add_range (loc, SHOW_RANGE_WITHOUT_CARET);
> +  }
> +
> +  void
> +  add_location_with_label (diagnostic_location_t loc,
> +			   const char *text)
> +  {
> +    std::unique_ptr<range_label> label = ::make_unique <impl_range_label> (text);
> +    m_rich_loc.add_range (loc,
> +			  SHOW_RANGE_WITHOUT_CARET,
> +			  label.get ());
> +    m_labels.push_back (std::move (label));
> +  }
> +
> +  void
> +  set_logical_location (const diagnostic_logical_location *logical_loc)
> +  {
> +    m_logical_loc = logical_loc;
> +  }
> +  const diagnostic_logical_location *get_logical_location () const
> +  {
> +    return m_logical_loc;
> +  }
> +
> +private:
> +  diagnostic_manager &m_diag_mgr;
> +  enum diagnostic_level m_level;
> +  impl_rich_location m_rich_loc;
> +  const diagnostic_logical_location *m_logical_loc;
> +  diagnostic_metadata m_metadata;
> +  std::vector<std::unique_ptr<range_label>> m_labels;
> +  std::vector<std::unique_ptr<impl_rule>> m_rules;
> +};
> +
> +static diagnostic_t
> +diagnostic_t_from_diagnostic_level (enum diagnostic_level level)
> +{
> +  switch (level)
> +    {
> +    default:
> +      gcc_unreachable ();
> +    case DIAGNOSTIC_LEVEL_ERROR:
> +      return DK_ERROR;
> +    case DIAGNOSTIC_LEVEL_WARNING:
> +      return DK_WARNING;
> +    case DIAGNOSTIC_LEVEL_NOTE:
> +      return DK_NOTE;
> +    }
> +}
> +
> +/* class impl_diagnostic_client_data_hooks.  */
> +
> +const client_version_info *
> +impl_diagnostic_client_data_hooks::get_any_version_info () const
> +{
> +  return m_mgr.get_client_version_info ();
> +}
> +
> +const logical_location *
> +impl_diagnostic_client_data_hooks::get_current_logical_location () const
> +{
> +  gcc_assert (m_mgr.get_current_diag ());
> +
> +  return m_mgr.get_current_diag ()->get_logical_location ();
> +}
> +
> +const char *
> +impl_diagnostic_client_data_hooks::
> +maybe_get_sarif_source_language (const char *filename) const
> +{
> +  return m_mgr.maybe_get_sarif_source_language (filename);
> +}
> +
> +void
> +impl_diagnostic_client_data_hooks::
> +add_sarif_invocation_properties (sarif_object &) const
> +{
> +  // No-op.
> +}
> +
> +/* class sink.  */
> +
> +void
> +sink::emit (diagnostic &diag, const char *msgid, va_list *args)
> +{
> +  diagnostic_info info;
> +  diagnostic_set_info (&info, msgid, args, diag.get_rich_location (),
> +		       diagnostic_t_from_diagnostic_level (diag.get_level ()));
> +  info.metadata = diag.get_metadata ();
> +  diagnostic_report_diagnostic (&m_dc, &info);
> +}
> +
> +sink::sink (diagnostic_manager &mgr)
> +: m_mgr (mgr)
> +{
> +  diagnostic_initialize (&m_dc, 0);
> +  m_dc.m_client_aux_data = this;
> +  m_dc.m_option_name = get_option_name_cb;
> +  m_dc.set_client_data_hooks (new impl_diagnostic_client_data_hooks (mgr));
> +  m_dc.file_cache_init ();
> +}
> +
> +sink::~sink ()
> +{
> +  diagnostic_finish (&m_dc);
> +}
> +
> +/* class text_sink : public sink.  */
> +
> +text_sink::text_sink (diagnostic_manager &mgr,
> +		      FILE *dst_stream,
> +		      enum diagnostic_colorize colorize)
> +: sink (mgr)
> +{
> +  m_dc.set_show_cwe (true);
> +  m_dc.set_show_rules (true);
> +
> +  diagnostic_color_rule_t color_rule;
> +  switch (colorize)
> +    {
> +    default:
> +      gcc_unreachable ();
> +    case DIAGNOSTIC_COLORIZE_IF_TTY:
> +      color_rule = DIAGNOSTICS_COLOR_AUTO;
> +      break;
> +    case DIAGNOSTIC_COLORIZE_NO:
> +      color_rule = DIAGNOSTICS_COLOR_NO;
> +      break;
> +    case DIAGNOSTIC_COLORIZE_YES:
> +      color_rule = DIAGNOSTICS_COLOR_YES;
> +      break;
> +    }
> +  diagnostic_color_init (&m_dc, color_rule);
> +  m_dc.printer->buffer->stream = dst_stream;
> +  m_dc.m_text_callbacks.begin_diagnostic
> +    = [] (diagnostic_context *context,
> +	  diagnostic_info *info)
> +      {
> +	text_sink *sink = static_cast<text_sink *> (context->m_client_aux_data);
> +	sink->on_begin_text_diagnostic (info);
> +      };
> +  m_dc.m_source_printing.enabled = true; // FIXME
> +  m_dc.m_source_printing.colorize_source_p = true; // FIXME
> +  m_dc.m_source_printing.show_labels_p = true; // FIXME
> +  m_dc.m_source_printing.show_line_numbers_p = true; // FIXME
> +  m_dc.m_source_printing.min_margin_width = 6; // FIXME
> +}
> +
> +void
> +text_sink::on_begin_text_diagnostic (diagnostic_info *info)
> +{
> +  const diagnostic *diag = m_mgr.get_current_diag ();
> +  gcc_assert (diag);
> +  const diagnostic_logical_location *diag_logical_loc
> +    = diag->get_logical_location ();
> +  if (m_current_logical_loc != diag_logical_loc)
> +    {
> +      m_current_logical_loc = diag_logical_loc;
> +      if (m_current_logical_loc)
> +	{
> +	  char *old_prefix = pp_take_prefix (m_dc.printer);
> +	  switch (m_current_logical_loc->get_kind ())
> +	    {
> +	    default:
> +	      break;
> +	    case LOGICAL_LOCATION_KIND_FUNCTION:
> +	      if (const char *name
> +		  = m_current_logical_loc->get_name_with_scope ())
> +		{
> +		  pp_printf (m_dc.printer, _("In function %qs"), name);
> +		  pp_character (m_dc.printer, ':');
> +		  pp_newline (m_dc.printer);
> +		}
> +	      break;
> +	      // FIXME: handle other cases
> +	    }
> +	  m_dc.printer->prefix = old_prefix;
> +	}
> +    }
> +  pp_set_prefix (m_dc.printer,
> +		 diagnostic_build_prefix (&m_dc, info));
> +}
> +
> +/* class sarif_sink : public sink.  */
> +
> +sarif_sink::sarif_sink (diagnostic_manager &mgr,
> +			FILE *dst_stream,
> +			enum diagnostic_sarif_version)
> +: sink (mgr)
> +{
> +  diagnostic_output_format_init_sarif_stream (&m_dc, dst_stream);
> +}
> +
> +/* struct diagnostic_manager.  */
> +
> +void
> +diagnostic_manager::write_patch (FILE *dst_stream)
> +{
> +  pretty_printer pp;
> +  pp.buffer->stream = dst_stream;
> +  m_edit_context.print_diff (&pp, true);
> +  pp_flush (&pp);
> +}
> +
> +void
> +diagnostic_manager::emit (diagnostic &diag, const char *msgid, va_list *args)
> +{
> +  set_line_table_global ();
> +
> +  m_current_diag = &diag;
> +  for (auto &sink : m_sinks)
> +    {
> +      va_list arg_copy;
> +      va_copy (arg_copy, *args);
> +      sink->emit (diag, msgid, &arg_copy);
> +    }
> +
> +  rich_location *rich_loc = diag.get_rich_location ();
> +  if (rich_loc->fixits_can_be_auto_applied_p ())
> +    m_edit_context.add_fixits (rich_loc);
> +
> +  m_current_diag = nullptr;
> +}
> +
> +/* Public entrypoints.  */
> +
> +/* Public entrypoint for clients to acquire a diagnostic_manager.  */
> +
> +diagnostic_manager *
> +diagnostic_manager_new (void)
> +{
> +  return new diagnostic_manager ();
> +}
> +
> +/* Public entrypoint for clients to release a diagnostic_manager.  */
> +
> +void
> +diagnostic_manager_release (diagnostic_manager *diag_mgr)
> +{
> +  delete diag_mgr;
> +}
> +
> +/* Public entrypoint.  */
> +
> +void
> +diagnostic_manager_set_tool_name (diagnostic_manager *diag_mgr,
> +				  const char *value)
> +{
> +  gcc_assert (diag_mgr);
> +  gcc_assert (value);
> +
> +  diag_mgr->get_client_version_info ()->m_name.set (value);
> +}
> +
> +/* Public entrypoint.  */
> +
> +void
> +diagnostic_manager_set_full_name (diagnostic_manager *diag_mgr,
> +				  const char *value)
> +{
> +  gcc_assert (diag_mgr);
> +  gcc_assert (value);
> +
> +  diag_mgr->get_client_version_info ()->m_full_name.set (value);
> +}
> +
> +/* Public entrypoint.  */
> +
> +void
> +diagnostic_manager_set_version_string (diagnostic_manager *diag_mgr,
> +				       const char *value)
> +{
> +  gcc_assert (diag_mgr);
> +  gcc_assert (value);
> +
> +  diag_mgr->get_client_version_info ()->m_version.set (value);
> +}
> +
> +/* Public entrypoint.  */
> +
> +void
> +diagnostic_manager_set_version_url (diagnostic_manager *diag_mgr,
> +				    const char *value)
> +{
> +  gcc_assert (diag_mgr);
> +  gcc_assert (value);
> +
> +  diag_mgr->get_client_version_info ()->m_version_url.set (value);
> +}
> +
> +/* Public entrypoint.  */
> +
> +void
> +diagnostic_manager_add_text_sink (diagnostic_manager *diag_mgr,
> +				  FILE *dst_stream,
> +				  enum diagnostic_colorize colorize)
> +{
> +  gcc_assert (diag_mgr);
> +  gcc_assert (dst_stream);
> +
> +  diag_mgr->add_sink (make_unique<text_sink> (*diag_mgr, dst_stream, colorize));
> +}
> +
> +/* Public entrypoint.  */
> +
> +void
> +diagnostic_manager_add_sarif_sink (diagnostic_manager *diag_mgr,
> +				   FILE *dst_stream,
> +				   enum diagnostic_sarif_version version)
> +{
> +  gcc_assert (diag_mgr);
> +  gcc_assert (dst_stream);
> +
> +  diag_mgr->add_sink (make_unique<sarif_sink> (*diag_mgr, dst_stream, version));
> +}
> +
> +/* Public entrypoint.  */
> +
> +void
> +diagnostic_manager_write_patch (diagnostic_manager *diag_mgr,
> +				FILE *dst_stream)
> +{
> +  gcc_assert (diag_mgr);
> +  gcc_assert (dst_stream);
> +
> +  diag_mgr->write_patch (dst_stream);
> +}
> +
> +/* Public entrypoint.  */
> +
> +const diagnostic_file *
> +diagnostic_manager_new_file (diagnostic_manager *diag_mgr,
> +			     const char *name,
> +			     const char *sarif_source_language)
> +{
> +  gcc_assert (diag_mgr);
> +  gcc_assert (name);
> +
> +  return diag_mgr->new_file (name, sarif_source_language);
> +}
> +
> +/* Public entrypoint.  */
> +
> +diagnostic_location_t
> +diagnostic_manager_new_location_from_file_and_line (diagnostic_manager *diag_mgr,
> +						    const diagnostic_file *file,
> +						    diagnostic_line_num_t linenum)
> +{
> +  gcc_assert (diag_mgr);
> +  gcc_assert (file);
> +
> +  return diag_mgr->new_location_from_file_and_line (file, linenum);
> +}
> +
> +/* Public entrypoint.  */
> +
> +diagnostic_location_t
> +diagnostic_manager_new_location_from_file_line_column (diagnostic_manager *diag_mgr,
> +						       const diagnostic_file *file,
> +						       diagnostic_line_num_t line_num,
> +					       diagnostic_column_num_t column_num)
> +{
> +  gcc_assert (diag_mgr);
> +  gcc_assert (file);
> +
> +  return diag_mgr->new_location_from_file_line_column (file,
> +						       line_num,
> +						       column_num);
> +}
> +
> +/* Public entrypoint.  */
> +
> +diagnostic_location_t
> +diagnostic_manager_new_location_from_range (diagnostic_manager *diag_mgr,
> +					    diagnostic_location_t loc_caret,
> +					    diagnostic_location_t loc_start,
> +					    diagnostic_location_t loc_end)
> +{
> +  gcc_assert (diag_mgr);
> +
> +  return diag_mgr->new_location_from_range (loc_caret,
> +					    loc_start,
> +					    loc_end);
> +}
> +
> +
> +// FIXME: emit text to stderr
> +
> +#if 0
> +void
> +diagnostic_location_debug (diagnostic_manager *diag_mgr,
> +			   diagnostic_location_t loc,
> +			   FILE *stream)
> +{
> +  text_sink sink (stream);
> +  diagnostic d (*diag_mgr, DIAGNOSTIC_LEVEL_NOTE);
> +}
> +#endif
> +
> +/* Public entrypoint.  */
> +
> +const diagnostic_logical_location *
> +diagnostic_manager_new_logical_location (diagnostic_manager *diag_mgr,
> +					 enum diagnostic_logical_location_kind_t kind,
> +					 const diagnostic_logical_location *parent,
> +					 const char *short_name,
> +					 const char *fully_qualified_name,
> +					 const char *decorated_name)
> +{
> +  gcc_assert (diag_mgr);
> +
> +  return diag_mgr->new_logical_location (kind,
> +					 parent,
> +					 short_name,
> +					 fully_qualified_name,
> +					 decorated_name);
> +}
> +
> +/* Public entrypoint.  */
> +
> +void
> +diagnostic_manager_begin_group (diagnostic_manager *diag_mgr)
> +{
> +  gcc_assert (diag_mgr);
> +  diag_mgr->begin_group ();
> +}
> +
> +/* Public entrypoint.  */
> +
> +extern void
> +diagnostic_manager_end_group (diagnostic_manager *diag_mgr)
> +{
> +  gcc_assert (diag_mgr);
> +  diag_mgr->end_group ();
> +}
> +
> +/* Public entrypoint.  */
> +
> +diagnostic *
> +diagnostic_begin (diagnostic_manager *diag_mgr,
> +		  enum diagnostic_level level)
> +{
> +  gcc_assert (diag_mgr);
> +
> +  return new diagnostic (*diag_mgr, level);
> +}
> +
> +/* Public entrypoint.  */
> +
> +void
> +diagnostic_set_cwe (diagnostic *diag,
> +		    unsigned cwe_id)
> +{
> +  gcc_assert (diag);
> +
> +  diag->set_cwe (cwe_id);
> +}
> +
> +/* Public entrypoint.  */
> +
> +void
> +diagnostic_add_rule (diagnostic *diag,
> +		     const char *title,
> +		     const char *url)
> +{
> +  gcc_assert (diag);
> +
> +  diag->add_rule (title, url);
> +}
> +
> +/* Public entrypoint.  */
> +
> +void
> +diagnostic_set_location (diagnostic *diag,
> +			 diagnostic_location_t loc)
> +{
> +  gcc_assert (diag);
> +  diag->get_manager ().assert_valid_diagnostic_location_t (loc);
> +
> +  diag->set_location (loc);
> +}
> +
> +/* Public entrypoint.  */
> +
> +void
> +diagnostic_add_location (diagnostic *diag,
> +			 diagnostic_location_t loc)
> +{
> +  gcc_assert (diag);
> +  diag->get_manager ().assert_valid_diagnostic_location_t (loc);
> +
> +  diag->add_location (loc);
> +}
> +
> +/* Public entrypoint.  */
> +
> +void
> +diagnostic_add_location_with_label (diagnostic *diag,
> +				    diagnostic_location_t loc,
> +				    const char *text)
> +{
> +  gcc_assert (diag);
> +  diag->get_manager ().assert_valid_diagnostic_location_t (loc);
> +  gcc_assert (text);
> +
> +  diag->add_location_with_label (loc, text);
> +}
> +
> +/* Public entrypoint.  */
> +
> +void
> +diagnostic_set_logical_location (diagnostic *diag,
> +				 const diagnostic_logical_location *logical_loc)
> +{
> +  gcc_assert (diag);
> +  gcc_assert (logical_loc);
> +
> +  diag->set_logical_location (logical_loc);
> +}
> +
> +/* Public entrypoint.  */
> +
> +void
> +diagnostic_add_fix_it_hint_insert_before (diagnostic *diag,
> +					  diagnostic_location_t loc,
> +					  const char *addition)
> +{
> +  gcc_assert (diag);
> +  diag->get_manager ().assert_valid_diagnostic_location_t (loc);
> +  gcc_assert (addition);
> +
> +  diag->get_manager ().set_line_table_global ();
> +  diag->get_rich_location ()->add_fixit_insert_before (loc, addition);
> +}
> +
> +/* Public entrypoint.  */
> +
> +void
> +diagnostic_add_fix_it_hint_insert_after (diagnostic *diag,
> +					 diagnostic_location_t loc,
> +					 const char *addition)
> +{
> +  gcc_assert (diag);
> +  diag->get_manager ().assert_valid_diagnostic_location_t (loc);
> +  gcc_assert (addition);
> +
> +  diag->get_manager ().set_line_table_global ();
> +  diag->get_rich_location ()->add_fixit_insert_after (loc, addition);
> +}
> +
> +/* Public entrypoint.  */
> +
> +void
> +diagnostic_add_fix_it_hint_replace (diagnostic *diag,
> +				    diagnostic_location_t loc,
> +				    const char *replacement)
> +{
> +  gcc_assert (diag);
> +  diag->get_manager ().assert_valid_diagnostic_location_t (loc);
> +  gcc_assert (replacement);
> +
> +  diag->get_manager ().set_line_table_global ();
> +  diag->get_rich_location ()->add_fixit_replace (loc, replacement);
> +}
> +
> +/* Public entrypoint.  */
> +
> +void
> +diagnostic_add_fix_it_hint_delete (diagnostic *diag,
> +				   diagnostic_location_t loc)
> +{
> +  gcc_assert (diag);
> +  diag->get_manager ().assert_valid_diagnostic_location_t (loc);
> +
> +  diag->get_manager ().set_line_table_global ();
> +  diag->get_rich_location ()->add_fixit_remove (loc);
> +}
> +
> +/* Public entrypoint.  */
> +
> +void
> +diagnostic_finish (diagnostic *diag, const char *gmsgid, ...)
> +{
> +  gcc_assert (diag);
> +
> +  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;
> +  va_list args;
> +  va_start (args, gmsgid);
> +  gcc_assert (diag);
> +  diag->get_manager ().emit (*diag, gmsgid, &args);
> +  va_end (args);
> +  delete diag;
> +}
> diff --git a/gcc/libdiagnostics.map b/gcc/libdiagnostics.map
> new file mode 100644
> index 00000000000..06da1d5c9c2
> --- /dev/null
> +++ b/gcc/libdiagnostics.map
> @@ -0,0 +1,57 @@
> +# Linker script for libdiagnostics.so
> +#   Copyright (C) 2023 Free Software Foundation, Inc.
> +#   Contributed by David Malcolm <dmalcolm@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/>.  */
> +
> +# The initial release of the library.
> +LIBDIAGNOSTICS_ABI_0
> +{
> +  global:
> +    # Keep this list in order of decls in header file.
> +    diagnostic_manager_new;
> +    diagnostic_manager_release;
> +    diagnostic_manager_set_tool_name;
> +    diagnostic_manager_set_full_name;
> +    diagnostic_manager_set_version_string;
> +    diagnostic_manager_set_version_url;
> +    diagnostic_manager_add_text_sink;
> +    diagnostic_manager_add_sarif_sink;
> +    diagnostic_manager_write_patch;
> +    diagnostic_manager_new_file;
> +    diagnostic_manager_new_location_from_file_and_line;
> +    diagnostic_manager_new_location_from_file_line_column;
> +    diagnostic_manager_new_location_from_range;
> +    diagnostic_manager_new_logical_location;
> +    diagnostic_manager_begin_group;
> +    diagnostic_manager_end_group;
> +    diagnostic_begin;
> +    diagnostic_set_cwe;
> +    diagnostic_add_rule;
> +    diagnostic_set_location;
> +    diagnostic_set_location_with_label;
> +    diagnostic_add_location;
> +    diagnostic_add_location_with_label;
> +    diagnostic_set_logical_location;
> +    diagnostic_add_fix_it_hint_insert_before;
> +    diagnostic_add_fix_it_hint_insert_after;
> +    diagnostic_add_fix_it_hint_replace;
> +    diagnostic_add_fix_it_hint_delete;
> +    diagnostic_finish;
> +
> +  local: *;
> +};
> diff --git a/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp b/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp
> new file mode 100644
> index 00000000000..bd50a5568a7
> --- /dev/null
> +++ b/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp
> @@ -0,0 +1,544 @@
> +# Test code for libdiagnostics.so
> +#
> +# FIXME:
> +#
> +# Test code for libdiagnostics.so
> +# We will compile each of jit.dg/test-*.c into an executable
> +# dynamically linked against libgccjit.so, and then run each
> +# such executable.
> +#
> +# These executables call into the libgccjit.so API to create
> +# code, compile it, and run it, verifying that the results
> +# are as expected.  See harness.h for shared code used by all
> +# such executables.
> +#
> +# The executables call into DejaGnu's unit testing C API to
> +# report PASS/FAIL results, which this script gathers back
> +# up into the Tcl world, reporting a summary of all results
> +# across all of the executables.
> +
> +# Kludge alert:
> +# We need g++_init so that it can find the stdlib include path.
> +#
> +# g++_init (in lib/g++.exp) uses g++_maybe_build_wrapper,
> +# which normally comes from the definition of
> +# ${tool}_maybe_build_wrapper within lib/wrapper.exp.
> +#
> +# However, for us, ${tool} is "jit".
> +# Hence we load wrapper.exp with tool == "g++", so that
> +# g++_maybe_build_wrapper is defined.
> +set tool g++
> +load_lib wrapper.exp
> +set tool diagnostics
> +
> +load_lib dg.exp
> +load_lib prune.exp
> +load_lib target-supports.exp
> +load_lib gcc-defs.exp
> +load_lib timeout.exp
> +load_lib target-libpath.exp
> +load_lib gcc.exp
> +load_lib g++.exp
> +load_lib dejagnu.exp
> +load_lib target-supports-dg.exp
> +
> +# # Skip these tests for targets that don't support -ldiagnostics
> +# if { ![check_effective_target_ldiagnostics] } {
> +#     return
> +# }
> +
> +# The default do-what keyword.
> +set dg-do-what-default compile
> +
> +# Look for lines of the form:
> +#   definitely lost: 11,316 bytes in 235 blocks
> +#   indirectly lost: 352 bytes in 4 blocks
> +# Ideally these would report zero bytes lost (which is a PASS);
> +# for now, report non-zero leaks as XFAILs.
> +proc report_leak {kind name logfile line} {
> +    set match [regexp "$kind lost: .*" $line result]
> +    if $match {
> +	verbose "Saw \"$result\" within \"$line\"" 4
> +	# Extract bytes and blocks.
> +	# These can contain commas as well as numerals,
> +	# but we only care about whether we have zero.
> +	regexp "$kind lost: (.+) bytes in (.+) blocks" \
> +	    $result -> bytes blocks
> +	verbose "bytes: '$bytes'" 4
> +	verbose "blocks: '$blocks'" 4
> +	if { $bytes == 0 } {
> +	    pass "$name: $logfile: $result"
> +	} else {
> +	    xfail "$name: $logfile: $result"
> +	}
> +    }
> +}
> +
> +proc parse_valgrind_logfile {name logfile} {
> +    verbose "parse_valgrind_logfile: $logfile" 2
> +    if [catch {set f [open $logfile]}] {
> +	fail "$name: unable to read $logfile"
> +	return
> +    }
> +
> +    while { [gets $f line] >= 0 } {
> +	# Strip off the PID prefix e.g. ==7675==
> +	set line [regsub "==\[0-9\]*== " $line ""]
> +	verbose $line 2
> +
> +	report_leak "definitely" $name $logfile $line
> +	report_leak "indirectly" $name $logfile $line
> +    }
> +    close $f
> +}
> +
> +# Given WRES, the result from "wait", issue a PASS
> +# if the spawnee exited cleanly, or a FAIL for various kinds of
> +# unexpected exits.
> +
> +proc verify_exit_status { executable wres } {
> +    lassign $wres pid spawnid os_error_flag value
> +    verbose "pid: $pid" 3
> +    verbose "spawnid: $spawnid" 3
> +    verbose "os_error_flag: $os_error_flag" 3
> +    verbose "value: $value" 3
> +
> +    # Detect segfaults etc:
> +    if { [llength $wres] > 4 } {
> +	if { [lindex $wres 4] == "CHILDKILLED" } {
> +	    fail "$executable killed: $wres"
> +	    return
> +	}
> +    }
> +    if { $os_error_flag != 0 } {
> +	fail "$executable: OS error: $wres"
> +	return
> +    }
> +    if { $value != 0 } {
> +	fail "$executable: non-zero exit code: $wres"
> +	return
> +    }
> +    pass "$executable exited cleanly"
> +}
> +
> +# This is host_execute from dejagnu.exp commit
> +#   126a089777158a7891ff975473939f08c0e31a1c
> +# with the following patch applied, and renaming to "fixed_host_execute".
> +# See the discussion at
> +#  http://lists.gnu.org/archive/html/dejagnu/2014-10/msg00000.html
> +#
> +#  --- /usr/share/dejagnu/dejagnu.exp.old  2014-10-08 13:38:57.274068541 -0400
> +#  +++ /usr/share/dejagnu/dejagnu.exp      2014-10-10 12:27:51.113813659 -0400
> +#  @@ -113,8 +113,6 @@ proc host_execute {args} {
> +#       set timetol 0
> +#       set arguments ""
> +#
> +#  -    expect_before buffer_full { perror "Buffer full" }
> +#  -
> +#       if { [llength $args] == 0} {
> +#          set executable $args
> +#       } else {
> +
> +
> +# Execute the executable file, and anaylyse the output for the
> +# test state keywords.
> +#    Returns:
> +#	A "" (empty) string if everything worked, or an error message
> +#	if there was a problem.
> +#
> +proc fixed_host_execute {args} {
> +    global env
> +    global text
> +    global spawn_id
> +
> +    verbose "fixed_host_execute: $args"
> +
> +    set timeoutmsg "Timed out: Never got started, "
> +    set timeout 100
> +    set file all
> +    set timetol 0
> +    set arguments ""
> +
> +    if { [llength $args] == 0} {
> +	set executable $args
> +    } else {
> +	set executable [lindex $args 0]
> +	set params [lindex $args 1]
> +    }
> +
> +    verbose "The executable is $executable" 2
> +    if {![file exists ${executable}]} {
> +	perror "The executable, \"$executable\" is missing" 0
> +	return "No source file found"
> +    } elseif {![file executable ${executable}]} {
> +	perror "The executable, \"$executable\" is not usable" 0
> +	return "Bad executable found"
> +    }
> +
> +    verbose "params: $params" 2
> +
> +    # spawn the executable and look for the DejaGnu output messages from the
> +    # test case.
> +    # spawn -noecho -open [open "|./${executable}" "r"]
> +
> +    # Run under valgrind if RUN_UNDER_VALGRIND is present in the environment.
> +    # Note that it's best to configure gcc with --enable-valgrind-annotations
> +    # when testing under valgrind.
> +    set run_under_valgrind [info exists env(RUN_UNDER_VALGRIND)]
> +    if $run_under_valgrind {
> +	set valgrind_logfile "${executable}.valgrind.txt"
> +	set valgrind_params {"valgrind"}
> +	lappend valgrind_params "--leak-check=full"
> +	lappend valgrind_params "--log-file=${valgrind_logfile}"
> +    } else {
> +	set valgrind_params {}
> +    }
> +    verbose "valgrind_params: $valgrind_params" 2
> +
> +    set args ${valgrind_params}
> +    lappend args "./${executable}"
> +    set args [concat $args ${params}]
> +    verbose "args: $args" 2
> +
> +    # We checked that the executable exists above, and can be executed, but
> +    # that does not cover other reasons that the launch could fail (e.g.
> +    # missing or malformed params); catch such cases here and report them.
> +    set err [catch "spawn -noecho $args" pid]
> +    set sub_proc_id $spawn_id
> +    if { $pid <= 0 || $err != 0 || $sub_proc_id < 0 } {
> +        warning "failed to spawn : $args : err = $err"
> +    }
> +
> +    # Increase the buffer size, if needed to avoid spurious buffer-full
> +    # events; GCC uses 10000; chose a power of two here.
> +    set current_max_match [match_max -i $sub_proc_id]
> +    if { $current_max_match < 8192 } {
> +        match_max -i $sub_proc_id 8192
> +        set used [match_max -i $sub_proc_id]
> +    }
> +
> +    # If we get a buffer-full error, that seems to be unrecoverable so try to
> +    # exit in a reasonable manner to avoid wedged processes.
> +    expect_after full_buffer {
> +        verbose -log "fixed_host_execute: $args FULL BUFFER"
> +        # FIXME: this all assumes that closing the connection will cause the
> +        # sub-process to terminate (but that is not going to be the case if,
> +        # for example, there is something started with -nohup somewhere).
> +        # We should explicitly kill it here.
> +        # Set the process to be a nowait exit.
> +        wait -nowait -i $sub_proc_id
> +        catch close
> +        perror "${executable} got full_buffer"
> +        return "${executable} got full_buffer"
> +    }
> +
> +    set prefix "\[^\r\n\]*"
> +    # Work around a Darwin tcl or termios bug that sometimes inserts extra
> +    # CR characters into the cooked tty stream
> +    set endline "\r\n"
> +    if { [istarget *-*-darwin*] } {
> +        set endline "\r(\r)*\n"
> +    }
> +
> +    # Note that the logic here assumes that we cannot (validly) get single
> +    # carriage return or line feed characters in the stream.  If those occur,
> +    # it will stop any further matching.  We arange for the matching to be
> +    # at the start of the buffer - so that if there is any spurious output
> +    # to be discarded, it must be done explicitly - not by matching part-way
> +    # through the buffer.
> +    expect {
> +	-re "^$prefix\[0-9\]\[0-9\]:..:..:${text}*$endline" {
> +	    regsub "\[\n\r\t\]*NOTE: $text\r\n" $expect_out(0,string) "" output
> +	    verbose "$output" 3
> +	    set timetol 0
> +	    exp_continue
> +	}
> +	-re "^\tNOTE: (\[^\r\n\]+)$endline" {
> +	    # discard notes.
> +	    verbose "Ignored note: $expect_out(1,string)" 2
> +	    set timetol 0
> +	    exp_continue
> +	}
> +	-re "^\tPASSED: (\[^\r\n\]+)$endline" {
> +	    pass "$expect_out(1,string)"
> +	    set timetol 0
> +	    exp_continue
> +	}
> +	-re "^\tFAILED: (\[^\r\n\]+)$endline" {
> +	    fail "$expect_out(1,string)"
> +	    set timetol 0
> +	    exp_continue
> +	}
> +	-re "^\tUNTESTED: (\[^\r\n\]+)$endline" {
> +	    untested "$expect_out(1,string)"
> +	    set timetol 0
> +	    exp_continue
> +	}
> +	-re "^\tUNRESOLVED: (\[^\r\n\]+)$endline" {
> +	    unresolved "$expect_out(1,string)"
> +	    set timetol 0
> +	    exp_continue
> +	}
> +	-re "^$prefix$endline" {
> +            # This matches and discards any other lines (including blank ones).
> +            if { [string length $expect_out(buffer)] <= 2 } {
> +                set output "blank line"
> +            } else {
> +	        set output [string range $expect_out(buffer) 0 end-2]
> +	    }
> +	    verbose -log "DISCARDED $expect_out(spawn_id) : $output"
> +	    exp_continue
> +	}
> +	eof {
> +	    # This seems to be the only way that we can reliably know that the
> +	    # output is finished since there are cases where further output
> +	    # follows the dejagnu test harness totals.
> +	    verbose "saw eof" 2
> +	}
> +	timeout {
> +	    if { $timetol <= 2 } {
> +	        verbose -log "Timed out with retry (timeout = $timeout)"
> +		incr timetol
> +		exp_continue
> +	    } else {
> +	        warning "Timed out executing testcase (timeout = $timeout)"
> +		catch close
> +		return "Timed out executing test case"
> +	    }
> +	}
> +    }
> +
> +    # Use "wait" to pick up the sub-process exit state.  If the sub-process is
> +    # writing to a file (perhaps under valgrind) then that also needs to be
> +    # complete; only attempt this on a valid spawn.
> +    if { $sub_proc_id > 0 } {
> +        verbose "waiting for $sub_proc_id" 1
> +        # Be explicit about what we are waiting for.
> +        catch "wait -i $sub_proc_id" wres
> +        verbose "wres: $wres" 2
> +        verify_exit_status $executable $wres
> +    }
> +
> +    if $run_under_valgrind {
> +	upvar 2 name name
> +	parse_valgrind_logfile $name $valgrind_logfile
> +    }
> +
> +    # force a close of the executable to be safe.
> +    catch close
> +
> +    return ""
> +}
> +
> +# (end of code from dejagnu.exp)
> +
> +# GCC_UNDER_TEST is needed by gcc_target_compile
> +global GCC_UNDER_TEST
> +if ![info exists GCC_UNDER_TEST] {
> +    set GCC_UNDER_TEST "[find_gcc]"
> +}
> +
> +g++_init
> +
> +# Initialize dg.
> +dg-init
> +
> +# Gather a list of all tests.
> +
> +# C tests within the testsuite: gcc/testsuite/libdiagnostics.dg/test-*.c
> +set tests [find $srcdir/$subdir test-*.c]
> +
> +set tests [lsort $tests]
> +
> +verbose "tests: $tests"
> +
> +# libgloss has found the driver (as "xgcc" or "gcc) and stored
> +# its full path as GCC_UNDER_TEST.
> +proc get_path_of_driver {} {
> +    global GCC_UNDER_TEST
> +
> +    verbose "GCC_UNDER_TEST: $GCC_UNDER_TEST"
> +    set binary [lindex $GCC_UNDER_TEST 0]
> +    verbose "binary: $binary"
> +
> +    return [file dirname $binary]
> +}
> +
> +# Expand "SRCDIR" within ARG to the location of the top-level
> +# src directory
> +
> +proc diagnostics-expand-vars {arg} {
> +    verbose "diagnostics-expand-vars: $arg"
> +    global srcdir
> +    verbose " srcdir: $srcdir"
> +    # "srcdir" is that of the gcc/testsuite directory, so
> +    # we need to go up two levels.
> +    set arg [string map [list "SRCDIR" $srcdir/../..] $arg]
> +    verbose " new arg: $arg"
> +    return $arg
> +}
> +
> +# Parameters used when invoking the executables built from the test cases.
> +
> +global diagnostics-exe-params
> +set diagnostics-exe-params {}
> +
> +# Set "diagnostics-exe-params", expanding "SRCDIR" in each arg to the location of
> +# the top-level srcdir.
> +
> +proc dg-diagnostics-set-exe-params { args } {
> +    verbose "dg-diagnostics-set-exe-params: $args"
> +
> +    global diagnostics-exe-params
> +    set diagnostics-exe-params {}
> +    # Skip initial arg (line number)
> +    foreach arg [lrange $args 1 [llength $args] ] {
> +	lappend diagnostics-exe-params [diagnostics-expand-vars $arg]
> +    }
> +}
> +
> +proc diagnostics-dg-test { prog do_what extra_tool_flags } {
> +    verbose "within diagnostics-dg-test..."
> +    verbose "  prog: $prog"
> +    verbose "  do_what: $do_what"
> +    verbose "  extra_tool_flags: $extra_tool_flags"
> +
> +    global dg-do-what-default
> +    set dg-do-what [list ${dg-do-what-default} "" P]
> +
> +    set tmp [dg-get-options $prog]
> +    foreach op $tmp {
> +	verbose "Processing option: $op" 3
> +	set status [catch "$op" errmsg]
> +	if { $status != 0 } {
> +	    if { 0 && [info exists errorInfo] } {
> +		# This also prints a backtrace which will just confuse
> +		# testcase writers, so it's disabled.
> +		perror "$name: $errorInfo\n"
> +	    } else {
> +		perror "$name: $errmsg for \"$op\"\n"
> +	    }
> +	    perror "$name: $errmsg for \"$op\"" 0
> +	    return
> +	}
> +    }
> +
> +    # If we're not supposed to try this test on this target, we're done.
> +    if { [lindex ${dg-do-what} 1] == "N" } {
> +	unsupported "$name"
> +	verbose "$name not supported on this target, skipping it" 3
> +	return
> +    }
> +
> +    # Determine what to name the built executable.
> +    #
> +    # We simply append .exe to the filename, e.g.
> +    #  "test-foo.c.exe"
> +    # since some testcases exist in both
> +    #  "test-foo.c" and
> +    #  "test-foo.cc"
> +    # variants, and we don't want them to clobber each other's
> +    # executables.
> +    #
> +    # This also ensures that the source name makes it into the
> +    # pass/fail output, so that we can distinguish e.g. which test-foo
> +    # is failing.
> +    set output_file "[file tail $prog].exe"
> +    verbose "output_file: $output_file"
> +
> +    # Create the test executable:
> +    set extension [file extension $prog]
> +    if {$extension == ".cc"} {
> +	set compilation_function "g++_target_compile"
> +	set options "{additional_flags=$extra_tool_flags}"
> +    } else {
> +	set compilation_function "gcc_target_compile"
> +	# Until recently, <dejagnu.h> assumed -fgnu89-inline
> +	# Ideally we should fixincludes it (PR other/63613), but
> +	# for now add -fgnu89-inline when compiling C JIT testcases.
> +	# See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63613
> +	# and http://lists.gnu.org/archive/html/dejagnu/2014-10/msg00011.html
> +	set options "{additional_flags=$extra_tool_flags -fgnu89-inline}"
> +    }
> +    verbose "compilation_function=$compilation_function"
> +    verbose "options=$options"
> +
> +    set comp_output [$compilation_function $prog $output_file \
> +			 "executable" $options]
> +    upvar 1 name name
> +    if ![diagnostics_check_compile "$name" "initial compilation" \
> +	    $output_file $comp_output] then {
> +      return
> +    }
> +
> +    # Run the test executable, capturing the PASS/FAIL textual output
> +    # from the C API, converting it into the Tcl API.
> +
> +    # We need to set LD_LIBRARY_PATH so that the test files can find
> +    # libdiagnostics.so
> +    # Do this using set_ld_library_path_env_vars from target-libpath.exp
> +    # We will restore the old value later using
> +    # restore_ld_library_path_env_vars.
> +
> +    # Unfortunately this API only supports a single saved value, rather
> +    # than a stack, and g++_init has already called into this API,
> +    # injecting the appropriate value for LD_LIBRARY_PATH for finding
> +    # the built copy of libstdc++.
> +    # Hence the call to restore_ld_library_path_env_vars would restore
> +    # the *initial* value of LD_LIBRARY_PATH, and attempts to run
> +    # a C++ testcase after running any prior testcases would thus look
> +    # in the wrong place for libstdc++.  This led to failures at startup
> +    # of the form:
> +    #   ./tut01-hello-world.cc.exe: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by ./tut01-hello-world.cc.exe)
> +    # when the built libstdc++ is more recent that the system libstdc++.
> +    #
> +    # As a workaround, reset the variable "orig_environment_saved" within
> +    # target-libpath.exp, so that the {set|restore}_ld_library_path_env_vars
> +    # API saves/restores the current value of LD_LIBRARY_PATH (as set up
> +    # by g++_init).
> +    global orig_environment_saved
> +    set orig_environment_saved 0
> +
> +    global ld_library_path
> +    global base_dir
> +    set ld_library_path "$base_dir/../../"
> +    set_ld_library_path_env_vars
> +
> +    # dejagnu.exp's host_execute has code to scrape out test results
> +    # from the DejaGnu C API and bring back into the tcl world, so we
> +    # use that to invoke the built code.
> +    # However, it appears to be buggy; see:
> +    #  http://lists.gnu.org/archive/html/dejagnu/2014-10/msg00000.html
> +    # We instead call a patched local copy, "fixed_host_execute", defined
> +    # above.
> +
> +    global diagnostics-exe-params
> +    set args ${diagnostics-exe-params}
> +    set diagnostics-exe-params {}
> +
> +    set result [fixed_host_execute $output_file $args ]
> +    verbose "result: $result"
> +
> +    restore_ld_library_path_env_vars
> +
> +    # Normally we would return $comp_output and $output_file to the
> +    # caller, which would delete $output_file, the generated executable.
> +    # If we need to debug, it's handy to be able to suppress this behavior,
> +    # keeping the executable around.
> +
> +    global env
> +    set preserve_executables [info exists env(PRESERVE_EXECUTABLES)]
> +    if $preserve_executables {
> +	set output_file ""
> +    }
> +
> +    return [list $comp_output $output_file]
> +}
> +
> +set DEFAULT_CFLAGS "-I$srcdir/.. -ldiagnostics -g -Wall -Werror"
> +
> +# Main loop.  This will invoke jig-dg-test on each test-*.c file.
> +dg-runtest $tests "" $DEFAULT_CFLAGS
> +
> +# All done.
> +dg-finish

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH] binutils: experimental use of libdiagnostics in gas
  2023-11-06 22:29 ` [PATCH] binutils: experimental use of libdiagnostics in gas David Malcolm
  2023-11-07  7:04   ` Simon Sobisch
@ 2023-11-07  9:21   ` Clément Chigot
  2023-11-07 14:09     ` David Malcolm
  2023-11-07 10:03   ` Jan Beulich
  2 siblings, 1 reply; 29+ messages in thread
From: Clément Chigot @ 2023-11-07  9:21 UTC (permalink / raw)
  To: David Malcolm; +Cc: gcc-patches, binutils, Nick Clifton, Simon Sobisch

Hi David,

Thanks for that interesting RFC ! I'm fully in favor of such
improvements, having uniformed error messages across gcc, gas and
later ld, would greatly help integration of these tools, let alone the
SARIF format output.
However, I'm not sure how you're planning to make the transition. But
currently, it looks like libdiagnostics is either enabled and thus the
new format being produced, either it's not and we do have the legacy
format. I think the transition should be smoother than that, there are
probably thousands of tests, scripts, whatever out in the wild
expecting this legacy format. Allowing both formats within the same
executable, basically chosen by a flag, would probably ease the
transition.

Apart from that, just a few remarks on the implementation details, see below.

Thanks,
Clément

On Mon, Nov 6, 2023 at 11:30 PM David Malcolm <dmalcolm@redhat.com> wrote:
>
> Here's a patch for gas in binutils that makes it use libdiagnostics
> (with some nasty hardcoded paths to specific places on my hard drive
> to make it easier to develop the API).
>
> For now this hardcodes adding two sinks: a text sink on stderr, and
> also a SARIF output to stderr (which happens after all regular output).
>
> For example, without this patch:
>
>    gas testsuite/gas/all/warn-1.s
>
> emits:
> VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
> testsuite/gas/all/warn-1.s: Assembler messages:
> testsuite/gas/all/warn-1.s:3: Warning: a warning message
> testsuite/gas/all/warn-1.s:4: Error: .warning argument must be a string
> testsuite/gas/all/warn-1.s:5: Warning: .warning directive invoked in source file
> testsuite/gas/all/warn-1.s:6: Warning: .warning directive invoked in source file
> testsuite/gas/all/warn-1.s:7: Warning:
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>
> whereas with this patch:
>   LD_LIBRARY_PATH=/home/david/coding-3/gcc-newgit-canvas-2023/build/gcc ./as-new testsuite/gas/all/warn-1.s
> emits:
>
> VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
> testsuite/gas/all/warn-1.s:3: warning: a warning message
>     3 |  .warning "a warning message"   ;# { dg-warning "Warning: a warning message" }
>       |
> testsuite/gas/all/warn-1.s:4: error: .warning argument must be a string
>     4 |  .warning a warning message     ;# { dg-error "Error: .warning argument must be a string" }
>       |
> testsuite/gas/all/warn-1.s:5: warning: .warning directive invoked in source file
>     5 |  .warning                       ;# { dg-warning "Warning: .warning directive invoked in source file" }
>       |
> testsuite/gas/all/warn-1.s:6: warning: .warning directive invoked in source file
>     6 |  .warning ".warning directive invoked in source file"   ;# { dg-warning "Warning: .warning directive invoked in source file" }
>       |
> testsuite/gas/all/warn-1.s:7: warning:
>     7 |  .warning ""                    ;# { dg-warning "Warning: " }
>       |
>         {"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", "version": "2.1.0", "runs": [{"tool": {"driver": {"rules": []}}, "invocations": [{"executionSuccessful": true, "toolExecutionNotifications": []}], "originalUriBaseIds": {"PWD": {"uri": "file:///home/david/coding-3/binutils-gdb/gas/"}}, "artifacts": [{"location": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "contents": {"text": ";# Test .warning directive.\n;# { dg-do assemble }\n .warning \"a warning message\"\t;# { dg-warning \"Warning: a warning message\" }\n .warning a warning message\t;# { dg-error \"Error: .warning argument must be a string\" }\n .warning\t\t\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n .warning \".warning directive invoked in source file\"\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n .warning \"\"\t\t\t;# { dg-warning \"Warning: \" }\n"}}], "results": [{"ruleId": "warning", "level": "warning", "message": {"text": "a warning message"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 3, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 3, "snippet": {"text": " .warning \"a warning message\"\t;# { dg-warning \"Warning: a warning message\" }\n"}}}}], "relatedLocations": [{"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 4, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 4, "snippet": {"text": " .warning a warning message\t;# { dg-error \"Error: .warning argument must be a string\" }\n"}}}, "message": {"text": ".warning argument must be a string"}}, {"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 5, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 5, "snippet": {"text": " .warning\t\t\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n"}}}, "message": {"text": ".warning directive invoked in source file"}}, {"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 6, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 6, "snippet": {"text": " .warning \".warning directive invoked in source file\"\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n"}}}, "message": {"text": ".warning directive invoked in source file"}}, {"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 7, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 7, "snippet": {"text": " .warning \"\"\t\t\t;# { dg-warning \"Warning: \" }\n"}}}, "message": {"text": ""}}]}]}]}
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>
> which I see:
> - drops the leading "Assembler messages" warning,
> - changes the capitalization of the "Warning" -> "warning" etc
> - quotes the pertinent line in the .s file
>
> All of the locations are just lines; does gas do column numbers at all?
> (or ranges?)
>
> For reference, running the above SARIF through "python -m json.tool" to
> prettyify it gives:
>
> VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
> {
>     "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
>     "version": "2.1.0",
>     "runs": [
>         {
>             "tool": {
>                 "driver": {
>                     "rules": []
>                 }
>             },
>             "invocations": [
>                 {
>                     "executionSuccessful": true,
>                     "toolExecutionNotifications": []
>                 }
>             ],
>             "originalUriBaseIds": {
>                 "PWD": {
>                     "uri": "file:///home/david/coding-3/binutils-gdb/gas/"
>                 }
>             },
>             "artifacts": [
>                 {
>                     "location": {
>                         "uri": "testsuite/gas/all/warn-1.s",
>                         "uriBaseId": "PWD"
>                     },
>                     "contents": {
>                         "text": ";# Test .warning directive.\n;# { dg-do assemble }\n .warning \"a warning message\"\t;# { dg-warning \"Warning: a warning message\" }\n .warning a warning message\t;# { dg-error \"Error: .warning argument must be a string\" }\n .warning\t\t\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n .warning \".warning directive invoked in source file\"\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n .warning \"\"\t\t\t;# { dg-warning \"Warning: \" }\n"
>                     }
>                 }
>             ],
>             "results": [
>                 {
>                     "ruleId": "warning",
>                     "level": "warning",
>                     "message": {
>                         "text": "a warning message"
>                     },
>                     "locations": [
>                         {
>                             "physicalLocation": {
>                                 "artifactLocation": {
>                                     "uri": "testsuite/gas/all/warn-1.s",
>                                     "uriBaseId": "PWD"
>                                 },
>                                 "region": {
>                                     "startLine": 3,
>                                     "startColumn": 0,
>                                     "endColumn": 1
>                                 },
>                                 "contextRegion": {
>                                     "startLine": 3,
>                                     "snippet": {
>                                         "text": " .warning \"a warning message\"\t;# { dg-warning \"Warning: a warning message\" }\n"
>                                     }
>                                 }
>                             }
>                         }
>                     ],
>                     "relatedLocations": [
>                         {
>                             "physicalLocation": {
>                                 "artifactLocation": {
>                                     "uri": "testsuite/gas/all/warn-1.s",
>                                     "uriBaseId": "PWD"
>                                 },
>                                 "region": {
>                                     "startLine": 4,
>                                     "startColumn": 0,
>                                     "endColumn": 1
>                                 },
>                                 "contextRegion": {
>                                     "startLine": 4,
>                                     "snippet": {
>                                         "text": " .warning a warning message\t;# { dg-error \"Error: .warning argument must be a string\" }\n"
>                                     }
>                                 }
>                             },
>                             "message": {
>                                 "text": ".warning argument must be a string"
>                             }
>                         },
>                         {
>                             "physicalLocation": {
>                                 "artifactLocation": {
>                                     "uri": "testsuite/gas/all/warn-1.s",
>                                     "uriBaseId": "PWD"
>                                 },
>                                 "region": {
>                                     "startLine": 5,
>                                     "startColumn": 0,
>                                     "endColumn": 1
>                                 },
>                                 "contextRegion": {
>                                     "startLine": 5,
>                                     "snippet": {
>                                         "text": " .warning\t\t\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n"
>                                     }
>                                 }
>                             },
>                             "message": {
>                                 "text": ".warning directive invoked in source file"
>                             }
>                         },
>                         {
>                             "physicalLocation": {
>                                 "artifactLocation": {
>                                     "uri": "testsuite/gas/all/warn-1.s",
>                                     "uriBaseId": "PWD"
>                                 },
>                                 "region": {
>                                     "startLine": 6,
>                                     "startColumn": 0,
>                                     "endColumn": 1
>                                 },
>                                 "contextRegion": {
>                                     "startLine": 6,
>                                     "snippet": {
>                                         "text": " .warning \".warning directive invoked in source file\"\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n"
>                                     }
>                                 }
>                             },
>                             "message": {
>                                 "text": ".warning directive invoked in source file"
>                             }
>                         },
>                         {
>                             "physicalLocation": {
>                                 "artifactLocation": {
>                                     "uri": "testsuite/gas/all/warn-1.s",
>                                     "uriBaseId": "PWD"
>                                 },
>                                 "region": {
>                                     "startLine": 7,
>                                     "startColumn": 0,
>                                     "endColumn": 1
>                                 },
>                                 "contextRegion": {
>                                     "startLine": 7,
>                                     "snippet": {
>                                         "text": " .warning \"\"\t\t\t;# { dg-warning \"Warning: \" }\n"
>                                     }
>                                 }
>                             },
>                             "message": {
>                                 "text": ""
>                             }
>                         }
>                     ]
>                 }
>             ]
>         }
>     ]
> }
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>
> Thoughts?
>
> gas/ChangeLog:
>         * Makefile.am (GASLIBS): Add nasty hack with hardcoded path
>         on my hard drive.
>         * Makefile.in (GASLIBS): Likewise.
>         * as.c (gas_init): Call messages_init.
>         (main): Call messages_end.
>         * as.h (messages_init): New decl.
>         (messages_end): New decl.
>         * messages.c (USE_LIBDIAGNOSTICS): New define.
>         Add #include with nasty hardcoded path.
>         (diag_mgr): New.
>         (messages_init): New.
>         (messages_end): New.
>         (as_warn_internal): Add support for libdiagnostics.
>         (as_bad_internal): Likewise.
> ---
>  gas/Makefile.am |  3 ++-
>  gas/Makefile.in |  4 ++-
>  gas/as.c        |  3 +++
>  gas/as.h        |  3 +++
>  gas/messages.c  | 68 +++++++++++++++++++++++++++++++++++++++++++++++++
>  5 files changed, 79 insertions(+), 2 deletions(-)
>
> diff --git a/gas/Makefile.am b/gas/Makefile.am
> index 0e98ca3ec85..fe92997129c 100644
> --- a/gas/Makefile.am
> +++ b/gas/Makefile.am
> @@ -408,7 +408,8 @@ AM_CPPFLAGS = -I. -I$(srcdir) -I../bfd -I$(srcdir)/config \
>  # How to link with both our special library facilities
>  # and the system's installed libraries.
>
> -GASLIBS = @OPCODES_LIB@ ../bfd/libbfd.la ../libiberty/libiberty.a
> +# FIXME:
> +GASLIBS = @OPCODES_LIB@ ../bfd/libbfd.la ../libiberty/libiberty.a /home/david/coding-3/gcc-newgit-canvas-2023/build/gcc/libdiagnostics.so
>
>  # Files to be copied away after each stage in building.
>  STAGESTUFF = *.@OBJEXT@ $(noinst_PROGRAMS)
> diff --git a/gas/Makefile.in b/gas/Makefile.in
> index fae3a47c144..2161d68f9c7 100644
> --- a/gas/Makefile.in
> +++ b/gas/Makefile.in
> @@ -885,7 +885,9 @@ AM_CPPFLAGS = -I. -I$(srcdir) -I../bfd -I$(srcdir)/config \
>
>  # How to link with both our special library facilities
>  # and the system's installed libraries.
> -GASLIBS = @OPCODES_LIB@ ../bfd/libbfd.la ../libiberty/libiberty.a
> +
> +# FIXME:
> +GASLIBS = @OPCODES_LIB@ ../bfd/libbfd.la ../libiberty/libiberty.a /home/david/coding-3/gcc-newgit-canvas-2023/build/gcc/libdiagnostics.so
>
>  # Files to be copied away after each stage in building.
>  STAGESTUFF = *.@OBJEXT@ $(noinst_PROGRAMS)
> diff --git a/gas/as.c b/gas/as.c
> index 6839c841588..2a8ce3734a0 100644
> --- a/gas/as.c
> +++ b/gas/as.c
> @@ -1300,6 +1300,7 @@ gas_early_init (int *argcp, char ***argvp)
>  static void
>  gas_init (void)
>  {
> +  messages_init ();
>    symbol_begin ();
>    frag_init ();
>    subsegs_begin ();
> @@ -1486,6 +1487,8 @@ main (int argc, char ** argv)
>
>    input_scrub_end ();
>
> +  messages_end ();
> +
>    /* Use xexit instead of return, because under VMS environments they
>       may not place the same interpretation on the value given.  */
>    if (had_errors () != 0)
> diff --git a/gas/as.h b/gas/as.h
> index 99ffe77afd2..9d878a87df5 100644
> --- a/gas/as.h
> +++ b/gas/as.h
> @@ -474,6 +474,9 @@ void   as_bad_value_out_of_range (const char *, offsetT, offsetT, offsetT,
>  void   print_version_id (void);
>  char * app_push (void);
>
> +void messages_init (void);
> +void messages_end (void);
> +
>  /* Number of littlenums required to hold an extended precision number. */
>  #define MAX_LITTLENUMS 6
>
> diff --git a/gas/messages.c b/gas/messages.c
> index 7c018acf69f..3cb8525fad9 100644
> --- a/gas/messages.c
> +++ b/gas/messages.c
> @@ -21,6 +21,14 @@
>  #include <limits.h>
>  #include <signal.h>
>
> +// FIXME: do some configury thing for this
> +#define USE_LIBDIAGNOSTICS 1
> +
> +#if USE_LIBDIAGNOSTICS
> +// FIXME: need to fix this path, obviously
> +#include "/home/david/coding-3/gcc-newgit-canvas-2023/src/gcc/libdiagnostics.h"
> +#endif
> +
>  /* If the system doesn't provide strsignal, we get it defined in
>     libiberty but no declaration is supplied.  Because, reasons. */
>  #if !defined (HAVE_STRSIGNAL) && !defined (strsignal)
> @@ -101,6 +109,29 @@ had_warnings (void)
>    return warning_count;
>  }
>
> +#if USE_LIBDIAGNOSTICS
> +static diagnostic_manager *diag_mgr;

Would it make sense for an application to have several
"diagnostic_manager" ? If no, I'm wondering if this variable shouldn't
be hidden inside libdiagnostics itself, avoiding every calls to have
this diag_mgr argument.

> +#endif
> +
> +void messages_init (void)
> +{
> +#if USE_LIBDIAGNOSTICS
> +  diag_mgr = diagnostic_manager_new ();
> +  diagnostic_manager_add_text_sink (diag_mgr, stderr,
> +                                   DIAGNOSTIC_COLORIZE_IF_TTY);
> +  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
> +                                    DIAGNOSTIC_SARIF_VERSION_2_1_0);
> +#endif
> +}
> +
> +void messages_end (void)
> +{
> +#if USE_LIBDIAGNOSTICS
> +  diagnostic_manager_release (diag_mgr);

IIUC, diagnostic_manager_release must be called to produce any output.
However, nothing prevents the application to exit earlier see
"as_fatal". Thus, this probably need to be called using atexit to
ensure that whatever happens the messages are being outputted.

> +  diag_mgr = NULL;
> +#endif
> +}
> +
>  /* Nonzero if we've hit a 'bad error', and should not write an obj file,
>     and exit with a nonzero error code.  */
>
> @@ -172,16 +203,34 @@ as_tsktsk (const char *format, ...)
>  static void
>  as_warn_internal (const char *file, unsigned int line, char *buffer)
>  {
> +#if !USE_LIBDIAGNOSTICS
>    bool context = false;
> +#endif
>
>    ++warning_count;
>
>    if (file == NULL)
>      {
>        file = as_where_top (&line);
> +#if !USE_LIBDIAGNOSTICS
>        context = true;
> +#endif
>      }
>
> +#if USE_LIBDIAGNOSTICS
> +  const diagnostic_file *file_obj
> +    = diagnostic_manager_new_file (diag_mgr, file, NULL);
> +
> +  diagnostic_location_t loc
> +    = diagnostic_manager_new_location_from_file_and_line (diag_mgr,
> +                                                         file_obj,
> +                                                         line);
> +
> +  diagnostic *d = diagnostic_begin (diag_mgr,
> +                                   DIAGNOSTIC_LEVEL_WARNING);
> +  diagnostic_set_location (d, loc);
> +  diagnostic_finish (d, "%s", buffer);
> +#else
>    identify (file);
>    if (file)
>      {
> @@ -199,6 +248,7 @@ as_warn_internal (const char *file, unsigned int line, char *buffer)
>  #ifndef NO_LISTING
>    listing_warning (buffer);
>  #endif
> +#endif /* #else clause of #if USE_LIBDIAGNOSTICS */
>  }
>
>  /* Send to stderr a string as a warning, and locate warning
> @@ -246,16 +296,33 @@ as_warn_where (const char *file, unsigned int line, const char *format, ...)
>  static void
>  as_bad_internal (const char *file, unsigned int line, char *buffer)
>  {
> +#if !USE_LIBDIAGNOSTICS
>    bool context = false;
> +#endif
>
>    ++error_count;
>
>    if (file == NULL)
>      {
>        file = as_where_top (&line);
> +#if !USE_LIBDIAGNOSTICS
>        context = true;
> +#endif
>      }
>
> +#if USE_LIBDIAGNOSTICS
> +  const diagnostic_file *file_obj
> +    = diagnostic_manager_new_file (diag_mgr, file, NULL);
> +  diagnostic_location_t loc
> +    = diagnostic_manager_new_location_from_file_and_line (diag_mgr,
> +                                                         file_obj,
> +                                                         line);
> +
> +  diagnostic *d = diagnostic_begin (diag_mgr,
> +                                   DIAGNOSTIC_LEVEL_ERROR);
> +  diagnostic_set_location (d, loc);
> +  diagnostic_finish (d, "%s", buffer);
> +#else
>    identify (file);
>    if (file)
>      {
> @@ -269,6 +336,7 @@ as_bad_internal (const char *file, unsigned int line, char *buffer)
>
>    if (context)
>      as_report_context ();
> +#endif /* #else clause of #if USE_LIBDIAGNOSTICS */
>
>  #ifndef NO_LISTING
>    listing_error (buffer);
> --
> 2.26.3
>

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH] binutils: experimental use of libdiagnostics in gas
  2023-11-06 22:29 ` [PATCH] binutils: experimental use of libdiagnostics in gas David Malcolm
  2023-11-07  7:04   ` Simon Sobisch
  2023-11-07  9:21   ` Clément Chigot
@ 2023-11-07 10:03   ` Jan Beulich
  2023-11-07 14:32     ` David Malcolm
  2 siblings, 1 reply; 29+ messages in thread
From: Jan Beulich @ 2023-11-07 10:03 UTC (permalink / raw)
  To: David Malcolm; +Cc: Nick Clifton, Simon Sobisch, gcc-patches, binutils

On 06.11.2023 23:29, David Malcolm wrote:
> Here's a patch for gas in binutils that makes it use libdiagnostics
> (with some nasty hardcoded paths to specific places on my hard drive
> to make it easier to develop the API).
> 
> For now this hardcodes adding two sinks: a text sink on stderr, and
> also a SARIF output to stderr (which happens after all regular output).
> 
> For example, without this patch:
> 
>    gas testsuite/gas/all/warn-1.s
> 
> emits:
> VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
> testsuite/gas/all/warn-1.s: Assembler messages:
> testsuite/gas/all/warn-1.s:3: Warning: a warning message
> testsuite/gas/all/warn-1.s:4: Error: .warning argument must be a string
> testsuite/gas/all/warn-1.s:5: Warning: .warning directive invoked in source file
> testsuite/gas/all/warn-1.s:6: Warning: .warning directive invoked in source file
> testsuite/gas/all/warn-1.s:7: Warning:
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 
> whereas with this patch:
>   LD_LIBRARY_PATH=/home/david/coding-3/gcc-newgit-canvas-2023/build/gcc ./as-new testsuite/gas/all/warn-1.s
> emits:
> 
> VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
> testsuite/gas/all/warn-1.s:3: warning: a warning message
>     3 |  .warning "a warning message"   ;# { dg-warning "Warning: a warning message" }
>       |
> testsuite/gas/all/warn-1.s:4: error: .warning argument must be a string
>     4 |  .warning a warning message     ;# { dg-error "Error: .warning argument must be a string" }
>       |
> testsuite/gas/all/warn-1.s:5: warning: .warning directive invoked in source file
>     5 |  .warning                       ;# { dg-warning "Warning: .warning directive invoked in source file" }
>       |
> testsuite/gas/all/warn-1.s:6: warning: .warning directive invoked in source file
>     6 |  .warning ".warning directive invoked in source file"   ;# { dg-warning "Warning: .warning directive invoked in source file" }
>       |
> testsuite/gas/all/warn-1.s:7: warning:
>     7 |  .warning ""                    ;# { dg-warning "Warning: " }
>       |
> 	{"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", "version": "2.1.0", "runs": [{"tool": {"driver": {"rules": []}}, "invocations": [{"executionSuccessful": true, "toolExecutionNotifications": []}], "originalUriBaseIds": {"PWD": {"uri": "file:///home/david/coding-3/binutils-gdb/gas/"}}, "artifacts": [{"location": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "contents": {"text": ";# Test .warning directive.\n;# { dg-do assemble }\n .warning \"a warning message\"\t;# { dg-warning \"Warning: a warning message\" }\n .warning a warning message\t;# { dg-error \"Error: .warning argument must be a string\" }\n .warning\t\t\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n .warning \".warning directive invoked in source file\"\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n .warning \"\"\t\t\t;# { dg-warning \"Warning: \" }\n"}}], "results": [{"ruleId": "warning", "level": "warning", "message": {"text": "a warning message"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 3, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 3, "snippet": {"text": " .warning \"a warning message\"\t;# { dg-warning \"Warning: a warning message\" }\n"}}}}], "relatedLocations": [{"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 4, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 4, "snippet": {"text": " .warning a warning message\t;# { dg-error \"Error: .warning argument must be a string\" }\n"}}}, "message": {"text": ".warning argument must be a string"}}, {"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 5, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 5, "snippet": {"text": " .warning\t\t\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n"}}}, "message": {"text": ".warning directive invoked in source file"}}, {"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 6, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 6, "snippet": {"text": " .warning \".warning directive invoked in source file\"\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n"}}}, "message": {"text": ".warning directive invoked in source file"}}, {"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 7, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 7, "snippet": {"text": " .warning \"\"\t\t\t;# { dg-warning \"Warning: \" }\n"}}}, "message": {"text": ""}}]}]}]}
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 
> which I see:
> - drops the leading "Assembler messages" warning,
> - changes the capitalization of the "Warning" -> "warning" etc
> - quotes the pertinent line in the .s file
> 
> All of the locations are just lines; does gas do column numbers at all?
> (or ranges?)

It currently doesn't, which is primarily related to the scrubbing done
before lines are actually processed.

I take it that the lack of column information is why there are lines of
this form

      |

in the example output above. Them uniformly not carrying any information
would make it desirable for them to be suppressed.

> @@ -172,16 +203,34 @@ as_tsktsk (const char *format, ...)
>  static void
>  as_warn_internal (const char *file, unsigned int line, char *buffer)
>  {
> +#if !USE_LIBDIAGNOSTICS
>    bool context = false;
> +#endif
>  
>    ++warning_count;
>  
>    if (file == NULL)
>      {
>        file = as_where_top (&line);
> +#if !USE_LIBDIAGNOSTICS
>        context = true;
> +#endif

I can't spot how this context information would be replaced. It works
for macros only right now, but the hope is to eventually extend it
also to .include files.

> @@ -199,6 +248,7 @@ as_warn_internal (const char *file, unsigned int line, char *buffer)
>  #ifndef NO_LISTING
>    listing_warning (buffer);
>  #endif
> +#endif /* #else clause of #if USE_LIBDIAGNOSTICS */

This listing integration of course needs to remain irrespective of
which way of emitting diagnostics is used.

Jan

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH] binutils: experimental use of libdiagnostics in gas
  2023-11-07  9:21   ` Clément Chigot
@ 2023-11-07 14:09     ` David Malcolm
  2023-11-07 15:57       ` Clément Chigot
  0 siblings, 1 reply; 29+ messages in thread
From: David Malcolm @ 2023-11-07 14:09 UTC (permalink / raw)
  To: Clément Chigot; +Cc: gcc-patches, binutils, Nick Clifton, Simon Sobisch

On Tue, 2023-11-07 at 10:21 +0100, Clément Chigot wrote:
> Hi David,
> 
> Thanks for that interesting RFC ! I'm fully in favor of such
> improvements, having uniformed error messages across gcc, gas and
> later ld, would greatly help integration of these tools, let alone
> the
> SARIF format output.

Indeed, I can imagine that ld might eventually want to use this as
well.

> However, I'm not sure how you're planning to make the transition. But
> currently, it looks like libdiagnostics is either enabled and thus
> the
> new format being produced, either it's not and we do have the legacy
> format. I think the transition should be smoother than that, there
> are
> probably thousands of tests, scripts, whatever out in the wild
> expecting this legacy format. Allowing both formats within the same
> executable, basically chosen by a flag, would probably ease the
> transition.

Yes.  I'm assuming that consumers of libdiagnostics would have a
configure-time test for the availability of libdiagnostics.  In the
example I gave, it was just a compile-time "choice" (I'm not an expert
at autotools, so I hacked all of that up for now)... but if the feature
is available, it could be a run-time choice.

We've been adding new features to GCC's diagnostic output over the
years (adding column numbers, showing macro expansions, quoting source
code with underlines, fix-it hints, etc), but each time we've added a
flag to turn them off (e.g. -fno-diagnostics-show-line-numbers,  -fno-
diagnostics-show-labels, etc).

As of GCC 11 we have a -fdiagnostics-plain-output which "requests that
diagnostic output look as plain as possible, which may be useful when
running dejagnu or other utilities that need to parse diagnostics
output and prefer that it remain more stable over time."

In the implementation patch I made the text sink turn on everything by
default here:
  m_dc.m_source_printing.enabled = true; // FIXME
  m_dc.m_source_printing.colorize_source_p = true; // FIXME
  m_dc.m_source_printing.show_labels_p = true; // FIXME
  m_dc.m_source_printing.show_line_numbers_p = true; // FIXME
  m_dc.m_source_printing.min_margin_width = 6; // FIXME
and I didn't provide a way of turning things off.  So maybe the API
needs a way to tweak options of the text sink?  Maybe:

  diagnostic_text_sink_set_source_printing (sink, true);
  diagnostic_text_sink_set_colorize_source (sink, COLORIZE_IF_AT_TTY);

...etc.

Also, I made no particular effort to make the output similar to before,
hence e.g. the difference in capitalization "Error: " vs "error: ".  Is
that capitalization something that you'd want to remain consistent?

> 
> Apart from that, just a few remarks on the implementation details,
> see below.

[...snip...]

> > @@ -101,6 +109,29 @@ had_warnings (void)
> >    return warning_count;
> >  }
> > 
> > +#if USE_LIBDIAGNOSTICS
> > +static diagnostic_manager *diag_mgr;
> 
> Would it make sense for an application to have several
> "diagnostic_manager" ? 
> If no, I'm wondering if this variable shouldn't
> be hidden inside libdiagnostics itself, avoiding every calls to have
> this diag_mgr argument.

Although it might not make sense for binutils-style use-cases to have
multiple diagnostic_manager instances (since these are implemented all
standalone programs), I think in general it *does* make sense.

I've found it's usually a bad idea for a shared library to have global
state, since at some point a consumer of the library is a shared
library itself, at which point users of the 2nd library see unexpected
interactions.

Consider the case of a linting tool implemented as a shared library:
the tool has no knowledge of where it's going to be embedded: e.g. in
an IDE, or as part of some other system.  Perhaps the IDE is
multithreaded.  So I think it's better for the user of the diagnostic
API (here the lint tool) to have an explicit "context" pointer that
it's sending diagnostics to, rather than having it be implicit inside
the library.


> 
> > +#endif
> > +
> > +void messages_init (void)
> > +{
> > +#if USE_LIBDIAGNOSTICS
> > +  diag_mgr = diagnostic_manager_new ();
> > +  diagnostic_manager_add_text_sink (diag_mgr, stderr,
> > +                                   DIAGNOSTIC_COLORIZE_IF_TTY);
> > +  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
> > +                                   
> > DIAGNOSTIC_SARIF_VERSION_2_1_0);
> > +#endif
> > +}
> > +
> > +void messages_end (void)
> > +{
> > +#if USE_LIBDIAGNOSTICS
> > +  diagnostic_manager_release (diag_mgr);
> 
> IIUC, diagnostic_manager_release must be called to produce any
> output.

The text sink emits (and flushes) each diagnostic to the FILE * stream
after diagnostic_finish is called on it.

The sarif sink accumulates a JSON representation in memory, and only
writes to its FILE * after the manager is released (since there are
aspects of the metadata part of the format that requiring knowing about
all diagnostics upfront).

> However, nothing prevents the application to exit earlier see
> "as_fatal". Thus, this probably need to be called using atexit to
> ensure that whatever happens the messages are being outputted.

atexit handlers are per-process global state, so I'm thinking that
being something the client would register, rather than libdiagnostics
doing it automatically.


[...snip...]

Thanks for the feedback; hope the above sounds reasonable
Dave


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH] binutils: experimental use of libdiagnostics in gas
  2023-11-07 10:03   ` Jan Beulich
@ 2023-11-07 14:32     ` David Malcolm
  2023-11-07 14:59       ` Jan Beulich
  0 siblings, 1 reply; 29+ messages in thread
From: David Malcolm @ 2023-11-07 14:32 UTC (permalink / raw)
  To: Jan Beulich; +Cc: Nick Clifton, Simon Sobisch, gcc-patches, binutils

On Tue, 2023-11-07 at 11:03 +0100, Jan Beulich wrote:
> On 06.11.2023 23:29, David Malcolm wrote:
> > Here's a patch for gas in binutils that makes it use libdiagnostics
> > (with some nasty hardcoded paths to specific places on my hard
> > drive
> > to make it easier to develop the API).
> > 
> > For now this hardcodes adding two sinks: a text sink on stderr, and
> > also a SARIF output to stderr (which happens after all regular
> > output).
> > 
> > For example, without this patch:
> > 
> >    gas testsuite/gas/all/warn-1.s
> > 
> > emits:
> > VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
> > VVVVVVVVV
> > testsuite/gas/all/warn-1.s: Assembler messages:
> > testsuite/gas/all/warn-1.s:3: Warning: a warning message
> > testsuite/gas/all/warn-1.s:4: Error: .warning argument must be a
> > string
> > testsuite/gas/all/warn-1.s:5: Warning: .warning directive invoked
> > in source file
> > testsuite/gas/all/warn-1.s:6: Warning: .warning directive invoked
> > in source file
> > testsuite/gas/all/warn-1.s:7: Warning:
> > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> > ^^^^^^^^^
> > 
> > whereas with this patch:
> >   LD_LIBRARY_PATH=/home/david/coding-3/gcc-newgit-canvas-
> > 2023/build/gcc ./as-new testsuite/gas/all/warn-1.s
> > emits:
> > 
> > VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
> > VVVVVVVVV
> > testsuite/gas/all/warn-1.s:3: warning: a warning message
> >     3 |  .warning "a warning message"   ;# { dg-warning "Warning: a
> > warning message" }
> >       |
> > testsuite/gas/all/warn-1.s:4: error: .warning argument must be a
> > string
> >     4 |  .warning a warning message     ;# { dg-error "Error:
> > .warning argument must be a string" }
> >       |
> > testsuite/gas/all/warn-1.s:5: warning: .warning directive invoked
> > in source file
> >     5 |  .warning                       ;# { dg-warning "Warning:
> > .warning directive invoked in source file" }
> >       |
> > testsuite/gas/all/warn-1.s:6: warning: .warning directive invoked
> > in source file
> >     6 |  .warning ".warning directive invoked in source file"   ;#
> > { dg-warning "Warning: .warning directive invoked in source file" }
> >       |
> > testsuite/gas/all/warn-1.s:7: warning:
> >     7 |  .warning ""                    ;# { dg-warning "Warning: "
> > }
> >       |

[...snip...]

> > which I see:
> > - drops the leading "Assembler messages" warning,
> > - changes the capitalization of the "Warning" -> "warning" etc
> > - quotes the pertinent line in the .s file
> > 
> > All of the locations are just lines; does gas do column numbers at
> > all?
> > (or ranges?)
> 
> It currently doesn't, which is primarily related to the scrubbing
> done
> before lines are actually processed.

How complicated/desirable would it be to track locations in .s files at
the column level?  I confess I didn't look at the parsing code at all.

> 
> I take it that the lack of column information is why there are lines
> of
> this form
> 
>       |
> 
> in the example output above. 

Yes: those lines are for annotation information such as underlining
specific columns.

> Them uniformly not carrying any information
> would make it desirable for them to be suppressed.

In GCC we typically have column information, so I'd never noticed this
behavior, but it ought to be fixable, to simply not display these if
there's no column info.

> 
> > @@ -172,16 +203,34 @@ as_tsktsk (const char *format, ...)
> >  static void
> >  as_warn_internal (const char *file, unsigned int line, char
> > *buffer)
> >  {
> > +#if !USE_LIBDIAGNOSTICS
> >    bool context = false;
> > +#endif
> >  
> >    ++warning_count;
> >  
> >    if (file == NULL)
> >      {
> >        file = as_where_top (&line);
> > +#if !USE_LIBDIAGNOSTICS
> >        context = true;
> > +#endif
> 
> I can't spot how this context information would be replaced. It works
> for macros only right now, but the hope is to eventually extend it
> also to .include files.

I confess I hacked this up, and I didn't check what this code does.
I see now that it calls as_report_context, which iterates over macro
expansions calling as_info_where with successively larger "indent"
values.

I could extend the patch to cover that.

More ambitiously, GCC's location tracking supports recording macro
expansions and include files, and the diagnostics subsystem has a way
of printing this information.  So potentially libdiagnostics could
provide API support for this - but I haven't yet looked at the
feasibility.

> 
> > @@ -199,6 +248,7 @@ as_warn_internal (const char *file, unsigned
> > int line, char *buffer)
> >  #ifndef NO_LISTING
> >    listing_warning (buffer);
> >  #endif
> > +#endif /* #else clause of #if USE_LIBDIAGNOSTICS */
> 
> This listing integration of course needs to remain irrespective of
> which way of emitting diagnostics is used.

Likewise; I think I just put the #endif in the wrong place above.

Thanks for the feedback; hope this is constructive.
Dave


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH] binutils: experimental use of libdiagnostics in gas
  2023-11-07  7:04   ` Simon Sobisch
@ 2023-11-07 14:51     ` David Malcolm
  0 siblings, 0 replies; 29+ messages in thread
From: David Malcolm @ 2023-11-07 14:51 UTC (permalink / raw)
  To: Simon Sobisch, gcc-patches, binutils; +Cc: Nick Clifton

On Tue, 2023-11-07 at 08:04 +0100, Simon Sobisch wrote:
> Thank you very much for this proof-of-concept use!
> 
> Inspecting it raises the following questions to me, both for a
> possible 
> binutils implementation and for the library use in general:
> 
> * How should the application set the relevant context (often lines
> are
>    shown before/after)?

Currently the source-printing code has this logic (in gcc/diagnostic-
show-locus.cc):
- filter locations within the diagnostic to "sufficiently sane" ones
(thus ignoring e.g. ranges that end before they start)
- look at all of the remaining locations that are in the same source
file as the primary location of the diagnostic
- determine a set of runs of source lines; layout::calculate_line_spans
has this comment:

/* We want to print the pertinent source code at a diagnostic.  The
   rich_location can contain multiple locations.  This will have been
   filtered into m_exploc (the caret for the primary location) and
   m_layout_ranges, for those ranges within the same source file.

   We will print a subset of the lines within the source file in question,
   as a collection of "spans" of lines.

   This function populates m_line_spans with an ordered, disjoint list of
   the line spans of interest.

   Printing a gap between line spans takes one line, so, when printing
   line numbers, we allow a gap of up to one line between spans when
   merging, since it makes more sense to print the source line rather than a
   "gap-in-line-numbering" line.  When not printing line numbers, it's
   better to be more explicit about what's going on, so keeping them as
   separate spans is preferred.

   For example, if the primary range is on lines 8-10, with secondary ranges
   covering lines 5-6 and lines 13-15:

     004
     005                   |RANGE 1
     006                   |RANGE 1
     007
     008  |PRIMARY RANGE
     009  |PRIMARY CARET
     010  |PRIMARY RANGE
     011
     012
     013                                |RANGE 2
     014                                |RANGE 2
     015                                |RANGE 2
     016

   With line numbering on, we want two spans: lines 5-10 and lines 13-15.

   With line numbering off (with span headers), we want three spans: lines 5-6,
   lines 8-10, and lines 13-15.  */
(end of quote)

This algorithm could be tweaked if you want extra lines of context,
perhaps having an integer option N for N extra lines of before/after
context around each run.


> * Should it be possible to override msgid used to display the
>    warning/error type?
>    If this would be possible then the text sink in messages_init may
> be
>    adjusted to replace the label with _("Warning") and _("Error"),
> which
>    would leave the text output "as-is" (if the text sink is
> configured to
>    not output the source line); this would make it usable without
>    adjusting the testsuite and to adopt to a standard later.

For the msgids, I was more thinking of the messages of the diagnostics
themselves; for instance, in GCC the error format string:

   "Invalid argument %d for builtin %qF"

has fr.po translation:

   "Argument %d invalide pour la fonction interne %qF"

But it sounds like gas also has capitalized severities (e.g.
"Warning"), so maybe that should simply be a flag in the text sink.

> 
> 
> Notes for the SARIF output:
> * the region contains an error, according to the linked JSON spec
>    startColumn has a minimum of 1 (I guess you'd just leave it away
> if
>    the application did not set it)

Good catch; looks like a bug in my SARIF output code.  I've filed it
for myself as:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=112425


> * the application should have the option to pre-set the
> sourceLanguage
>    for the diagnostic_manager (maybe even make that a positional
> argument
>    that needs to be passed but can be NULL) and override it when
>    specifying a region

Why?

Note that the sourceLanguage can always be NULL.  I considered setting
it for gas, but it's not clear what the value can be, so I just omit
it; see:
  https://github.com/oasis-tcs/sarif-spec/issues/608



[..snip...]

Thanks for the feedback
Dave


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 2/2] libdiagnostics: work-in-progress implementation
  2023-11-07  7:54   ` Simon Sobisch
@ 2023-11-07 14:59     ` David Malcolm
  2023-11-07 15:35       ` Simon Sobisch
  0 siblings, 1 reply; 29+ messages in thread
From: David Malcolm @ 2023-11-07 14:59 UTC (permalink / raw)
  To: Simon Sobisch, gcc-patches, binutils; +Cc: Nick Clifton

On Tue, 2023-11-07 at 08:54 +0100, Simon Sobisch wrote:
> Thank you for our work and providing this patch.
> 
> GCC related questions:
> 
> Is it planned to change GCC diagnostics to use libdiagnostic itself?

No.  GCC uses C++ internally, and the internal diagnostic API is
written in C++. libdiagnostic wraps up this C++ API in a C interface. 
GCC would continue using the C++ interface internally.

> 
> Is it planned to "directly" add features or would the result for GCC
> be 
> identical (apart from build changes)?
> 
> So far it looks like it wouldn't be possible to "just build 
> libdiagnostics", and much less to "just distrubute its source" for
> that 
> purpose, is it?

Correct: libdiagnostics is just an extra .cc file within the rest of
GCC, and almost all the work is being done in other .cc files.

> As building GCC does take a significant amount of resources and 
> system-wide switching to a new GCC version is considered a serious
> task 
> (distributions commonly stay with their major GCC version for quite
> some 
> time), I'd search for an option to building a "self-contained"
> version 
> that does not need the complete necessary toolset and may also be 
> distributed separately.

It's possible to reduce the resources by disabling bootstrapping, and
only enabling a minimal set of languages.

I'd see libdiagnostics as coming from the distribution build of GCC.  I
suppose distributions might want to have a simple build of GCC and ship
just the .so/.h file from libdiagnostics from the build.

> 
> This definitely can come later, too; I _guess_ this would mean moving
> part of GCCs code in a sub-folder libdiagnostics and use it as 
> subproject for configure/make, with then option to run "make dist" in
> that subfolder alone, too.

It would involve a lot of refactoring :)

> 
> The main reason for that would be to allow applications move from
> their 
> previous own diagnostic to libdiagnostics, if it isn't available on
> the 
> system they can build and install it as subproject, too; and to be
> able 
> to build libdiagnostics with a much reduced dependency list.

I can try to come up with a minimal recipe for building gcc if all you
want is libdiagnostics

> 
> 
> Thank you for considering that,
> Simon

Thanks
Dave

[...snip...]


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH] binutils: experimental use of libdiagnostics in gas
  2023-11-07 14:32     ` David Malcolm
@ 2023-11-07 14:59       ` Jan Beulich
  0 siblings, 0 replies; 29+ messages in thread
From: Jan Beulich @ 2023-11-07 14:59 UTC (permalink / raw)
  To: David Malcolm; +Cc: Nick Clifton, Simon Sobisch, gcc-patches, binutils

On 07.11.2023 15:32, David Malcolm wrote:
> On Tue, 2023-11-07 at 11:03 +0100, Jan Beulich wrote:
>> On 06.11.2023 23:29, David Malcolm wrote:
>>> All of the locations are just lines; does gas do column numbers at
>>> all?
>>> (or ranges?)
>>
>> It currently doesn't, which is primarily related to the scrubbing
>> done
>> before lines are actually processed.
> 
> How complicated/desirable would it be to track locations in .s files at
> the column level?  I confess I didn't look at the parsing code at all.

At the parsing level tracking may be feasible, but as said the scrubber
(zapping in particular redundant whitespace, but also doing other
"interesting" things) is the problem point here, imo.

Jan

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 2/2] libdiagnostics: work-in-progress implementation
  2023-11-07 14:59     ` David Malcolm
@ 2023-11-07 15:35       ` Simon Sobisch
  0 siblings, 0 replies; 29+ messages in thread
From: Simon Sobisch @ 2023-11-07 15:35 UTC (permalink / raw)
  To: David Malcolm, gcc-patches, binutils; +Cc: Nick Clifton



Am 07.11.2023 um 15:59 schrieb David Malcolm:
> On Tue, 2023-11-07 at 08:54 +0100, Simon Sobisch wrote:
>> Thank you for our work and providing this patch.
>>
>> GCC related questions:
>>
>> Is it planned to change GCC diagnostics to use libdiagnostic itself?
> 
> No.  GCC uses C++ internally, and the internal diagnostic API is
> written in C++. libdiagnostic wraps up this C++ API in a C interface.
> GCC would continue using the C++ interface internally.

Why not providing both a C and C++ API in libdiagnostics?
GNU programs (and also others) are written in both.

The benefit of using it withing GCC itself "eat your own dogfood" would 
be that more or less any need that GCC has is also found in the 
library... thinking again, this may also make it "too heavy" - not sure.

>>
>> Is it planned to "directly" add features or would the result for GCC
>> be
>> identical (apart from build changes)?
>>
>> So far it looks like it wouldn't be possible to "just build
>> libdiagnostics", and much less to "just distrubute its source" for
>> that
>> purpose, is it?
> 
> Correct: libdiagnostics is just an extra .cc file within the rest of
> GCC, and almost all the work is being done in other .cc files.

Maybe call that "status quo - initial patch"? ;-)

>> As building GCC does take a significant amount of resources and
>> system-wide switching to a new GCC version is considered a serious
>> task
>> (distributions commonly stay with their major GCC version for quite
>> some
>> time), I'd search for an option to building a "self-contained"
>> version
>> that does not need the complete necessary toolset and may also be
>> distributed separately.
> 
> It's possible to reduce the resources by disabling bootstrapping, and
> only enabling a minimal set of languages.
> 
> I'd see libdiagnostics as coming from the distribution build of GCC.  I
> suppose distributions might want to have a simple build of GCC and ship
> just the .so/.h file from libdiagnostics from the build.

Agreed. But I'm as a "user" would like to have that "easy" option, too.
As a maintainer that plans to move to libdiagnostics it would be _very_ 
helpful to be able to use it as a sub-project, in case it isn't available.

>> This definitely can come later, too; I _guess_ this would mean moving
>> part of GCCs code in a sub-folder libdiagnostics and use it as
>> subproject for configure/make, with then option to run "make dist" in
>> that subfolder alone, too.
> 
> It would involve a lot of refactoring :)

Something to "consider along, do later", I guess.
> 
>>
>> The main reason for that would be to allow applications move from
>> their previous own diagnostic to libdiagnostics, if it isn't available on
>> the system they can build and install it as subproject, too; and to be
>> able to build libdiagnostics with a much reduced dependency list.
> 
> I can try to come up with a minimal recipe for building gcc if all you
> want is libdiagnostics

Thanks, that already helps a lot.

Simon

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH] binutils: experimental use of libdiagnostics in gas
  2023-11-07 14:09     ` David Malcolm
@ 2023-11-07 15:57       ` Clément Chigot
  2023-11-07 16:18         ` David Malcolm
  0 siblings, 1 reply; 29+ messages in thread
From: Clément Chigot @ 2023-11-07 15:57 UTC (permalink / raw)
  To: David Malcolm; +Cc: gcc-patches, binutils, Nick Clifton, Simon Sobisch

> > However, I'm not sure how you're planning to make the transition. But
> > currently, it looks like libdiagnostics is either enabled and thus
> > the
> > new format being produced, either it's not and we do have the legacy
> > format. I think the transition should be smoother than that, there
> > are
> > probably thousands of tests, scripts, whatever out in the wild
> > expecting this legacy format. Allowing both formats within the same
> > executable, basically chosen by a flag, would probably ease the
> > transition.
>
> Yes.  I'm assuming that consumers of libdiagnostics would have a
> configure-time test for the availability of libdiagnostics.  In the
> example I gave, it was just a compile-time "choice" (I'm not an expert
> at autotools, so I hacked all of that up for now)... but if the feature
> is available, it could be a run-time choice.
>
> We've been adding new features to GCC's diagnostic output over the
> years (adding column numbers, showing macro expansions, quoting source
> code with underlines, fix-it hints, etc), but each time we've added a
> flag to turn them off (e.g. -fno-diagnostics-show-line-numbers,  -fno-
> diagnostics-show-labels, etc).
>
> As of GCC 11 we have a -fdiagnostics-plain-output which "requests that
> diagnostic output look as plain as possible, which may be useful when
> running dejagnu or other utilities that need to parse diagnostics
> output and prefer that it remain more stable over time."

I guess starting by a configure-time choice before such flags like
-fdiagnostics-plain-output are implemented is enough. I merely wish
that there is a way to preserve the old output, giving people the time
to experiment and then transitioning all their tools.

We can also introduce a flag like "-fdiagnostics-legacy-output`.
Though, I'm fearing it will never be removed, meaning maintaining the
previous code forever... A configure-time choice can be more easily
enabled by default in a few versions and then removed completely after
another bunch of versions.

> In the implementation patch I made the text sink turn on everything by
> default here:
>   m_dc.m_source_printing.enabled = true; // FIXME
>   m_dc.m_source_printing.colorize_source_p = true; // FIXME
>   m_dc.m_source_printing.show_labels_p = true; // FIXME
>   m_dc.m_source_printing.show_line_numbers_p = true; // FIXME
>   m_dc.m_source_printing.min_margin_width = 6; // FIXME
> and I didn't provide a way of turning things off.  So maybe the API
> needs a way to tweak options of the text sink?  Maybe:
>
>   diagnostic_text_sink_set_source_printing (sink, true);
>   diagnostic_text_sink_set_colorize_source (sink, COLORIZE_IF_AT_TTY);
>
> ...etc.
>
> Also, I made no particular effort to make the output similar to before,
> hence e.g. the difference in capitalization "Error: " vs "error: ".  Is
> that capitalization something that you'd want to remain consistent?

If we keep a way to produce the old output, I don't think so. And it
would probably be better to be coherent across gcc, gas, etc.

> >
> > Apart from that, just a few remarks on the implementation details,
> > see below.
>
> [...snip...]
>
> > > @@ -101,6 +109,29 @@ had_warnings (void)
> > >    return warning_count;
> > >  }
> > >
> > > +#if USE_LIBDIAGNOSTICS
> > > +static diagnostic_manager *diag_mgr;
> >
> > Would it make sense for an application to have several
> > "diagnostic_manager" ?
> > If no, I'm wondering if this variable shouldn't
> > be hidden inside libdiagnostics itself, avoiding every calls to have
> > this diag_mgr argument.
>
> Although it might not make sense for binutils-style use-cases to have
> multiple diagnostic_manager instances (since these are implemented all
> standalone programs), I think in general it *does* make sense.
>
> I've found it's usually a bad idea for a shared library to have global
> state, since at some point a consumer of the library is a shared
> library itself, at which point users of the 2nd library see unexpected
> interactions.
>
> Consider the case of a linting tool implemented as a shared library:
> the tool has no knowledge of where it's going to be embedded: e.g. in
> an IDE, or as part of some other system.  Perhaps the IDE is
> multithreaded.  So I think it's better for the user of the diagnostic
> API (here the lint tool) to have an explicit "context" pointer that
> it's sending diagnostics to, rather than having it be implicit inside
> the library.

Thanks for the explanation. I'm fine with that now.

> >
> > > +#endif
> > > +
> > > +void messages_init (void)
> > > +{
> > > +#if USE_LIBDIAGNOSTICS
> > > +  diag_mgr = diagnostic_manager_new ();
> > > +  diagnostic_manager_add_text_sink (diag_mgr, stderr,
> > > +                                   DIAGNOSTIC_COLORIZE_IF_TTY);
> > > +  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
> > > +
> > > DIAGNOSTIC_SARIF_VERSION_2_1_0);
> > > +#endif
> > > +}
> > > +
> > > +void messages_end (void)
> > > +{
> > > +#if USE_LIBDIAGNOSTICS
> > > +  diagnostic_manager_release (diag_mgr);
> >
> > IIUC, diagnostic_manager_release must be called to produce any
> > output.
>
> The text sink emits (and flushes) each diagnostic to the FILE * stream
> after diagnostic_finish is called on it.

Arf, missed that sorry.

> The sarif sink accumulates a JSON representation in memory, and only
> writes to its FILE * after the manager is released (since there are
> aspects of the metadata part of the format that requiring knowing about
> all diagnostics upfront).
>
> > However, nothing prevents the application to exit earlier see
> > "as_fatal". Thus, this probably need to be called using atexit to
> > ensure that whatever happens the messages are being outputted.
>
> atexit handlers are per-process global state, so I'm thinking that
> being something the client would register, rather than libdiagnostics
> doing it automatically.

Yes, we would probably need something like that here. Otherwise, we
would miss some sink outputs if they can be completed only at the end,
making them usable.

> [...snip...]
>
> Thanks for the feedback; hope the above sounds reasonable
> Dave

It does !

Thanks,
Clément

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH] binutils: experimental use of libdiagnostics in gas
  2023-11-07 15:57       ` Clément Chigot
@ 2023-11-07 16:18         ` David Malcolm
  0 siblings, 0 replies; 29+ messages in thread
From: David Malcolm @ 2023-11-07 16:18 UTC (permalink / raw)
  To: Clément Chigot; +Cc: gcc-patches, binutils, Nick Clifton, Simon Sobisch

On Tue, 2023-11-07 at 16:57 +0100, Clément Chigot wrote:
> > > However, I'm not sure how you're planning to make the transition.
> > > But
> > > currently, it looks like libdiagnostics is either enabled and
> > > thus
> > > the
> > > new format being produced, either it's not and we do have the
> > > legacy
> > > format. I think the transition should be smoother than that,
> > > there
> > > are
> > > probably thousands of tests, scripts, whatever out in the wild
> > > expecting this legacy format. Allowing both formats within the
> > > same
> > > executable, basically chosen by a flag, would probably ease the
> > > transition.
> > 
> > Yes.  I'm assuming that consumers of libdiagnostics would have a
> > configure-time test for the availability of libdiagnostics.  In the
> > example I gave, it was just a compile-time "choice" (I'm not an
> > expert
> > at autotools, so I hacked all of that up for now)... but if the
> > feature
> > is available, it could be a run-time choice.
> > 
> > We've been adding new features to GCC's diagnostic output over the
> > years (adding column numbers, showing macro expansions, quoting
> > source
> > code with underlines, fix-it hints, etc), but each time we've added
> > a
> > flag to turn them off (e.g. -fno-diagnostics-show-line-numbers,  -
> > fno-
> > diagnostics-show-labels, etc).
> > 
> > As of GCC 11 we have a -fdiagnostics-plain-output which "requests
> > that
> > diagnostic output look as plain as possible, which may be useful
> > when
> > running dejagnu or other utilities that need to parse diagnostics
> > output and prefer that it remain more stable over time."
> 
> I guess starting by a configure-time choice before such flags like
> -fdiagnostics-plain-output are implemented is enough. I merely wish
> that there is a way to preserve the old output, giving people the
> time
> to experiment and then transitioning all their tools.
> 
> We can also introduce a flag like "-fdiagnostics-legacy-output`.
> Though, I'm fearing it will never be removed, meaning maintaining the
> previous code forever... 

One other consideration here may be bootstrapping a toolchain (e.g.
bringing up a new CPU architecture) and thus minimizing dependencies. 
binutils is such an important component that perhaps you'd want to
retain a minimal implementation of diagnostics that doesn't bring in an
external requirement? especially one based on GCC 14 (which itself
requires GCC 4.8 or later to build), e.g. configuring "--without-
libdiagnostics" or somesuch?

> A configure-time choice can be more easily
> enabled by default in a few versions and then removed completely
> after
> another bunch of versions.
> 
> 

[...snip...]

Dave


^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH 0/6] v2 of libdiagnostics
  2023-11-06 22:29 [PATCH/RFC] libdiagnostics: a shared library for emitting diagnostics David Malcolm
                   ` (2 preceding siblings ...)
  2023-11-06 22:29 ` [PATCH] binutils: experimental use of libdiagnostics in gas David Malcolm
@ 2023-11-21 22:20 ` David Malcolm
  2023-11-21 22:20   ` [PATCH 1/5] libdiagnostics v2: header and examples David Malcolm
                     ` (7 more replies)
  3 siblings, 8 replies; 29+ messages in thread
From: David Malcolm @ 2023-11-21 22:20 UTC (permalink / raw)
  To: gcc-patches, binutils; +Cc: Nick Clifton, Simon Sobisch, David Malcolm

Here's v2 of the "libdiagnostics" shared library idea; see:
  https://gcc.gnu.org/wiki/libdiagnostics

As in v1, patch 1 (for GCC) shows libdiagnostic.h (the public
header file), along with examples of simple self-contained programs that
show various uses of the API.

As in v1, patch 2 (for GCC) is the work-in-progress implementation.

Patch 3 (for GCC) adds a new libdiagnostics++.h, a wrapper API providing
some syntactic sugar when using the API from C++.  I've been using this
to "eat my own dogfood" and write a simple SARIF-dumping tool:
  https://github.com/davidmalcolm/libdiagnostics-sarif-dump

Patch 4 (for GCC) is an internal change needed by patch 1.

Patch 5 (for GCC) updates GCC's source printing code so that when
there's no column information, we don't print annotation lines.  This
fixes the extra lines seen using it from gas discussed in:
https://gcc.gnu.org/pipermail/gcc-patches/2023-November/635575.html

Patch 6 (for binutils) is an updated version of the experiment at using
the API from gas.

Thoughts?

David Malcolm (5):
  libdiagnostics v2: header and examples
  libdiagnostics v2: work-in-progress implementation
  libdiagnostics v2: add C++ wrapper API
  diagnostics: add diagnostic_context::get_location_text
  diagnostics: don't print annotation lines when there's no column info

 gcc/Makefile.in                               |  131 +-
 gcc/configure                                 |    2 +-
 gcc/configure.ac                              |    2 +-
 gcc/diagnostic-show-locus.cc                  |   26 +-
 gcc/diagnostic.cc                             |   35 +-
 gcc/diagnostic.h                              |    2 +
 gcc/libdiagnostics++.h                        |  378 +++++
 gcc/libdiagnostics.cc                         | 1306 +++++++++++++++++
 gcc/libdiagnostics.h                          |  602 ++++++++
 gcc/libdiagnostics.map                        |   63 +
 .../libdiagnostics.dg/libdiagnostics.exp      |  544 +++++++
 gcc/testsuite/libdiagnostics.dg/test-dump.c   |   55 +
 .../libdiagnostics.dg/test-error-with-note.c  |   57 +
 .../libdiagnostics.dg/test-error-with-note.cc |   47 +
 gcc/testsuite/libdiagnostics.dg/test-error.c  |   49 +
 gcc/testsuite/libdiagnostics.dg/test-error.cc |   40 +
 .../libdiagnostics.dg/test-fix-it-hint.c      |   49 +
 .../libdiagnostics.dg/test-fix-it-hint.cc     |   44 +
 .../libdiagnostics.dg/test-helpers++.h        |   28 +
 .../libdiagnostics.dg/test-helpers.h          |   29 +
 .../libdiagnostics.dg/test-labelled-ranges.c  |   52 +
 .../libdiagnostics.dg/test-labelled-ranges.cc |   43 +
 .../libdiagnostics.dg/test-logical-location.c |   60 +
 .../libdiagnostics.dg/test-metadata.c         |   54 +
 .../libdiagnostics.dg/test-multiple-lines.c   |   61 +
 .../libdiagnostics.dg/test-no-column.c        |   41 +
 .../test-note-with-fix-it-hint.c              |   52 +
 .../test-text-sink-options.c                  |   46 +
 .../libdiagnostics.dg/test-warning.c          |   52 +
 .../test-write-sarif-to-file.c                |   46 +
 .../test-write-text-to-file.c                 |   47 +
 31 files changed, 4018 insertions(+), 25 deletions(-)
 create mode 100644 gcc/libdiagnostics++.h
 create mode 100644 gcc/libdiagnostics.cc
 create mode 100644 gcc/libdiagnostics.h
 create mode 100644 gcc/libdiagnostics.map
 create mode 100644 gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-dump.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error-with-note.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error-with-note.cc
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error.cc
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.cc
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-helpers++.h
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-helpers.h
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.cc
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-logical-location.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-metadata.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-multiple-lines.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-no-column.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-text-sink-options.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-warning.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-write-text-to-file.c

-- 
2.26.3


^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH 1/5] libdiagnostics v2: header and examples
  2023-11-21 22:20 ` [PATCH 0/6] v2 of libdiagnostics David Malcolm
@ 2023-11-21 22:20   ` David Malcolm
  2023-11-21 22:20   ` [PATCH 2/5] libdiagnostics v2: work-in-progress implementation David Malcolm
                     ` (6 subsequent siblings)
  7 siblings, 0 replies; 29+ messages in thread
From: David Malcolm @ 2023-11-21 22:20 UTC (permalink / raw)
  To: gcc-patches, binutils; +Cc: Nick Clifton, Simon Sobisch, David Malcolm

Changed in v2:
* Changed from diagnostic_location_t -> const diagnostic_physical_location *
* Add entrypoint: diagnostic_finish_va
* add new type diagnostic_text_sink, and new entrypoints for
  enabling/disabling options on it
* add new debugging entrypoints for dumping objects to a FILE *
* new test cases

Blurb from v1:

Here's a work-in-progress patch for GCC that adds a libdiagnostics.h
header describing the public interface, along with various testcases
that show usage examples for the API.  Various aspects of this need
work; posting now for early feedback on overall direction.

How does the interface look?

gcc/ChangeLog:
	* libdiagnostics.h: New file.

gcc/testsuite/ChangeLog:
	* libdiagnostics.dg/test-error-with-note.c: New test.
	* libdiagnostics.dg/test-error.c: New test.
	* libdiagnostics.dg/test-fix-it-hint.c: New test.
	* libdiagnostics.dg/test-helpers.h: New.
	* libdiagnostics.dg/test-labelled-ranges.c: New test.
	* libdiagnostics.dg/test-logical-location.c: New test.
	* libdiagnostics.dg/test-metadata.c: New test.
	* libdiagnostics.dg/test-multiple-lines.c: New test.
	* libdiagnostics.dg/test-note-with-fix-it-hint.c: New test.
	* libdiagnostics.dg/test-warning.c: New test.
	* libdiagnostics.dg/test-write-sarif-to-file.c: New test.
	* libdiagnostics.dg/test-write-text-to-file.c: New test.
---
 gcc/libdiagnostics.h                          | 602 ++++++++++++++++++
 gcc/testsuite/libdiagnostics.dg/test-dump.c   |  55 ++
 .../libdiagnostics.dg/test-error-with-note.c  |  57 ++
 gcc/testsuite/libdiagnostics.dg/test-error.c  |  49 ++
 .../libdiagnostics.dg/test-fix-it-hint.c      |  49 ++
 .../libdiagnostics.dg/test-helpers.h          |  29 +
 .../libdiagnostics.dg/test-labelled-ranges.c  |  52 ++
 .../libdiagnostics.dg/test-logical-location.c |  60 ++
 .../libdiagnostics.dg/test-metadata.c         |  54 ++
 .../libdiagnostics.dg/test-multiple-lines.c   |  61 ++
 .../libdiagnostics.dg/test-no-column.c        |  41 ++
 .../test-note-with-fix-it-hint.c              |  52 ++
 .../test-text-sink-options.c                  |  46 ++
 .../libdiagnostics.dg/test-warning.c          |  52 ++
 .../test-write-sarif-to-file.c                |  46 ++
 .../test-write-text-to-file.c                 |  47 ++
 16 files changed, 1352 insertions(+)
 create mode 100644 gcc/libdiagnostics.h
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-dump.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error-with-note.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-helpers.h
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-logical-location.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-metadata.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-multiple-lines.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-no-column.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-text-sink-options.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-warning.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file.c
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-write-text-to-file.c

diff --git a/gcc/libdiagnostics.h b/gcc/libdiagnostics.h
new file mode 100644
index 000000000000..5c4e203b6a0a
--- /dev/null
+++ b/gcc/libdiagnostics.h
@@ -0,0 +1,602 @@
+/* A pure C API for emitting diagnostics.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+
+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 LIBDIAGNOSTICS_H
+#define LIBDIAGNOSTICS_H
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**********************************************************************
+ Macros for attributes.
+ These are all currently empty, and thus for the human reader rather than
+ the compiler.
+ **********************************************************************/
+
+#define LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL(ARG_NUM)
+
+#define LIBDIAGNOSTICS_PARAM_CAN_BE_NULL(ARG_NUM)
+
+#define LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING(FMT_ARG_NUM, ARGS_ARG_NUM)
+
+
+/**********************************************************************
+ Data structures and types.
+ All structs within the API are opaque.
+ **********************************************************************/
+
+/* An opaque bundle of state for a client of the library.
+   Has zero of more "sinks" to which diagnostics are emitted.
+   Responsibilities:
+   - location-management
+   - caching of source file content
+   - patch generation.  */
+typedef struct diagnostic_manager diagnostic_manager;
+
+/* Types relating to diagnostic output sinks.  */
+
+typedef struct diagnostic_text_sink diagnostic_text_sink;
+
+/* An enum for determining if we should colorize a text output sink.  */
+enum diagnostic_colorize
+{
+  DIAGNOSTIC_COLORIZE_IF_TTY,
+  DIAGNOSTIC_COLORIZE_NO,
+  DIAGNOSTIC_COLORIZE_YES
+};
+
+/* An enum for choosing the SARIF version for a SARIF output sink.
+   Eventually the SARIF output may support multiple SARIF versions.  */
+
+enum diagnostic_sarif_version
+{
+  DIAGNOSTIC_SARIF_VERSION_2_1_0
+};
+
+/* Types relating to "physical" source locations i.e. locations within
+   specific files expressed via line/column.  */
+
+/* Opaque type describing a particular input file.  */
+typedef struct diagnostic_file diagnostic_file;
+
+/* Opaque type representing a key into a database of source locations within
+   a diagnostic_manager.  Locations are created by various API calls into
+   the diagnostic_manager expressing source code points and ranges.  They
+   persist until the diagnostic_manager is released, which cleans them
+   up.
+
+   NULL means "UNKNOWN", and can be returned by the manager as a
+   fallback when a problem occurs (e.g. too many locations).
+
+   A diagnostic_location can be a single point within the source code,
+   such as here (at the the '"' at the start of the string literal):
+
+     int i = "foo";
+             ^
+
+   or be a range with a start and finish, and a "caret" location.
+
+      a = (foo && bar)
+          ~~~~~^~~~~~~
+   where the caret here is at the first "&", and the start and finish
+   are at the parentheses.  */
+
+typedef struct diagnostic_physical_location diagnostic_physical_location;
+
+/* Types for storing line and column information in text files.
+
+   Both libdiagnostics and emacs number source *lines* starting at 1, but
+   they have differing conventions for *columns*.
+
+   libdiagnostics uses a 1-based convention for source columns,
+   whereas Emacs's M-x column-number-mode uses a 0-based convention.
+
+   For example, an error in the initial, left-hand
+   column of source line 3 is reported by libdiagnostics as:
+
+      some-file.c:3:1: error: ...etc...
+
+   On navigating to the location of that error in Emacs
+   (e.g. via "next-error"),
+   the locus is reported in the Mode Line
+   (assuming M-x column-number-mode) as:
+
+     some-file.c   10%   (3, 0)
+
+   i.e. "3:1:" in libdiagnostics corresponds to "(3, 0)" in Emacs.  */
+
+typedef unsigned int diagnostic_line_num_t;
+typedef unsigned int diagnostic_column_num_t;
+
+/* An opaque type describing a "logical" source location
+   e.g. "within function 'foo'".  */
+
+typedef struct diagnostic_logical_location diagnostic_logical_location;
+
+/* An enum for discriminating between different kinds of logical location
+   for a diagnostic.
+
+   Roughly corresponds to logicalLocation's "kind" property in SARIF v2.1.0
+   (section 3.33.7).  */
+
+enum diagnostic_logical_location_kind_t
+{
+  DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION,
+  DIAGNOSTIC_LOGICAL_LOCATION_KIND_MEMBER,
+  DIAGNOSTIC_LOGICAL_LOCATION_KIND_MODULE,
+  DIAGNOSTIC_LOGICAL_LOCATION_KIND_NAMESPACE,
+  DIAGNOSTIC_LOGICAL_LOCATION_KIND_TYPE,
+  DIAGNOSTIC_LOGICAL_LOCATION_KIND_RETURN_TYPE,
+  DIAGNOSTIC_LOGICAL_LOCATION_KIND_PARAMETER,
+  DIAGNOSTIC_LOGICAL_LOCATION_KIND_VARIABLE
+};
+
+/* A "diagnostic" is an opaque bundle of state for a particular
+   diagnostic that is being constructed in memory.
+
+   A diagnostic has a primary location and zero or more secondary
+   locations.  For example:
+
+      a = (foo && bar)
+          ~~~~~^~~~~~~
+
+   This diagnostic has a single diagnostic_location, with the caret
+   at the first "&", and the start/finish at the parentheses.
+
+   Contrast with:
+
+      a = (foo && bar)
+           ~~~ ^~ ~~~
+
+   This diagnostic has three locations
+   - The primary location (at "&&") has its caret and start location at
+   the first "&" and end at the second "&.
+   - The secondary location for "foo" has its start and finish at the "f"
+   and "o" of "foo"; the caret is not flagged for display, but is perhaps at
+   the "f" of "foo".
+   - Similarly, the other secondary location (for "bar") has its start and
+   finish at the "b" and "r" of "bar"; the caret is not flagged for
+   display, but is perhaps at the"b" of "bar".  */
+typedef struct diagnostic diagnostic;
+
+enum diagnostic_level
+{
+  DIAGNOSTIC_LEVEL_ERROR,
+  DIAGNOSTIC_LEVEL_WARNING,
+  DIAGNOSTIC_LEVEL_NOTE
+};
+
+/**********************************************************************
+ API entrypoints.
+ **********************************************************************/
+
+/* Create a new diagnostic_manager.
+   The client needs to call diagnostic_release_manager on it at some
+   point.
+   Note that no output sinks are created by default.  */
+
+extern diagnostic_manager *
+diagnostic_manager_new (void);
+
+/* Release a diagnostic_manager.
+   This will flush output to all of the output sinks, and clean up. */
+
+extern void
+diagnostic_manager_release (diagnostic_manager *)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Optional metadata about the manager.  */
+
+/* Set a string suitable for use as the value of the SARIF "name" property
+   (SARIF v2.1.0 section 3.19.8).  */
+
+extern void
+diagnostic_manager_set_tool_name (diagnostic_manager *diag_mgr,
+				  const char *value)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Set a string suitable for use as the value of the SARIF "fullName" property
+   (SARIF v2.1.0 section 3.19.9).  */
+
+extern void
+diagnostic_manager_set_full_name (diagnostic_manager *diag_mgr,
+				  const char *value)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Set a string suitable for use as the value of the SARIF "version" property
+   (SARIF v2.1.0 section 3.19.13).  */
+
+extern void
+diagnostic_manager_set_version_string (diagnostic_manager *diag_mgr,
+				       const char *value)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Set a string suitable for use as the value of the SARIF "informationUri"
+   property (SARIF v2.1.0 section 3.19.17).  */
+
+extern void
+diagnostic_manager_set_version_url (diagnostic_manager *diag_mgr,
+				    const char *value)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Destinations for diagnostics.  */
+
+/* Add a new output sink to DIAG_MGR, which writes GCC-style diagnostics
+   to DST_STREAM.
+   Return a borrowed pointer to the sink, which is cleaned up when DIAG_MGR
+   is released.
+   The output for each diagnostic is written and flushed as each
+   diagnostic is finished.  */
+
+extern diagnostic_text_sink *
+diagnostic_manager_add_text_sink (diagnostic_manager *diag_mgr,
+				  FILE *dst_stream,
+				  enum diagnostic_colorize colorize)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Functions to manipulate text sinks.  */
+
+/* Enable/disable printing of source text in the text sink.
+   Default: enabled.  */
+
+extern void
+diagnostic_text_sink_set_source_printing_enabled (diagnostic_text_sink *text_sink,
+						  int value)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Enable/disable colorization of the characters of source text
+   that are underlined.
+   This should be true for clients that generate range information
+   (so that the ranges of code are colorized),
+   and false for clients that merely specify points within the
+   source code (to avoid e.g. colorizing just the first character in
+   a token, which would look strange).
+   Default: enabled.  */
+
+extern void
+diagnostic_text_sink_set_labelled_source_colorization_enabled (diagnostic_text_sink *text_sink,
+							       int value)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Add a new output sink to DIAG_MGR, which writes SARIF of the given
+   version to DST_STREAM.
+   The output is not written until DIAG_MGR is released.  */
+
+extern void
+diagnostic_manager_add_sarif_sink (diagnostic_manager *diag_mgr,
+				   FILE *dst_stream,
+				   enum diagnostic_sarif_version version)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Write a patch to DST_STREAM consisting of all fix-it hints
+   on all diagnostics that have been finished on DIAG_MGR.  */
+
+extern void
+diagnostic_manager_write_patch (diagnostic_manager *diag_mgr,
+				FILE *dst_stream)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Location management.  */
+
+/* Create a new diagnostic_file * for file NAME.
+
+   Repeated calls with matching NAMEs will return the
+   same object.
+
+   If SARIF_SOURCE_LANGUAGE is non-NULL, it specifies a "sourceLanguage"
+   value for the file when use when writing SARIF.
+   See SARIF v2.1.0 Appendix J for suggested values for various
+   programmming languages.  */
+
+extern const diagnostic_file *
+diagnostic_manager_new_file (diagnostic_manager *diag_mgr,
+			     const char *name,
+			     const char *sarif_source_language)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3);
+
+// TODO
+extern void
+diagnostic_manager_debug_dump_file (diagnostic_manager *diag_mgr,
+				    const diagnostic_file *file,
+				    FILE *out)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+/* Attempt to create a diagnostic_location representing
+   FILENAME:LINE_NUM, with no column information
+   (thus "the whole line").  */
+
+extern const diagnostic_physical_location *
+diagnostic_manager_new_location_from_file_and_line (diagnostic_manager *diag_mgr,
+						    const diagnostic_file *file,
+						    diagnostic_line_num_t line_num)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Attempt to create a diagnostic_physical_location representing
+   FILENAME:LINE_NUM:COLUMN_NUM.  */
+
+extern const diagnostic_physical_location *
+diagnostic_manager_new_location_from_file_line_column (diagnostic_manager *diag_mgr,
+						       const diagnostic_file *file,
+						       diagnostic_line_num_t line_num,
+						       diagnostic_column_num_t column_num)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2);
+
+/* Attempt to create a diagnostic_physical_location representing a 
+   range within a source file, with a highlighted "caret" location.
+
+   All must be within the same file, but they can be on different lines.
+
+   For example, consider the location of the binary expression below:
+
+     ...|__________1111111112222222
+     ...|12345678901234567890123456
+     ...|
+     521|int sum (int foo, int bar)
+     522|{
+     523|   return foo + bar;
+     ...|          ~~~~^~~~~
+     524|}
+
+   The location's caret is at the "+", line 523 column 15, but starts
+   earlier, at the "f" of "foo" at column 11.  The finish is at the "r"
+   of "bar" at column 19.  */
+
+extern const diagnostic_physical_location *
+diagnostic_manager_new_location_from_range (diagnostic_manager *diag_mgr,
+					    const diagnostic_physical_location *loc_caret,
+					    const diagnostic_physical_location *loc_start,
+					    const diagnostic_physical_location *loc_end)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (4);
+
+// TODO
+extern void
+diagnostic_manager_debug_dump_location (const diagnostic_manager *diag_mgr,
+					const diagnostic_physical_location *loc,
+					FILE *out)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+/* A bundle of state describing a logical location in the user's source,
+   such as "in function 'foo'".
+
+   SHORT_NAME can be NULL, or else a string suitable for use by
+   the SARIF logicalLocation "name" property (SARIF v2.1.0 section 3.33.4).
+
+   FULLY_QUALIFIED_NAME can be NULL or else a string  suitable for use by
+   the SARIF logicalLocation "fullyQualifiedName" property
+   (SARIF v2.1.0 section 3.33.5).
+
+   DECORATED_NAME can be NULL or else a string  suitable for use by
+   the SARIF logicalLocation "decoratedName" property
+   (SARIF v2.1.0 section 3.33.6).  */
+
+extern const diagnostic_logical_location *
+diagnostic_manager_new_logical_location (diagnostic_manager *diag_mgr,
+					 enum diagnostic_logical_location_kind_t kind,
+					 const diagnostic_logical_location *parent,
+					 const char *short_name,
+					 const char *fully_qualified_name,
+					 const char *decorated_name)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (4)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (5)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (6);
+
+// TODO
+extern void
+diagnostic_manager_debug_dump_logical_location (const diagnostic_manager *diag_mgr,
+						const diagnostic_logical_location *loc,
+						FILE *out)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+/* Diagnostic groups.  */
+
+// FIXME: docs
+
+extern void
+diagnostic_manager_begin_group (diagnostic_manager *diag_mgr)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+extern void
+diagnostic_manager_end_group (diagnostic_manager *diag_mgr)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Step-by-step creation of a diagnostic.  */
+
+extern diagnostic *
+diagnostic_begin (diagnostic_manager *diag_mgr,
+		  enum diagnostic_level level)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+#if 0
+// FIXME: TODO
+extern void
+diagnostic_set_option (diagnostic *diag,
+		       const char *option,
+		       const char *url);
+#endif
+
+/* Associate this diagnostic with the given ID within
+   the Common Weakness Enumeration.  */
+
+extern void
+diagnostic_set_cwe (diagnostic *diag,
+		    unsigned cwe_id)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Associate this diagnostic with a particular rule that has been violated
+   (such as in a coding standard, or within a specification).
+   The rule must have at least one of a title and a URL, but these
+   can be NULL.
+   A diagnostic can be associated with zero or more rules.  */
+
+extern void
+diagnostic_add_rule (diagnostic *diag,
+		     const char *title,
+		     const char *url)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3);
+
+/* Set the primary location of DIAG.  */
+
+extern void
+diagnostic_set_location (diagnostic *diag,
+			 const diagnostic_physical_location * loc)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2);
+
+/* Set the primary location of DIAG, with a label.  */
+
+extern void
+diagnostic_set_location_with_label (diagnostic *diag,
+				    const diagnostic_physical_location *loc,
+				    const char *fmt, ...)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+/* Add a secondary location to DIAG.  */
+
+extern void
+diagnostic_add_location (diagnostic *diag,
+			 const diagnostic_physical_location * loc)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1);
+
+/* Add a secondary location to DIAG, with a label.  */
+
+extern void
+diagnostic_add_location_with_label (diagnostic *diag,
+				    const diagnostic_physical_location *loc,
+				    const char *text)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+/* Set the logical location of DIAG.  */
+
+extern void
+diagnostic_set_logical_location (diagnostic *diag,
+				 const diagnostic_logical_location *logical_loc)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2);
+
+/* Fix-it hints.  */
+
+extern void
+diagnostic_add_fix_it_hint_insert_before (diagnostic *diag,
+					  const diagnostic_physical_location *loc,
+					  const char *addition)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+extern void
+diagnostic_add_fix_it_hint_insert_after (diagnostic *diag,
+					 const diagnostic_physical_location *loc,
+					 const char *addition)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+extern void
+diagnostic_add_fix_it_hint_replace (diagnostic *diag,
+				    const diagnostic_physical_location *loc,
+				    const char *replacement)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (3);
+
+extern void
+diagnostic_add_fix_it_hint_delete (diagnostic *diag,
+				   const diagnostic_physical_location *loc)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (2);
+
+/* Emit DIAG to all sinks of its manager, and release DIAG.
+   Use FMT for the message.
+   TODO: this uses gcc's pretty-print format, which is *not* printf.
+   TODO: who is responsible for putting FMT through gettext?  */
+
+extern void
+diagnostic_finish (diagnostic *diag, const char *fmt, ...)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2)
+  LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (2, 3);
+
+// FIXME
+
+extern void
+diagnostic_finish_va (diagnostic *diag, const char *fmt, va_list *args)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (1)
+  LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2)
+  LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (2, 0);
+
+// FIXME: we might want one that handles plurals
+#if 0
+extern void
+diagnostic_finish_n (diagnostic_manager *diag_mgr,
+		     int n,
+		     const char *singular_fmt,
+		     const char *plural_fmt,
+		     ...);
+#endif
+
+/* TODO:
+
+   DEFERRED:
+   - thread-safety
+   - plural forms
+   - enum about what a "column number" means (bytes, unichars, etc)
+   - locations within binary files
+   - options and URLs for warnings
+   - enable/disable of warnings by kind
+   - execution paths associated with/triggering a problem
+   - plugin metadata.  */
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif  /* LIBDIAGNOSTICS_H  */
diff --git a/gcc/testsuite/libdiagnostics.dg/test-dump.c b/gcc/testsuite/libdiagnostics.dg/test-dump.c
new file mode 100644
index 000000000000..c9a7d61315cb
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-dump.c
@@ -0,0 +1,55 @@
+/* Usage example of dump API.  */
+
+#include "libdiagnostics.h"
+
+const int line_num = 42;
+
+int
+main ()
+{
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, "foo.c", "c");
+
+  fprintf (stderr, "file: ");
+  diagnostic_manager_debug_dump_file (diag_mgr, file, stderr);
+  fprintf (stderr, "\n");
+  
+  const diagnostic_physical_location *loc_start
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8);
+  const diagnostic_physical_location *loc_end
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19);
+  const diagnostic_physical_location *loc_range
+    = diagnostic_manager_new_location_from_range (diag_mgr,
+					  loc_start,
+					  loc_start,
+					  loc_end);
+
+  fprintf (stderr, "loc_start: ");
+  diagnostic_manager_debug_dump_location (diag_mgr, loc_start, stderr);
+  fprintf (stderr, "\n");
+
+  fprintf (stderr, "loc_end: ");
+  diagnostic_manager_debug_dump_location (diag_mgr, loc_end, stderr);
+  fprintf (stderr, "\n");
+
+  fprintf (stderr, "loc_range: ");
+  diagnostic_manager_debug_dump_location (diag_mgr, loc_range, stderr);
+  fprintf (stderr, "\n");
+  
+
+  const diagnostic_logical_location *logical_loc
+    = diagnostic_manager_new_logical_location (diag_mgr,
+					       DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION,
+					       NULL, /* parent */
+					       "test_short_name",
+					       "test_qualified_name",
+					       "test_decorated_name");
+
+  fprintf (stderr, "logical_loc: ");
+  diagnostic_manager_debug_dump_logical_location (diag_mgr, logical_loc, stderr);
+  fprintf (stderr, "\n");
+  
+  diagnostic_manager_release (diag_mgr);
+  return 0;
+};
diff --git a/gcc/testsuite/libdiagnostics.dg/test-error-with-note.c b/gcc/testsuite/libdiagnostics.dg/test-error-with-note.c
new file mode 100644
index 000000000000..0f4ee70232ca
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-error-with-note.c
@@ -0,0 +1,57 @@
+/* Example of emitting an error with an associated note.
+
+   Intended output is similar to:
+
+PATH/test-error-with-note.c:6: error: can't find 'foo'
+    6 | PRINT "hello world!";
+      |        ^~~~~~~~~~~~
+PATH/test-error-with-note.c:6: note: have you looked behind the couch?
+
+   along with the equivalent in SARIF.  */
+
+#include "libdiagnostics.h"
+
+/*
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_add_text_sink (diag_mgr, stderr,
+				    DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
+				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  const diagnostic_physical_location *loc_start
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8);
+  const diagnostic_physical_location *loc_end
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19);
+  const diagnostic_physical_location *loc_range
+    = diagnostic_manager_new_location_from_range (diag_mgr,
+						  loc_start,
+						  loc_start,
+						  loc_end);
+
+  diagnostic_manager_begin_group (diag_mgr);
+  
+  diagnostic *err = diagnostic_begin (diag_mgr,
+				      DIAGNOSTIC_LEVEL_ERROR);
+  diagnostic_set_location (err, loc_range);
+  diagnostic_finish (err, "can't find %qs", "foo");
+
+  diagnostic *note = diagnostic_begin (diag_mgr, DIAGNOSTIC_LEVEL_NOTE);
+  diagnostic_set_location (note, loc_range);
+  diagnostic_finish (note, "have you looked behind the couch?");
+
+  diagnostic_manager_end_group (diag_mgr);
+
+  diagnostic_manager_release (diag_mgr);
+  return 0;
+};
diff --git a/gcc/testsuite/libdiagnostics.dg/test-error.c b/gcc/testsuite/libdiagnostics.dg/test-error.c
new file mode 100644
index 000000000000..1b99c4e5299f
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-error.c
@@ -0,0 +1,49 @@
+/* Example of emitting an error.
+
+   Intended output is similar to:
+
+PATH/test-error-with-note.c:6: error: can't find 'foo'
+    6 | PRINT "hello world!";
+      |        ^~~~~~~~~~~~
+
+   along with the equivalent in SARIF.  */
+
+#include "libdiagnostics.h"
+
+/*
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_add_text_sink (diag_mgr, stderr,
+				    DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
+				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  const diagnostic_physical_location *loc_start
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8);
+  const diagnostic_physical_location *loc_end
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19);
+  const diagnostic_physical_location *loc_range
+    = diagnostic_manager_new_location_from_range (diag_mgr,
+						  loc_start,
+						  loc_start,
+						  loc_end);
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_ERROR);
+  diagnostic_set_location (d, loc_range);
+  
+  diagnostic_finish (d, "can't find %qs", "foo");
+
+  diagnostic_manager_release (diag_mgr);
+  return 0;
+};
diff --git a/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.c b/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.c
new file mode 100644
index 000000000000..d3f7e82d1173
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.c
@@ -0,0 +1,49 @@
+/* Example of a fix-it hint, including patch generation.
+
+   Intended output is similar to:
+
+PATH/test-fix-it-hint.c:19: error: unknown field 'colour'; did you mean 'color'
+   19 |   return p->colour;
+      |             ^~~~~~
+      |             color
+
+   along with the equivalent in SARIF, and a generated patch (on stderr) to
+   make the change.  */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+/*
+_________11111111112
+12345678901234567890
+  return p->colour;
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_add_text_sink (diag_mgr, stderr,
+				    DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
+				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  const diagnostic_physical_location *loc_token
+    = make_range (diag_mgr, file, line_num, 13, 18);
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_ERROR);
+  diagnostic_set_location (d, loc_token);
+
+  diagnostic_add_fix_it_hint_replace (d, loc_token, "color");
+  
+  diagnostic_finish (d, "unknown field %qs; did you mean %qs", "colour", "color");
+
+  diagnostic_manager_write_patch (diag_mgr, stderr);
+
+  diagnostic_manager_release (diag_mgr);
+  return 0;
+}
diff --git a/gcc/testsuite/libdiagnostics.dg/test-helpers.h b/gcc/testsuite/libdiagnostics.dg/test-helpers.h
new file mode 100644
index 000000000000..ad22284fb495
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-helpers.h
@@ -0,0 +1,29 @@
+/* Common utility code shared between test cases.  */
+
+#ifndef TEST_HELPERS_H
+#define TEST_HELPERS_H
+
+const diagnostic_physical_location *
+make_range (diagnostic_manager *diag_mgr,
+	    const diagnostic_file *file,
+	    diagnostic_line_num_t line_num,
+	    diagnostic_column_num_t start_column,
+	    diagnostic_column_num_t end_column)
+{
+  const diagnostic_physical_location *loc_start
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr,
+							     file,
+							     line_num,
+							     start_column);
+  const diagnostic_physical_location *loc_end
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr,
+							     file,
+							     line_num,
+							     end_column);
+  return diagnostic_manager_new_location_from_range (diag_mgr,
+						     loc_start,
+						     loc_start,
+						     loc_end);
+}
+
+#endif /* #ifndef TEST_HELPERS_H */
diff --git a/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.c b/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.c
new file mode 100644
index 000000000000..b5723810b1fc
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.c
@@ -0,0 +1,52 @@
+/* Example of multiple locations, with labelling of ranges.
+
+   Intended output is similar to:
+
+PATH/test-labelled-ranges.c:9: error: mismatching types: 'int' and 'const char *'
+   19 |   42 + "foo"
+      |   ~~ ^ ~~~~~
+      |   |    |
+      |   int  const char *
+
+   along with the equivalent in SARIF.  */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+/*
+_________11111111112
+12345678901234567890
+  42 + "foo"
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_add_text_sink (diag_mgr, stderr,
+				    DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
+				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  const diagnostic_physical_location *loc_operator
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 6);
+
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_ERROR);
+  diagnostic_set_location (d, loc_operator);
+  diagnostic_add_location_with_label (d,
+				      make_range (diag_mgr, file, line_num, 3, 4),
+				      "int");
+  diagnostic_add_location_with_label (d,
+				      make_range (diag_mgr, file, line_num, 8, 12),
+				      "const char *");
+  
+  diagnostic_finish (d, "mismatching types: %qs and %qs", "int", "const char *");
+  
+  diagnostic_manager_release (diag_mgr);
+  return 0;
+}
diff --git a/gcc/testsuite/libdiagnostics.dg/test-logical-location.c b/gcc/testsuite/libdiagnostics.dg/test-logical-location.c
new file mode 100644
index 000000000000..c94f54eae686
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-logical-location.c
@@ -0,0 +1,60 @@
+/* Example of using a logical location.
+
+   Intended output is similar to:
+
+In function 'test_qualified_name':
+PATH/test-error-with-note.c:6: error: can't find 'foo'
+    6 | PRINT "hello world!";
+      |        ^~~~~~~~~~~~
+
+   along with the equivalent in SARIF.  */
+
+#include "libdiagnostics.h"
+
+/* Placeholder source:
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_add_text_sink (diag_mgr, stderr,
+				    DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
+				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  const diagnostic_physical_location *loc_start
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8);
+  const diagnostic_physical_location *loc_end
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19);
+  const diagnostic_physical_location *loc_range
+    = diagnostic_manager_new_location_from_range (diag_mgr,
+					  loc_start,
+					  loc_start,
+					  loc_end);
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_ERROR);
+  diagnostic_set_location (d, loc_range);
+
+  const diagnostic_logical_location *logical_loc
+    = diagnostic_manager_new_logical_location (diag_mgr,
+					       DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION,
+					       NULL, /* parent */
+					       "test_short_name",
+					       "test_qualified_name",
+					       "test_decorated_name");
+  
+  diagnostic_set_logical_location (d, logical_loc);
+  
+  diagnostic_finish (d, "can't find %qs", "foo");
+  
+  diagnostic_manager_release (diag_mgr);
+  return 0;
+};
diff --git a/gcc/testsuite/libdiagnostics.dg/test-metadata.c b/gcc/testsuite/libdiagnostics.dg/test-metadata.c
new file mode 100644
index 000000000000..119e2302937c
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-metadata.c
@@ -0,0 +1,54 @@
+/* Example of setting a CWE and adding extra metadata.
+
+   Intended output is similar to:
+
+PATH/test-metadata.c:21: warning: never use 'gets' [CWE-242] [STR34-C]
+   21 |   gets (buf);
+      |   ^~~~~~~~~~
+
+   where the metadata tags are linkified in a sufficiently capable terminal,
+   along with the equivalent in SARIF.  */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+/* Placeholder source:
+_________11111111112
+12345678901234567890
+void test_cwe (void)
+{
+  char buf[1024];
+  gets (buf);
+}
+*/
+const int line_num = __LINE__ - 3;
+
+int
+main ()
+{
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_set_tool_name (diag_mgr, "FooChecker");
+  diagnostic_manager_set_full_name (diag_mgr, "FooChecker 0.1 (en_US)");
+  diagnostic_manager_set_version_string (diag_mgr, "0.1");
+  diagnostic_manager_set_version_url (diag_mgr, "https://www.example.com/0.1/");
+
+  diagnostic_manager_add_text_sink (diag_mgr, stderr,
+				    DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
+				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  const diagnostic_physical_location *loc_token
+    = make_range (diag_mgr, file, line_num, 3, 12);
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_WARNING);
+  diagnostic_set_location (d, loc_token);
+  diagnostic_set_cwe (d, 242); /* CWE-242: Use of Inherently Dangerous Function.  */
+  diagnostic_add_rule (d, "STR34-C", "https://example.com/");
+  
+  diagnostic_finish (d, "never use %qs", "gets");
+
+  diagnostic_manager_release (diag_mgr);
+  return 0;
+}
diff --git a/gcc/testsuite/libdiagnostics.dg/test-multiple-lines.c b/gcc/testsuite/libdiagnostics.dg/test-multiple-lines.c
new file mode 100644
index 000000000000..37399c0e9466
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-multiple-lines.c
@@ -0,0 +1,61 @@
+/* Example of a warning with multiple locations in various source lines,
+   with an insertion fix-it hint.
+
+   Intended output is similar to:
+   
+/PATH/test-multiple-lines.c:17: warning: missing comma
+   16 | const char *strs[3] = {"foo",
+      |                        ~~~~~ 
+   17 |                        "bar"
+      |                        ~~~~~^
+   18 |                        "baz"};
+      |                        ~~~~~ 
+
+   along with the equivalent in SARIF.  */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+/* Placeholder source (missing comma after "bar"):
+_________11111111112222222222
+12345678901234567890123456789
+const char *strs[3] = {"foo",
+                       "bar"
+                       "baz"};
+*/
+const int foo_line_num = __LINE__ - 4;
+
+int
+main ()
+{
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_add_text_sink (diag_mgr, stderr,
+				    DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
+				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  const diagnostic_physical_location *loc_comma
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, foo_line_num + 1, 29);
+  const diagnostic_physical_location *loc_foo
+    = make_range (diag_mgr, file, foo_line_num, 24, 28);
+  const diagnostic_physical_location *loc_bar
+    = make_range (diag_mgr, file, foo_line_num + 1, 24, 28);
+  const diagnostic_physical_location *loc_baz
+    = make_range (diag_mgr, file, foo_line_num + 2, 24, 28);
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_WARNING);
+  diagnostic_set_location (d, loc_comma);
+  diagnostic_add_location (d, loc_foo);
+  diagnostic_add_location (d, loc_bar);
+  diagnostic_add_location (d, loc_baz);
+
+  diagnostic_add_fix_it_hint_insert_after (d, loc_bar, ",");
+  
+  diagnostic_finish (d, "missing comma");
+  
+  diagnostic_manager_release (diag_mgr);
+  return 0;
+};
diff --git a/gcc/testsuite/libdiagnostics.dg/test-no-column.c b/gcc/testsuite/libdiagnostics.dg/test-no-column.c
new file mode 100644
index 000000000000..e8fd63d90518
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-no-column.c
@@ -0,0 +1,41 @@
+/* Example of emitting an error without a column number.
+
+   Intended output is similar to:
+
+PATH/test-error-with-note.c:6: error: can't find 'foo'
+    6 | PRINT "hello world!";
+
+   along with the equivalent in SARIF.  */
+
+#include "libdiagnostics.h"
+
+/*
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_add_text_sink (diag_mgr, stderr,
+				    DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
+				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  const diagnostic_physical_location *loc
+    = diagnostic_manager_new_location_from_file_and_line (diag_mgr, file, line_num);
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_ERROR);
+  diagnostic_set_location (d, loc);
+  
+  diagnostic_finish (d, "can't find %qs", "foo");
+
+  diagnostic_manager_release (diag_mgr);
+  return 0;
+};
diff --git a/gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint.c b/gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint.c
new file mode 100644
index 000000000000..7184552ad099
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint.c
@@ -0,0 +1,52 @@
+/* Example of a grouped error and note, with a fix-it hint on the note.
+
+   Intended output is similar to:
+   
+/PATH/test-note-with-fix-it-hint.c:19: error: unknown field 'colour'
+   19 |   return p->colour;
+      |             ^~~~~~
+/PATH/test-note-with-fix-it-hint.c:19: note: did you mean 'color'
+   19 |   return p->colour;
+      |             ^~~~~~
+      |             color
+
+   along with the equivalent in SARIF.  */
+
+#include "libdiagnostics.h"
+#include "test-helpers.h"
+
+/* Placeholder source:
+_________11111111112
+12345678901234567890
+  return p->colour;
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_add_text_sink (diag_mgr, stderr, DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_manager_add_sarif_sink (diag_mgr, stderr, DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  const diagnostic_physical_location *loc_token
+    = make_range (diag_mgr, file, line_num, 13, 18);
+
+  diagnostic_manager_begin_group (diag_mgr);
+
+  diagnostic *err = diagnostic_begin (diag_mgr, DIAGNOSTIC_LEVEL_ERROR);
+  diagnostic_set_location (err, loc_token);
+  diagnostic_finish (err, "unknown field %qs", "colour");
+
+  diagnostic *n = diagnostic_begin (diag_mgr, DIAGNOSTIC_LEVEL_NOTE);
+  diagnostic_set_location (n, loc_token);
+  diagnostic_add_fix_it_hint_replace (n, loc_token, "color");
+  diagnostic_finish (n, "did you mean %qs", "color");
+
+  diagnostic_manager_end_group (diag_mgr);
+
+  diagnostic_manager_release (diag_mgr);
+  return 0;
+}
diff --git a/gcc/testsuite/libdiagnostics.dg/test-text-sink-options.c b/gcc/testsuite/libdiagnostics.dg/test-text-sink-options.c
new file mode 100644
index 000000000000..2c4dc8aeac01
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-text-sink-options.c
@@ -0,0 +1,46 @@
+/* Example of controlling options for text sinks.  */
+
+#include "libdiagnostics.h"
+
+/*
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_text_sink *sink_1
+    = diagnostic_manager_add_text_sink (diag_mgr, stderr,
+					DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_text_sink_set_source_printing_enabled (sink_1, 0);
+
+  diagnostic_text_sink *sink_2
+    = diagnostic_manager_add_text_sink (diag_mgr, stderr,
+					DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_text_sink_set_labelled_source_colorization_enabled (sink_2, 0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  const diagnostic_physical_location *loc_start
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8);
+  const diagnostic_physical_location *loc_end
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19);
+  const diagnostic_physical_location *loc_range
+    = diagnostic_manager_new_location_from_range (diag_mgr,
+						  loc_start,
+						  loc_start,
+						  loc_end);
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_ERROR);
+  diagnostic_set_location (d, loc_range);
+  
+  diagnostic_finish (d, "can't find %qs", "foo");
+
+  diagnostic_manager_release (diag_mgr);
+  return 0;
+};
diff --git a/gcc/testsuite/libdiagnostics.dg/test-warning.c b/gcc/testsuite/libdiagnostics.dg/test-warning.c
new file mode 100644
index 000000000000..cef1db3cfced
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-warning.c
@@ -0,0 +1,52 @@
+/* Example of emitting a warning.
+
+   Intended output is similar to:
+   
+/PATH/test-warning.c:15: warning: this is a warning
+   15 | PRINT "hello world!";
+      |        ^~~~~~~~~~~~
+
+   along with the equivalent in SARIF.  */
+
+#include "libdiagnostics.h"
+
+/*
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_add_text_sink (diag_mgr, stderr,
+				    DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
+				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr,
+							     __FILE__,
+							     "c");
+
+  const diagnostic_physical_location *loc_start
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8);
+  const diagnostic_physical_location *loc_end
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19);
+  const diagnostic_physical_location *loc_range
+    = diagnostic_manager_new_location_from_range (diag_mgr,
+						  loc_start,
+						  loc_start,
+						  loc_end);
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_WARNING);
+  diagnostic_set_location (d, loc_range);
+  
+  diagnostic_finish (d, "this is a warning");
+  
+  diagnostic_manager_release (diag_mgr);
+  return 0;
+};
diff --git a/gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file.c b/gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file.c
new file mode 100644
index 000000000000..2cde6947a6df
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file.c
@@ -0,0 +1,46 @@
+/* Example of writing diagnostics as SARIF to a file.  */
+
+#include "libdiagnostics.h"
+
+/*
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  FILE *outfile = fopen ("test.txt", "w");
+  if (!outfile)
+    return -1;
+
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_add_sarif_sink (diag_mgr, outfile,
+				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  const diagnostic_physical_location *loc_start
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8);
+  const diagnostic_physical_location *loc_end
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19);
+  const diagnostic_physical_location *loc_range
+    = diagnostic_manager_new_location_from_range (diag_mgr,
+						  loc_start,
+						  loc_start,
+						  loc_end);
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_ERROR);
+  diagnostic_set_location (d, loc_range);
+  
+  diagnostic_finish (d, "can't find %qs", "foo");
+
+  diagnostic_manager_release (diag_mgr);
+
+  fclose (outfile);
+
+  return 0;
+};
diff --git a/gcc/testsuite/libdiagnostics.dg/test-write-text-to-file.c b/gcc/testsuite/libdiagnostics.dg/test-write-text-to-file.c
new file mode 100644
index 000000000000..8ad448c8c9ce
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-write-text-to-file.c
@@ -0,0 +1,47 @@
+/* Example of writing diagnostics in text form, but to a file, 
+   rather than stderr.  */
+
+#include "libdiagnostics.h"
+
+/*
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  FILE *outfile = fopen ("test.txt", "w");
+  if (!outfile)
+    return -1;
+
+  diagnostic_manager *diag_mgr = diagnostic_manager_new ();
+
+  diagnostic_manager_add_text_sink (diag_mgr, outfile,
+				    DIAGNOSTIC_COLORIZE_NO);
+
+  const diagnostic_file *file = diagnostic_manager_new_file (diag_mgr, __FILE__, "c");
+  const diagnostic_physical_location *loc_start
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 8);
+  const diagnostic_physical_location *loc_end
+    = diagnostic_manager_new_location_from_file_line_column (diag_mgr, file, line_num, 19);
+  const diagnostic_physical_location *loc_range
+    = diagnostic_manager_new_location_from_range (diag_mgr,
+						  loc_start,
+						  loc_start,
+						  loc_end);
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_ERROR);
+  diagnostic_set_location (d, loc_range);
+  
+  diagnostic_finish (d, "can't find %qs", "foo");
+
+  diagnostic_manager_release (diag_mgr);
+
+  fclose (outfile);
+
+  return 0;
+};
-- 
2.26.3


^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH 2/5] libdiagnostics v2: work-in-progress implementation
  2023-11-21 22:20 ` [PATCH 0/6] v2 of libdiagnostics David Malcolm
  2023-11-21 22:20   ` [PATCH 1/5] libdiagnostics v2: header and examples David Malcolm
@ 2023-11-21 22:20   ` David Malcolm
  2023-11-21 22:20   ` [PATCH 3/5] libdiagnostics v2: add C++ wrapper API David Malcolm
                     ` (5 subsequent siblings)
  7 siblings, 0 replies; 29+ messages in thread
From: David Malcolm @ 2023-11-21 22:20 UTC (permalink / raw)
  To: gcc-patches, binutils; +Cc: Nick Clifton, Simon Sobisch, David Malcolm

Changed in v2:
* Changed diagnostic_location_t -> const diagnostic_physical_location *
* new entrypoint: diagnostic_finish_va
* new debugging entrypoints for dumping to a FILE *
* Makefile.in: dropped FULL_DRIVER_NAME from libdiagnostics
* fix up for my recent changes to gcc/diagnostic.h

Blurb from v1:

Here's a work-in-progress patch for GCC that adds the implementation
of libdiagnostics.  Various aspects of this need work; posting now
for early feedback on overall direction.

gcc/ChangeLog:
	* Makefile.in (lang_checks): Add check-libdiagnostics.
	(start.encap): Add libdiagnostics.
	(libdiagnostics_OBJS): New.
	...plus a bunch of stuff hacked up from jit/Make-lang.in.
	* configure: Regenerate.
	* configure.ac (check_languages): Add check-libdiagnostics.
	* libdiagnostics.cc: New file.
	* libdiagnostics.map: New file.

gcc/testsuite/ChangeLog:
	* libdiagnostics.dg/libdiagnostics.exp: New, based on jit.exp.
---
 gcc/Makefile.in                               |  131 +-
 gcc/configure                                 |    2 +-
 gcc/configure.ac                              |    2 +-
 gcc/libdiagnostics.cc                         | 1306 +++++++++++++++++
 gcc/libdiagnostics.map                        |   63 +
 .../libdiagnostics.dg/libdiagnostics.exp      |  544 +++++++
 6 files changed, 2044 insertions(+), 4 deletions(-)
 create mode 100644 gcc/libdiagnostics.cc
 create mode 100644 gcc/libdiagnostics.map
 create mode 100644 gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 7b2f0c0f629c..7ad9eb442220 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -618,7 +618,7 @@ host_xm_defines=@host_xm_defines@
 xm_file_list=@xm_file_list@
 xm_include_list=@xm_include_list@
 xm_defines=@xm_defines@
-lang_checks=
+lang_checks=check-libdiagnostics
 lang_checks_parallelized=
 lang_opt_files=@lang_opt_files@ $(srcdir)/c-family/c.opt $(srcdir)/common.opt $(srcdir)/params.opt $(srcdir)/analyzer/analyzer.opt
 lang_specs_files=@lang_specs_files@
@@ -2169,7 +2169,7 @@ all.cross: native gcc-cross$(exeext) cpp$(exeext) specs \
 	libgcc-support lang.all.cross doc selftest @GENINSRC@ srcextra
 # This is what must be made before installing GCC and converting libraries.
 start.encap: native xgcc$(exeext) cpp$(exeext) specs \
-	libgcc-support lang.start.encap @GENINSRC@ srcextra
+	libgcc-support lang.start.encap libdiagnostics @GENINSRC@ srcextra
 # These can't be made until after GCC can run.
 rest.encap: lang.rest.encap
 # This is what is made with the host's compiler
@@ -2258,6 +2258,133 @@ cpp$(exeext): $(GCC_OBJS) c-family/cppspec.o libcommon-target.a $(LIBDEPS) \
 	  c-family/cppspec.o $(EXTRA_GCC_OBJS) libcommon-target.a \
 	  $(EXTRA_GCC_LIBS) $(LIBS)
 
+
+libdiagnostics_OBJS = libdiagnostics.o \
+	libcommon.a
+
+# FIXME:
+# Define the names for selecting jit in LANGUAGES.
+# Note that it would be nice to move the dependency on g++
+# into the jit rule, but that needs a little bit of work
+# to do the right thing within all.cross.
+
+LIBDIAGNOSTICS_VERSION_NUM = 0
+LIBDIAGNOSTICS_MINOR_NUM = 0
+LIBDIAGNOSTICS_RELEASE_NUM = 1
+
+ifneq (,$(findstring mingw,$(target)))
+LIBDIAGNOSTICS_FILENAME = libdiagnostics-$(LIBDIAGNOSTICS_VERSION_NUM).dll
+LIBDIAGNOSTICS_IMPORT_LIB = libdiagnostics.dll.a
+
+libdiagnostics: $(LIBDIAGNOSTICS_FILENAME)
+
+else
+
+ifneq (,$(findstring darwin,$(host)))
+
+LIBDIAGNOSTICS_AGE = 1
+LIBDIAGNOSTICS_BASENAME = libdiagnostics
+
+LIBDIAGNOSTICS_SONAME = \
+  ${libdir}/$(LIBDIAGNOSTICS_BASENAME).$(LIBDIAGNOSTICS_VERSION_NUM).dylib
+LIBDIAGNOSTICS_FILENAME = $(LIBDIAGNOSTICS_BASENAME).$(LIBDIAGNOSTICS_VERSION_NUM).dylib
+LIBDIAGNOSTICS_LINKER_NAME = $(LIBDIAGNOSTICS_BASENAME).dylib
+
+# Conditionalize the use of the LD_VERSION_SCRIPT_OPTION and
+# LD_SONAME_OPTION depending if configure found them, using $(if)
+# We have to define a LIBDIAGNOSTICS_COMMA here, otherwise the commas in the "true"
+# result are treated as separators by the $(if).
+LIBDIAGNOSTICS_COMMA := ,
+LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION = \
+	$(if $(LD_VERSION_SCRIPT_OPTION),\
+	  -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_VERSION_SCRIPT_OPTION)$(LIBDIAGNOSTICS_COMMA)$(srcdir)/libdiagnostics.map)
+
+LIBDIAGNOSTICS_SONAME_OPTION = \
+	$(if $(LD_SONAME_OPTION), \
+	     -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_SONAME_OPTION)$(LIBDIAGNOSTICS_COMMA)$(LIBDIAGNOSTICS_SONAME))
+
+LIBDIAGNOSTICS_SONAME_SYMLINK = $(LIBDIAGNOSTICS_FILENAME)
+LIBDIAGNOSTICS_LINKER_NAME_SYMLINK = $(LIBDIAGNOSTICS_LINKER_NAME)
+
+libdiagnostics: $(LIBDIAGNOSTICS_FILENAME) \
+	$(LIBDIAGNOSTICS_SYMLINK) \
+	$(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK)
+
+else
+
+LIBDIAGNOSTICS_LINKER_NAME = libdiagnostics.so
+LIBDIAGNOSTICS_SONAME = $(LIBDIAGNOSTICS_LINKER_NAME).$(LIBDIAGNOSTICS_VERSION_NUM)
+LIBDIAGNOSTICS_FILENAME = \
+  $(LIBDIAGNOSTICS_SONAME).$(LIBDIAGNOSTICS_MINOR_NUM).$(LIBDIAGNOSTICS_RELEASE_NUM)
+
+LIBDIAGNOSTICS_LINKER_NAME_SYMLINK = $(LIBDIAGNOSTICS_LINKER_NAME)
+LIBDIAGNOSTICS_SONAME_SYMLINK = $(LIBDIAGNOSTICS_SONAME)
+
+# Conditionalize the use of the LD_VERSION_SCRIPT_OPTION and
+# LD_SONAME_OPTION depending if configure found them, using $(if)
+# We have to define a LIBDIAGNOSTICS_COMMA here, otherwise the commas in the "true"
+# result are treated as separators by the $(if).
+LIBDIAGNOSTICS_COMMA := ,
+LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION = \
+	$(if $(LD_VERSION_SCRIPT_OPTION),\
+	  -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_VERSION_SCRIPT_OPTION)$(LIBDIAGNOSTICS_COMMA)$(srcdir)/libdiagnostics.map)
+
+LIBDIAGNOSTICS_SONAME_OPTION = \
+	$(if $(LD_SONAME_OPTION), \
+	     -Wl$(LIBDIAGNOSTICS_COMMA)$(LD_SONAME_OPTION)$(LIBDIAGNOSTICS_COMMA)$(LIBDIAGNOSTICS_SONAME))
+
+libdiagnostics: $(LIBDIAGNOSTICS_FILENAME) \
+	$(LIBDIAGNOSTICS_SYMLINK) \
+	$(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK)
+
+endif
+endif
+
+libdiagnostics.serial = $(LIBDIAGNOSTICS_FILENAME)
+
+# Tell GNU make to ignore these if they exist.
+.PHONY: libdiagnostics
+
+ifneq (,$(findstring mingw,$(target)))
+# Create import library
+LIBDIAGNOSTICS_EXTRA_OPTS = -Wl,--out-implib,$(LIBDIAGNOSTICS_IMPORT_LIB)
+else
+
+ifneq (,$(findstring darwin,$(host)))
+# TODO : Construct a Darwin-style symbol export file.
+LIBDIAGNOSTICS_EXTRA_OPTS = -Wl,-compatibility_version,$(LIBDIAGNOSTICS_VERSION_NUM) \
+	-Wl,-current_version,$(LIBDIAGNOSTICS_VERSION_NUM).$(LIBDIAGNOSTICS_MINOR_NUM).$(LIBDIAGNOSTICS_AGE) \
+	$(LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION) \
+	$(LIBDIAGNOSTICS_SONAME_OPTION)
+else
+
+LIBDIAGNOSTICS_EXTRA_OPTS = $(LIBDIAGNOSTICS_VERSION_SCRIPT_OPTION) \
+	$(LIBDIAGNOSTICS_SONAME_OPTION)
+endif
+endif
+
+$(LIBDIAGNOSTICS_FILENAME): $(libdiagnostics_OBJS) $(CPPLIB) $(EXTRA_GCC_LIBS) $(LIBS) \
+	$(LIBDEPS) $(srcdir)/libdiagnostics.map
+	@$(call LINK_PROGRESS,$(INDEX.libdiagnostics),start)
+	+$(LLINKER) $(ALL_LINKERFLAGS) $(LDFLAGS) -o $@ -shared \
+	     $(libdiagnostics_OBJS) \
+	     $(CPPLIB) $(EXTRA_GCC_LIBS) $(LIBS) \
+	     $(LIBDIAGNOSTICS_EXTRA_OPTS)
+	@$(call LINK_PROGRESS,$(INDEX.libdiagnostics),end)
+
+# Create symlinks when not building for Windows
+ifeq (,$(findstring mingw,$(target)))
+
+ifeq (,$(findstring darwin,$(host)))
+# but only one level for Darwin, version info is embedded.
+$(LIBDIAGNOSTICS_SONAME_SYMLINK): $(LIBDIAGNOSTICS_FILENAME)
+	ln -sf $(LIBDIAGNOSTICS_FILENAME) $(LIBDIAGNOSTICS_SONAME_SYMLINK)
+endif
+
+$(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK): $(LIBDIAGNOSTICS_SONAME_SYMLINK)
+	ln -sf $(LIBDIAGNOSTICS_SONAME_SYMLINK) $(LIBDIAGNOSTICS_LINKER_NAME_SYMLINK)
+endif
+
 # Dump a specs file to make -B./ read these specs over installed ones.
 $(SPECS): xgcc$(exeext)
 	$(GCC_FOR_TARGET) -dumpspecs > tmp-specs
diff --git a/gcc/configure b/gcc/configure
index cc0c3aad67be..3ae0b4eb37da 100755
--- a/gcc/configure
+++ b/gcc/configure
@@ -33729,7 +33729,7 @@ $as_echo "#define ENABLE_LTO 1" >>confdefs.h
 	esac
 done
 
-check_languages=
+check_languages=check-libdiagnostics
 for language in $all_selected_languages
 do
 	check_languages="$check_languages check-$language"
diff --git a/gcc/configure.ac b/gcc/configure.ac
index d9a35069e30d..55fbb8acebaa 100644
--- a/gcc/configure.ac
+++ b/gcc/configure.ac
@@ -7327,7 +7327,7 @@ changequote([,])dnl
 	esac
 done
 
-check_languages=
+check_languages=check-libdiagnostics
 for language in $all_selected_languages
 do
 	check_languages="$check_languages check-$language"
diff --git a/gcc/libdiagnostics.cc b/gcc/libdiagnostics.cc
new file mode 100644
index 000000000000..3236ce6837c7
--- /dev/null
+++ b/gcc/libdiagnostics.cc
@@ -0,0 +1,1306 @@
+/* C++ implementation of a pure C API for emitting diagnostics.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+
+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/>.  */
+
+#include "config.h"
+#define INCLUDE_MEMORY
+#define INCLUDE_VECTOR
+#include "system.h"
+#include "coretypes.h"
+#include "intl.h"
+#include "diagnostic.h"
+#include "diagnostic-color.h"
+#include "diagnostic-url.h"
+#include "diagnostic-metadata.h"
+#include "diagnostic-client-data-hooks.h"
+#include "logical-location.h"
+#include "edit-context.h"
+#include "make-unique.h"
+#include "libdiagnostics.h"
+
+class owned_nullable_string
+{
+public:
+  owned_nullable_string () : m_str (nullptr) {}
+  owned_nullable_string (const char *str)
+  : m_str (str ? ::xstrdup (str) : nullptr)
+  {
+  }
+
+  ~owned_nullable_string ()
+  {
+    free (m_str);
+  }
+
+  void set (const char *str)
+  {
+    free (m_str);
+    m_str = str ? ::xstrdup (str) : nullptr;
+  }
+
+  const char *get_str () const { return m_str; }
+
+  char *xstrdup () const
+  {
+    return m_str ? ::xstrdup (m_str) : nullptr;
+  }
+
+private:
+  char *m_str;
+};
+
+/* This has to be a "struct" as it is exposed in the C API.  */
+
+struct diagnostic_file
+{
+  diagnostic_file (const char *name, const char *sarif_source_language)
+  : m_name (name), m_sarif_source_language (sarif_source_language)
+  {
+  }
+
+  const char *get_name () const { return m_name.get_str (); }
+  const char *get_sarif_source_language () const
+  {
+    return m_sarif_source_language.get_str ();
+  }
+
+private:
+  owned_nullable_string m_name;
+  owned_nullable_string m_sarif_source_language;
+};
+
+/* This has to be a "struct" as it is exposed in the C API.  */
+
+struct diagnostic_physical_location
+{
+  diagnostic_physical_location (location_t inner) : m_inner (inner) {}
+
+  location_t m_inner;
+};
+
+static location_t
+as_location_t (const diagnostic_physical_location *loc)
+{
+  if (!loc)
+    return UNKNOWN_LOCATION;
+  return loc->m_inner;
+}
+
+/* This has to be a "struct" as it is exposed in the C API.  */
+
+struct diagnostic_logical_location : public logical_location
+{
+  diagnostic_logical_location (enum diagnostic_logical_location_kind_t kind,
+			       const diagnostic_logical_location *parent,
+			       const char *short_name,
+			       const char *fully_qualified_name,
+			       const char *decorated_name)
+  : m_kind (kind),
+    m_parent (parent),
+    m_short_name (short_name),
+    m_fully_qualified_name (fully_qualified_name),
+    m_decorated_name (decorated_name)
+  {
+  }
+
+  const char *get_short_name () const final override
+  {
+    return m_short_name.get_str ();
+  }
+  const char *get_name_with_scope () const final override
+  {
+    return m_fully_qualified_name.get_str ();
+  }
+  const char *get_internal_name () const final override
+  {
+    return m_decorated_name.get_str ();
+  }
+  enum logical_location_kind get_kind () const final override
+  {
+    switch (m_kind)
+      {
+      default:
+	gcc_unreachable ();
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION:
+	return LOGICAL_LOCATION_KIND_FUNCTION;
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_MEMBER:
+	return LOGICAL_LOCATION_KIND_MEMBER;
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_MODULE:
+	return LOGICAL_LOCATION_KIND_MODULE;
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_NAMESPACE:
+	return LOGICAL_LOCATION_KIND_NAMESPACE;
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_TYPE:
+	return LOGICAL_LOCATION_KIND_TYPE;
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_RETURN_TYPE:
+	return LOGICAL_LOCATION_KIND_RETURN_TYPE;
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_PARAMETER:
+	return LOGICAL_LOCATION_KIND_PARAMETER;
+      case DIAGNOSTIC_LOGICAL_LOCATION_KIND_VARIABLE:
+	return LOGICAL_LOCATION_KIND_VARIABLE;
+      }
+  }
+
+  enum diagnostic_logical_location_kind_t get_external_kind () const
+  {
+    return m_kind;
+  }
+
+  const diagnostic_logical_location *get_parent () const { return m_parent; }
+
+private:
+  enum diagnostic_logical_location_kind_t m_kind;
+  const diagnostic_logical_location *m_parent;
+  owned_nullable_string m_short_name;
+  owned_nullable_string m_fully_qualified_name;
+  owned_nullable_string m_decorated_name;
+};
+
+class sink
+{
+public:
+  virtual ~sink ();
+
+  void begin_group ()
+  {
+    m_dc.begin_group ();
+  }
+  void end_group ()
+  {
+    m_dc.end_group ();
+  }
+
+  void emit (diagnostic &diag, const char *msgid, va_list *args)
+    LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING(3, 0);
+
+protected:
+  sink (diagnostic_manager &mgr);
+
+  diagnostic_manager &m_mgr;
+
+  /* One context per sink.  */
+  diagnostic_context m_dc;
+};
+
+/* This has to be a "struct" as it is exposed in the C API.  */
+
+struct diagnostic_text_sink : public sink
+{
+public:
+  diagnostic_text_sink (diagnostic_manager &mgr,
+			FILE *dst_stream,
+			enum diagnostic_colorize colorize);
+
+  void
+  on_begin_text_diagnostic (diagnostic_info *info);
+
+  diagnostic_source_printing_options &get_source_printing_options ()
+  {
+    return m_dc.m_source_printing;
+  }
+
+private:
+  const diagnostic_logical_location *m_current_logical_loc;
+};
+
+class sarif_sink : public sink
+{
+public:
+  sarif_sink (diagnostic_manager &mgr,
+	      FILE *dst_stream,
+	      enum diagnostic_sarif_version version);
+};
+
+/* Helper for the linemap code.  */
+
+static size_t
+round_alloc_size (size_t s)
+{
+  return s;
+}
+
+class impl_diagnostic_client_data_hooks : public diagnostic_client_data_hooks
+{
+public:
+  impl_diagnostic_client_data_hooks (diagnostic_manager &mgr)
+  : m_mgr (mgr)
+  {}
+
+  const client_version_info *get_any_version_info () const final override;
+  const logical_location *get_current_logical_location () const final override;
+  const char * maybe_get_sarif_source_language (const char *filename)
+    const final override;
+  void add_sarif_invocation_properties (sarif_object &invocation_obj)
+    const final override;
+
+private:
+  diagnostic_manager &m_mgr;
+};
+
+class impl_client_version_info : public client_version_info
+{
+public:
+  const char *get_tool_name () const final override
+  {
+    return m_name.get_str ();
+  }
+
+  char *maybe_make_full_name () const final override
+  {
+    return m_full_name.xstrdup ();
+  }
+
+  const char *get_version_string () const final override
+  {
+    return m_version.get_str ();
+  }
+
+  char *maybe_make_version_url () const final override
+  {
+    return m_version_url.xstrdup ();
+  }
+
+  void for_each_plugin (plugin_visitor &) const final override
+  {
+    // No-op.
+  }
+
+  owned_nullable_string m_name;
+  owned_nullable_string m_full_name;
+  owned_nullable_string m_version;
+  owned_nullable_string m_version_url;
+};
+
+/* This has to be a "struct" as it is exposed in the C API.  */
+
+struct diagnostic_manager
+{
+public:
+  diagnostic_manager ()
+    : m_current_diag (nullptr),
+      m_edit_context (m_file_cache)
+  {
+    linemap_init (&m_line_table, BUILTINS_LOCATION);
+    m_line_table.m_reallocator = xrealloc;
+    m_line_table.m_round_alloc_size = round_alloc_size;
+    m_line_table.default_range_bits = 5;
+  }
+  ~diagnostic_manager ()
+  {
+    /* Clean up sinks first, as they can use other fields. */
+    for (size_t i = 0; i < m_sinks.size (); i++)
+      m_sinks[i] = nullptr;
+
+    for (auto iter : m_str_to_file_map)
+      delete iter.second;
+
+    for (auto iter :m_location_t_map)
+      delete iter.second;
+  }
+
+  line_maps *get_line_table () { return &m_line_table; }
+  file_cache *get_file_cache () { return &m_file_cache; }
+
+  void write_patch (FILE *dst_stream);
+
+  void add_sink (std::unique_ptr<sink> sink)
+  {
+    m_sinks.push_back (std::move (sink));
+  }
+
+  void emit (diagnostic &diag, const char *msgid, va_list *args)
+    LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING(3, 0);
+
+  const diagnostic_file *
+  new_file (const char *name,
+	    const char *sarif_source_language)
+  {
+    if (diagnostic_file **slot = m_str_to_file_map.get (name))
+      return *slot;
+    diagnostic_file *file = new diagnostic_file (name, sarif_source_language);
+    m_str_to_file_map.put (file->get_name (), file);
+    return file;
+  }
+
+  const diagnostic_physical_location *
+  new_location_from_file_and_line (const diagnostic_file *file,
+				   diagnostic_line_num_t linenum)
+  {
+    // FIXME: this is a hack...
+    /* Build a simple linemap describing some locations. */
+    if (LINEMAPS_ORDINARY_USED (&m_line_table) == 0)
+      linemap_add (&m_line_table, LC_ENTER, false, file->get_name (), 0);
+    else
+      {
+	const line_map *map
+	  = linemap_add (&m_line_table, LC_RENAME_VERBATIM, false,
+			 file->get_name (), 0);
+	// FIXME:
+	((line_map_ordinary *)map)->included_from = UNKNOWN_LOCATION;
+      }
+    linemap_line_start (&m_line_table, linenum, 100);
+    return new_location (linemap_position_for_column (&m_line_table, 0));
+  }
+
+
+  const diagnostic_physical_location *
+  new_location_from_file_line_column (const diagnostic_file *file,
+				      diagnostic_line_num_t line_num,
+				      diagnostic_column_num_t column_num)
+  {
+    // FIXME: this is a hack...
+    /* Build a simple linemap describing some locations. */
+    linemap_add (&m_line_table, LC_ENTER, false, file->get_name (), 0);
+    linemap_line_start (&m_line_table, line_num, 100);
+    return new_location (linemap_position_for_column (&m_line_table, column_num));
+  }
+
+  const diagnostic_physical_location *
+  new_location_from_range (const diagnostic_physical_location *loc_caret,
+			   const diagnostic_physical_location *loc_start,
+			   const diagnostic_physical_location *loc_end)
+  {
+    return new_location
+      (m_line_table.make_location (as_location_t (loc_caret),
+				   as_location_t (loc_start),
+				   as_location_t (loc_end)));
+  }
+
+  const diagnostic_logical_location *
+  new_logical_location (enum diagnostic_logical_location_kind_t kind,
+			const diagnostic_logical_location *parent,
+			const char *short_name,
+			const char *fully_qualified_name,
+			const char *decorated_name)
+  {
+    std::unique_ptr<diagnostic_logical_location> logical_loc
+      = ::make_unique<diagnostic_logical_location> (kind,
+						    parent,
+						    short_name,
+						    fully_qualified_name,
+						    decorated_name);
+    const diagnostic_logical_location *result = logical_loc.get ();
+    m_logical_locs.push_back (std::move (logical_loc));
+    return result;
+  }
+
+  void begin_group ()
+  {
+    for (auto &sink : m_sinks)
+      sink->begin_group ();
+  }
+
+  void end_group ()
+  {
+    for (auto &sink : m_sinks)
+      sink->end_group ();
+  }
+
+  const char *
+  maybe_get_sarif_source_language (const char *filename)
+  {
+    if (diagnostic_file **slot = m_str_to_file_map.get (filename))
+      {
+	gcc_assert (*slot);
+	return (*slot)->get_sarif_source_language ();
+      }
+    return nullptr;
+  }
+
+  const diagnostic *get_current_diag () { return m_current_diag; }
+
+  const client_version_info *get_client_version_info () const
+  {
+    return &m_client_version_info;
+  }
+  impl_client_version_info *get_client_version_info ()
+  {
+    return &m_client_version_info;
+  }
+
+  void
+  assert_valid_diagnostic_physical_location (const diagnostic_physical_location *loc) const
+  {
+    // TODO
+    (void)loc;
+  }
+
+  /* FIXME: Various things still used the "line_table" global variable.
+     Set it to be this diagnostic_manager's m_line_table.
+     Ideally we should eliminate this global (and this function).  */
+  void set_line_table_global () const
+  {
+    line_table = const_cast<line_maps *> (&m_line_table);
+  }
+
+private:
+  const diagnostic_physical_location *
+  new_location (location_t loc)
+  {
+    if (loc == UNKNOWN_LOCATION)
+      return nullptr;
+    if (diagnostic_physical_location **slot = m_location_t_map.get (loc))
+      return *slot;
+    diagnostic_physical_location *phys_loc
+      = new diagnostic_physical_location (loc);
+    m_location_t_map.put (loc, phys_loc);
+    return phys_loc;
+  }
+
+  line_maps m_line_table;
+  file_cache m_file_cache;
+  impl_client_version_info m_client_version_info;
+  std::vector<std::unique_ptr<sink>> m_sinks;
+  hash_map<nofree_string_hash, diagnostic_file *> m_str_to_file_map;
+  hash_map<int_hash<location_t, UNKNOWN_LOCATION, UINT_MAX>,
+	   diagnostic_physical_location *> m_location_t_map;
+  std::vector<std::unique_ptr<diagnostic_logical_location>> m_logical_locs;
+  const diagnostic *m_current_diag;
+  edit_context m_edit_context;
+};
+
+class impl_rich_location : public rich_location
+{
+public:
+  impl_rich_location (line_maps *set)
+  : rich_location (set, UNKNOWN_LOCATION)
+  {}
+};
+
+class impl_range_label : public range_label
+{
+public:
+  impl_range_label (const char *text)
+  : m_text (xstrdup (text))
+  {}
+
+  ~impl_range_label () { free (m_text); }
+
+  label_text get_text (unsigned) const final override
+  {
+    return label_text::borrow (m_text);
+  }
+
+private:
+  char *m_text;
+};
+
+class impl_rule : public diagnostic_metadata::rule
+{
+public:
+  impl_rule (const char *title, const char *url)
+  :  m_title (title),
+     m_url (url)
+  {
+  }
+
+  char *make_description () const final override
+  {
+    return m_title.xstrdup ();
+  }
+
+  char *make_url () const final override
+  {
+    return m_url.xstrdup ();
+  }
+
+private:
+  owned_nullable_string m_title;
+  owned_nullable_string m_url;
+};
+
+/* This has to be a "struct" as it is exposed in the C API.  */
+
+struct diagnostic
+{
+public:
+  diagnostic (diagnostic_manager &diag_mgr,
+	      enum diagnostic_level level)
+  : m_diag_mgr (diag_mgr),
+    m_level (level),
+    m_rich_loc (diag_mgr.get_line_table ()),
+    m_logical_loc (nullptr)
+  {}
+
+  diagnostic_manager &get_manager () const
+  {
+    return m_diag_mgr;
+  }
+
+  enum diagnostic_level get_level () const { return m_level; }
+
+  rich_location *get_rich_location () { return &m_rich_loc; }
+  const diagnostic_metadata *get_metadata () { return &m_metadata; }
+
+#if 0
+  diagnostic_location_t get_location () const { return m_loc; }
+#endif
+
+  void set_cwe (unsigned cwe_id)
+  {
+    m_metadata.add_cwe (cwe_id);
+  }
+
+  void add_rule (const char *title,
+		 const char *url)
+  {
+    std::unique_ptr<impl_rule> rule = ::make_unique<impl_rule> (title, url);
+    m_metadata.add_rule (*rule.get ());
+    m_rules.push_back (std::move (rule));
+  }
+
+  void set_location (const diagnostic_physical_location *loc)
+  {
+    m_rich_loc.set_range (0, as_location_t (loc), SHOW_RANGE_WITH_CARET);
+  }
+
+  void
+  add_location (const diagnostic_physical_location *loc)
+  {
+    m_rich_loc.add_range (as_location_t (loc),
+			  SHOW_RANGE_WITHOUT_CARET);
+  }
+
+  void
+  add_location_with_label (const diagnostic_physical_location *loc,
+			   const char *text)
+  {
+    std::unique_ptr<range_label> label = ::make_unique <impl_range_label> (text);
+    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)
+  {
+    m_logical_loc = logical_loc;
+  }
+  const diagnostic_logical_location *get_logical_location () const
+  {
+    return m_logical_loc;
+  }
+
+private:
+  diagnostic_manager &m_diag_mgr;
+  enum diagnostic_level m_level;
+  impl_rich_location m_rich_loc;
+  const diagnostic_logical_location *m_logical_loc;
+  diagnostic_metadata m_metadata;
+  std::vector<std::unique_ptr<range_label>> m_labels;
+  std::vector<std::unique_ptr<impl_rule>> m_rules;
+};
+
+static diagnostic_t
+diagnostic_t_from_diagnostic_level (enum diagnostic_level level)
+{
+  switch (level)
+    {
+    default:
+      gcc_unreachable ();
+    case DIAGNOSTIC_LEVEL_ERROR:
+      return DK_ERROR;
+    case DIAGNOSTIC_LEVEL_WARNING:
+      return DK_WARNING;
+    case DIAGNOSTIC_LEVEL_NOTE:
+      return DK_NOTE;
+    }
+}
+
+/* class impl_diagnostic_client_data_hooks.  */
+
+const client_version_info *
+impl_diagnostic_client_data_hooks::get_any_version_info () const
+{
+  return m_mgr.get_client_version_info ();
+}
+
+const logical_location *
+impl_diagnostic_client_data_hooks::get_current_logical_location () const
+{
+  gcc_assert (m_mgr.get_current_diag ());
+
+  return m_mgr.get_current_diag ()->get_logical_location ();
+}
+
+const char *
+impl_diagnostic_client_data_hooks::
+maybe_get_sarif_source_language (const char *filename) const
+{
+  return m_mgr.maybe_get_sarif_source_language (filename);
+}
+
+void
+impl_diagnostic_client_data_hooks::
+add_sarif_invocation_properties (sarif_object &) const
+{
+  // No-op.
+}
+
+/* class sink.  */
+
+void
+sink::emit (diagnostic &diag, const char *msgid, va_list *args)
+{
+  diagnostic_info info;
+  diagnostic_set_info (&info, msgid, args, diag.get_rich_location (),
+		       diagnostic_t_from_diagnostic_level (diag.get_level ()));
+  info.metadata = diag.get_metadata ();
+  diagnostic_report_diagnostic (&m_dc, &info);
+}
+
+sink::sink (diagnostic_manager &mgr)
+: m_mgr (mgr)
+{
+  diagnostic_initialize (&m_dc, 0);
+  m_dc.m_client_aux_data = this;
+  m_dc.set_client_data_hooks (new impl_diagnostic_client_data_hooks (mgr));
+}
+
+sink::~sink ()
+{
+  diagnostic_finish (&m_dc);
+}
+
+/* struct diagnostic_text_sink : public sink.  */
+
+diagnostic_text_sink::diagnostic_text_sink (diagnostic_manager &mgr,
+					    FILE *dst_stream,
+					    enum diagnostic_colorize colorize)
+: sink (mgr)
+{
+  m_dc.set_show_cwe (true);
+  m_dc.set_show_rules (true);
+
+  diagnostic_color_rule_t color_rule;
+  switch (colorize)
+    {
+    default:
+      gcc_unreachable ();
+    case DIAGNOSTIC_COLORIZE_IF_TTY:
+      color_rule = DIAGNOSTICS_COLOR_AUTO;
+      break;
+    case DIAGNOSTIC_COLORIZE_NO:
+      color_rule = DIAGNOSTICS_COLOR_NO;
+      break;
+    case DIAGNOSTIC_COLORIZE_YES:
+      color_rule = DIAGNOSTICS_COLOR_YES;
+      break;
+    }
+  diagnostic_color_init (&m_dc, color_rule);
+  m_dc.printer->buffer->stream = dst_stream;
+  diagnostic_starter (&m_dc)
+    = [] (diagnostic_context *context,
+	  diagnostic_info *info)
+      {
+	diagnostic_text_sink *sink
+	  = static_cast<diagnostic_text_sink *> (context->m_client_aux_data);
+	sink->on_begin_text_diagnostic (info);
+      };
+  m_dc.m_source_printing.enabled = true;
+  m_dc.m_source_printing.colorize_source_p = true;
+
+  /* We don't currently expose a way for clients to manipulate the
+     following.  */
+  m_dc.m_source_printing.show_labels_p = true;
+  m_dc.m_source_printing.show_line_numbers_p = true;
+  m_dc.m_source_printing.min_margin_width = 6;
+}
+
+void
+diagnostic_text_sink::on_begin_text_diagnostic (diagnostic_info *info)
+{
+  const diagnostic *diag = m_mgr.get_current_diag ();
+  gcc_assert (diag);
+  const diagnostic_logical_location *diag_logical_loc
+    = diag->get_logical_location ();
+  if (m_current_logical_loc != diag_logical_loc)
+    {
+      m_current_logical_loc = diag_logical_loc;
+      if (m_current_logical_loc)
+	{
+	  char *old_prefix = pp_take_prefix (m_dc.printer);
+	  switch (m_current_logical_loc->get_kind ())
+	    {
+	    default:
+	      break;
+	    case LOGICAL_LOCATION_KIND_FUNCTION:
+	      if (const char *name
+		  = m_current_logical_loc->get_name_with_scope ())
+		{
+		  pp_printf (m_dc.printer, _("In function %qs"), name);
+		  pp_character (m_dc.printer, ':');
+		  pp_newline (m_dc.printer);
+		}
+	      break;
+	      // FIXME: handle other cases
+	    }
+	  m_dc.printer->prefix = old_prefix;
+	}
+    }
+  pp_set_prefix (m_dc.printer,
+		 diagnostic_build_prefix (&m_dc, info));
+}
+
+/* class sarif_sink : public sink.  */
+
+sarif_sink::sarif_sink (diagnostic_manager &mgr,
+			FILE *dst_stream,
+			enum diagnostic_sarif_version)
+: sink (mgr)
+{
+  diagnostic_output_format_init_sarif_stream (&m_dc, dst_stream);
+}
+
+/* struct diagnostic_manager.  */
+
+void
+diagnostic_manager::write_patch (FILE *dst_stream)
+{
+  pretty_printer pp;
+  pp.buffer->stream = dst_stream;
+  m_edit_context.print_diff (&pp, true);
+  pp_flush (&pp);
+}
+
+void
+diagnostic_manager::emit (diagnostic &diag, const char *msgid, va_list *args)
+{
+  set_line_table_global ();
+
+  m_current_diag = &diag;
+  for (auto &sink : m_sinks)
+    {
+      va_list arg_copy;
+      va_copy (arg_copy, *args);
+      sink->emit (diag, msgid, &arg_copy);
+    }
+
+  rich_location *rich_loc = diag.get_rich_location ();
+  if (rich_loc->fixits_can_be_auto_applied_p ())
+    m_edit_context.add_fixits (rich_loc);
+
+  m_current_diag = nullptr;
+}
+
+/* Public entrypoints.  */
+
+/* Public entrypoint for clients to acquire a diagnostic_manager.  */
+
+diagnostic_manager *
+diagnostic_manager_new (void)
+{
+  return new diagnostic_manager ();
+}
+
+/* Public entrypoint for clients to release a diagnostic_manager.  */
+
+void
+diagnostic_manager_release (diagnostic_manager *diag_mgr)
+{
+  delete diag_mgr;
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_set_tool_name (diagnostic_manager *diag_mgr,
+				  const char *value)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (value);
+
+  diag_mgr->get_client_version_info ()->m_name.set (value);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_set_full_name (diagnostic_manager *diag_mgr,
+				  const char *value)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (value);
+
+  diag_mgr->get_client_version_info ()->m_full_name.set (value);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_set_version_string (diagnostic_manager *diag_mgr,
+				       const char *value)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (value);
+
+  diag_mgr->get_client_version_info ()->m_version.set (value);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_set_version_url (diagnostic_manager *diag_mgr,
+				    const char *value)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (value);
+
+  diag_mgr->get_client_version_info ()->m_version_url.set (value);
+}
+
+/* Public entrypoint.  */
+
+diagnostic_text_sink *
+diagnostic_manager_add_text_sink (diagnostic_manager *diag_mgr,
+				  FILE *dst_stream,
+				  enum diagnostic_colorize colorize)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (dst_stream);
+
+  diagnostic_text_sink *result
+    = new diagnostic_text_sink (*diag_mgr, dst_stream, colorize);
+  diag_mgr->add_sink (std::unique_ptr<sink> (result));
+  return result;
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_text_sink_set_source_printing_enabled (diagnostic_text_sink *text_sink,
+						  int value)
+{
+  gcc_assert (text_sink);
+
+  text_sink->get_source_printing_options ().enabled = value;
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_text_sink_set_labelled_source_colorization_enabled (diagnostic_text_sink *text_sink,
+							       int value)
+{
+  gcc_assert (text_sink);
+
+  text_sink->get_source_printing_options ().colorize_source_p = value;
+}
+
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_add_sarif_sink (diagnostic_manager *diag_mgr,
+				   FILE *dst_stream,
+				   enum diagnostic_sarif_version version)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (dst_stream);
+
+  diag_mgr->add_sink (make_unique<sarif_sink> (*diag_mgr, dst_stream, version));
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_write_patch (diagnostic_manager *diag_mgr,
+				FILE *dst_stream)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (dst_stream);
+
+  diag_mgr->write_patch (dst_stream);
+}
+
+/* Public entrypoint.  */
+
+const diagnostic_file *
+diagnostic_manager_new_file (diagnostic_manager *diag_mgr,
+			     const char *name,
+			     const char *sarif_source_language)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (name);
+
+  return diag_mgr->new_file (name, sarif_source_language);
+}
+
+void
+diagnostic_manager_debug_dump_file (diagnostic_manager *,
+				    const diagnostic_file *file,
+				    FILE *out)
+{
+  gcc_assert (out);
+  if (file)
+    {
+      if (file->get_sarif_source_language ())
+	{
+	  fprintf (out, "file(name=\"%s\", sarif_source_language=\"%s\")",
+		   file->get_name (),
+		   file->get_sarif_source_language ());
+	}
+      else
+	{
+	  fprintf (out, "file(name=\"%s\")",
+		   file->get_name ());
+	}
+    }
+  else
+    fprintf (out, "(null)");
+}
+
+
+/* Public entrypoint.  */
+
+const diagnostic_physical_location *
+diagnostic_manager_new_location_from_file_and_line (diagnostic_manager *diag_mgr,
+						    const diagnostic_file *file,
+						    diagnostic_line_num_t linenum)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (file);
+
+  return diag_mgr->new_location_from_file_and_line (file, linenum);
+}
+
+/* Public entrypoint.  */
+
+const diagnostic_physical_location *
+diagnostic_manager_new_location_from_file_line_column (diagnostic_manager *diag_mgr,
+						       const diagnostic_file *file,
+						       diagnostic_line_num_t line_num,
+					       diagnostic_column_num_t column_num)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (file);
+
+  return diag_mgr->new_location_from_file_line_column (file,
+						       line_num,
+						       column_num);
+}
+
+/* Public entrypoint.  */
+
+const diagnostic_physical_location *
+diagnostic_manager_new_location_from_range (diagnostic_manager *diag_mgr,
+					    const diagnostic_physical_location *loc_caret,
+					    const diagnostic_physical_location *loc_start,
+					    const diagnostic_physical_location *loc_end)
+{
+  gcc_assert (diag_mgr);
+
+  return diag_mgr->new_location_from_range (loc_caret,
+					    loc_start,
+					    loc_end);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_debug_dump_location (const diagnostic_manager *diag_mgr,
+					const diagnostic_physical_location *loc,
+					FILE *out)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (out);
+
+  if (loc)
+    {
+      const location_t cpplib_loc = as_location_t (loc);
+      diag_mgr->set_line_table_global ();
+      const expanded_location exp_loc (expand_location (cpplib_loc));
+
+      diagnostic_context dc;
+      diagnostic_initialize (&dc, 0);
+      dc.m_show_column = true;
+
+      label_text loc_text = dc.get_location_text (exp_loc);
+      fprintf (out, "%s", loc_text.get ());
+    }
+  else
+    fprintf (out, "(null)");
+}
+
+/* Public entrypoint.  */
+
+const diagnostic_logical_location *
+diagnostic_manager_new_logical_location (diagnostic_manager *diag_mgr,
+					 enum diagnostic_logical_location_kind_t kind,
+					 const diagnostic_logical_location *parent,
+					 const char *short_name,
+					 const char *fully_qualified_name,
+					 const char *decorated_name)
+{
+  gcc_assert (diag_mgr);
+
+  return diag_mgr->new_logical_location (kind,
+					 parent,
+					 short_name,
+					 fully_qualified_name,
+					 decorated_name);
+}
+
+void
+diagnostic_manager_debug_dump_logical_location (const diagnostic_manager *diag_mgr,
+						const diagnostic_logical_location *loc,
+						FILE *out)
+{
+  gcc_assert (diag_mgr);
+  gcc_assert (out);
+
+  if (loc)
+    {
+      fprintf (out, "logical_location(kind=");
+      switch (loc->get_external_kind ())
+	{
+	default:
+	  gcc_unreachable ();
+	case DIAGNOSTIC_LOGICAL_LOCATION_KIND_FUNCTION:
+	  fprintf (out, "function");
+	  break;
+	case DIAGNOSTIC_LOGICAL_LOCATION_KIND_MEMBER:
+	  fprintf (out, "member");
+	  break;
+	case DIAGNOSTIC_LOGICAL_LOCATION_KIND_MODULE:
+	  fprintf (out, "module");
+	  break;
+	case DIAGNOSTIC_LOGICAL_LOCATION_KIND_NAMESPACE:
+	  fprintf (out, "namespace");
+	  break;
+	case DIAGNOSTIC_LOGICAL_LOCATION_KIND_TYPE:
+	  fprintf (out, "file");
+	  break;
+	case DIAGNOSTIC_LOGICAL_LOCATION_KIND_RETURN_TYPE:
+	  fprintf (out, "return_type");
+	  break;
+	case DIAGNOSTIC_LOGICAL_LOCATION_KIND_PARAMETER:
+	  fprintf (out, "parameter");
+	  break;
+	case DIAGNOSTIC_LOGICAL_LOCATION_KIND_VARIABLE:
+	  fprintf (out, "variable");
+	  break;
+	}
+      if (const diagnostic_logical_location *parent = loc->get_parent ())
+	diagnostic_manager_debug_dump_logical_location (diag_mgr,
+							parent,
+							out);
+      if (const char *val = loc->get_short_name ())
+	fprintf (out, ", short_name=\"%s\"", val);
+      if (const char *val = loc->get_name_with_scope ())
+	fprintf (out, ", fully_qualified_name=\"%s\"", val);
+      if (const char *val = loc->get_internal_name ())
+	fprintf (out, ", decorated_name=\"%s\"", val);
+      fprintf (out, ")");
+    }
+  else
+    fprintf (out, "(null)");
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_manager_begin_group (diagnostic_manager *diag_mgr)
+{
+  gcc_assert (diag_mgr);
+  diag_mgr->begin_group ();
+}
+
+/* Public entrypoint.  */
+
+extern void
+diagnostic_manager_end_group (diagnostic_manager *diag_mgr)
+{
+  gcc_assert (diag_mgr);
+  diag_mgr->end_group ();
+}
+
+/* Public entrypoint.  */
+
+diagnostic *
+diagnostic_begin (diagnostic_manager *diag_mgr,
+		  enum diagnostic_level level)
+{
+  gcc_assert (diag_mgr);
+
+  return new diagnostic (*diag_mgr, level);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_set_cwe (diagnostic *diag,
+		    unsigned cwe_id)
+{
+  gcc_assert (diag);
+
+  diag->set_cwe (cwe_id);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_rule (diagnostic *diag,
+		     const char *title,
+		     const char *url)
+{
+  gcc_assert (diag);
+
+  diag->add_rule (title, url);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_set_location (diagnostic *diag,
+			 const diagnostic_physical_location *loc)
+{
+  gcc_assert (diag);
+  diag->get_manager ().assert_valid_diagnostic_physical_location (loc);
+
+  diag->set_location (loc);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_location (diagnostic *diag,
+			 const diagnostic_physical_location *loc)
+{
+  gcc_assert (diag);
+  diag->get_manager ().assert_valid_diagnostic_physical_location (loc);
+
+  diag->add_location (loc);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_location_with_label (diagnostic *diag,
+				    const diagnostic_physical_location *loc,
+				    const char *text)
+{
+  gcc_assert (diag);
+  diag->get_manager ().assert_valid_diagnostic_physical_location (loc);
+  gcc_assert (text);
+
+  diag->add_location_with_label (loc, text);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_set_logical_location (diagnostic *diag,
+				 const diagnostic_logical_location *logical_loc)
+{
+  gcc_assert (diag);
+  gcc_assert (logical_loc);
+
+  diag->set_logical_location (logical_loc);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_fix_it_hint_insert_before (diagnostic *diag,
+					  const diagnostic_physical_location *loc,
+					  const char *addition)
+{
+  gcc_assert (diag);
+  diag->get_manager ().assert_valid_diagnostic_physical_location (loc);
+  gcc_assert (addition);
+
+  diag->get_manager ().set_line_table_global ();
+  diag->get_rich_location ()->add_fixit_insert_before (as_location_t (loc),
+						       addition);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_fix_it_hint_insert_after (diagnostic *diag,
+					 const diagnostic_physical_location *loc,
+					 const char *addition)
+{
+  gcc_assert (diag);
+  diag->get_manager ().assert_valid_diagnostic_physical_location (loc);
+  gcc_assert (addition);
+
+  diag->get_manager ().set_line_table_global ();
+  diag->get_rich_location ()->add_fixit_insert_after (as_location_t (loc),
+						      addition);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_fix_it_hint_replace (diagnostic *diag,
+				    const diagnostic_physical_location *loc,
+				    const char *replacement)
+{
+  gcc_assert (diag);
+  diag->get_manager ().assert_valid_diagnostic_physical_location (loc);
+  gcc_assert (replacement);
+
+  diag->get_manager ().set_line_table_global ();
+  diag->get_rich_location ()->add_fixit_replace (as_location_t (loc),
+						 replacement);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_add_fix_it_hint_delete (diagnostic *diag,
+				   const diagnostic_physical_location *loc)
+{
+  gcc_assert (diag);
+  diag->get_manager ().assert_valid_diagnostic_physical_location (loc);
+
+  diag->get_manager ().set_line_table_global ();
+  diag->get_rich_location ()->add_fixit_remove (as_location_t (loc));
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_finish (diagnostic *diag, const char *gmsgid, ...)
+{
+  gcc_assert (diag);
+
+  va_list args;
+  va_start (args, gmsgid);
+  diagnostic_finish_va (diag, gmsgid, &args);
+  va_end (args);
+}
+
+/* Public entrypoint.  */
+
+void
+diagnostic_finish_va (diagnostic *diag, const char *gmsgid, va_list *args)
+{
+  gcc_assert (diag);
+
+  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 (*diag, gmsgid, args);
+  delete diag;
+}
diff --git a/gcc/libdiagnostics.map b/gcc/libdiagnostics.map
new file mode 100644
index 000000000000..2a7666859fcb
--- /dev/null
+++ b/gcc/libdiagnostics.map
@@ -0,0 +1,63 @@
+# Linker script for libdiagnostics.so
+#   Copyright (C) 2023 Free Software Foundation, Inc.
+#   Contributed by David Malcolm <dmalcolm@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/>.  */
+
+# The initial release of the library.
+LIBDIAGNOSTICS_ABI_0
+{
+  global:
+    # Keep this list in order of decls in header file.
+    diagnostic_manager_new;
+    diagnostic_manager_release;
+    diagnostic_manager_set_tool_name;
+    diagnostic_manager_set_full_name;
+    diagnostic_manager_set_version_string;
+    diagnostic_manager_set_version_url;
+    diagnostic_manager_add_text_sink;
+    diagnostic_text_sink_set_source_printing_enabled;
+    diagnostic_text_sink_set_labelled_source_colorization_enabled;
+    diagnostic_manager_add_sarif_sink;
+    diagnostic_manager_write_patch;
+    diagnostic_manager_new_file;
+    diagnostic_manager_debug_dump_file;
+    diagnostic_manager_new_location_from_file_and_line;
+    diagnostic_manager_new_location_from_file_line_column;
+    diagnostic_manager_new_location_from_range;
+    diagnostic_manager_debug_dump_location;
+    diagnostic_manager_new_logical_location;
+    diagnostic_manager_debug_dump_logical_location;
+    diagnostic_manager_begin_group;
+    diagnostic_manager_end_group;
+    diagnostic_begin;
+    diagnostic_set_cwe;
+    diagnostic_add_rule;
+    diagnostic_set_location;
+    diagnostic_set_location_with_label;
+    diagnostic_add_location;
+    diagnostic_add_location_with_label;
+    diagnostic_set_logical_location;
+    diagnostic_add_fix_it_hint_insert_before;
+    diagnostic_add_fix_it_hint_insert_after;
+    diagnostic_add_fix_it_hint_replace;
+    diagnostic_add_fix_it_hint_delete;
+    diagnostic_finish;
+    diagnostic_finish_va;
+
+  local: *;
+};
diff --git a/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp b/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp
new file mode 100644
index 000000000000..bd50a5568a73
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp
@@ -0,0 +1,544 @@
+# Test code for libdiagnostics.so
+#
+# FIXME:
+#
+# Test code for libdiagnostics.so
+# We will compile each of jit.dg/test-*.c into an executable
+# dynamically linked against libgccjit.so, and then run each
+# such executable.
+#
+# These executables call into the libgccjit.so API to create
+# code, compile it, and run it, verifying that the results
+# are as expected.  See harness.h for shared code used by all
+# such executables.
+#
+# The executables call into DejaGnu's unit testing C API to
+# report PASS/FAIL results, which this script gathers back
+# up into the Tcl world, reporting a summary of all results
+# across all of the executables.
+
+# Kludge alert:
+# We need g++_init so that it can find the stdlib include path.
+#
+# g++_init (in lib/g++.exp) uses g++_maybe_build_wrapper,
+# which normally comes from the definition of
+# ${tool}_maybe_build_wrapper within lib/wrapper.exp.
+#
+# However, for us, ${tool} is "jit".
+# Hence we load wrapper.exp with tool == "g++", so that
+# g++_maybe_build_wrapper is defined.
+set tool g++
+load_lib wrapper.exp
+set tool diagnostics
+
+load_lib dg.exp
+load_lib prune.exp
+load_lib target-supports.exp
+load_lib gcc-defs.exp
+load_lib timeout.exp
+load_lib target-libpath.exp
+load_lib gcc.exp
+load_lib g++.exp
+load_lib dejagnu.exp
+load_lib target-supports-dg.exp
+
+# # Skip these tests for targets that don't support -ldiagnostics
+# if { ![check_effective_target_ldiagnostics] } {
+#     return
+# }
+
+# The default do-what keyword.
+set dg-do-what-default compile
+
+# Look for lines of the form:
+#   definitely lost: 11,316 bytes in 235 blocks
+#   indirectly lost: 352 bytes in 4 blocks
+# Ideally these would report zero bytes lost (which is a PASS);
+# for now, report non-zero leaks as XFAILs.
+proc report_leak {kind name logfile line} {
+    set match [regexp "$kind lost: .*" $line result]
+    if $match {
+	verbose "Saw \"$result\" within \"$line\"" 4
+	# Extract bytes and blocks.
+	# These can contain commas as well as numerals,
+	# but we only care about whether we have zero.
+	regexp "$kind lost: (.+) bytes in (.+) blocks" \
+	    $result -> bytes blocks
+	verbose "bytes: '$bytes'" 4
+	verbose "blocks: '$blocks'" 4
+	if { $bytes == 0 } {
+	    pass "$name: $logfile: $result"
+	} else {
+	    xfail "$name: $logfile: $result"
+	}
+    }
+}
+
+proc parse_valgrind_logfile {name logfile} {
+    verbose "parse_valgrind_logfile: $logfile" 2
+    if [catch {set f [open $logfile]}] {
+	fail "$name: unable to read $logfile"
+	return
+    }
+
+    while { [gets $f line] >= 0 } {
+	# Strip off the PID prefix e.g. ==7675==
+	set line [regsub "==\[0-9\]*== " $line ""]
+	verbose $line 2
+
+	report_leak "definitely" $name $logfile $line
+	report_leak "indirectly" $name $logfile $line
+    }
+    close $f
+}
+
+# Given WRES, the result from "wait", issue a PASS
+# if the spawnee exited cleanly, or a FAIL for various kinds of
+# unexpected exits.
+
+proc verify_exit_status { executable wres } {
+    lassign $wres pid spawnid os_error_flag value
+    verbose "pid: $pid" 3
+    verbose "spawnid: $spawnid" 3
+    verbose "os_error_flag: $os_error_flag" 3
+    verbose "value: $value" 3
+
+    # Detect segfaults etc:
+    if { [llength $wres] > 4 } {
+	if { [lindex $wres 4] == "CHILDKILLED" } {
+	    fail "$executable killed: $wres"
+	    return
+	}
+    }
+    if { $os_error_flag != 0 } {
+	fail "$executable: OS error: $wres"
+	return
+    }
+    if { $value != 0 } {
+	fail "$executable: non-zero exit code: $wres"
+	return
+    }
+    pass "$executable exited cleanly"
+}
+
+# This is host_execute from dejagnu.exp commit
+#   126a089777158a7891ff975473939f08c0e31a1c
+# with the following patch applied, and renaming to "fixed_host_execute".
+# See the discussion at
+#  http://lists.gnu.org/archive/html/dejagnu/2014-10/msg00000.html
+#
+#  --- /usr/share/dejagnu/dejagnu.exp.old  2014-10-08 13:38:57.274068541 -0400
+#  +++ /usr/share/dejagnu/dejagnu.exp      2014-10-10 12:27:51.113813659 -0400
+#  @@ -113,8 +113,6 @@ proc host_execute {args} {
+#       set timetol 0
+#       set arguments ""
+#   
+#  -    expect_before buffer_full { perror "Buffer full" }
+#  -
+#       if { [llength $args] == 0} {
+#          set executable $args
+#       } else {
+
+
+# Execute the executable file, and anaylyse the output for the
+# test state keywords.
+#    Returns:
+#	A "" (empty) string if everything worked, or an error message
+#	if there was a problem.
+#
+proc fixed_host_execute {args} {
+    global env
+    global text
+    global spawn_id
+
+    verbose "fixed_host_execute: $args"
+
+    set timeoutmsg "Timed out: Never got started, "
+    set timeout 100
+    set file all
+    set timetol 0
+    set arguments ""
+
+    if { [llength $args] == 0} {
+	set executable $args
+    } else {
+	set executable [lindex $args 0]
+	set params [lindex $args 1]
+    }
+
+    verbose "The executable is $executable" 2
+    if {![file exists ${executable}]} {
+	perror "The executable, \"$executable\" is missing" 0
+	return "No source file found"
+    } elseif {![file executable ${executable}]} {
+	perror "The executable, \"$executable\" is not usable" 0
+	return "Bad executable found"
+    }
+
+    verbose "params: $params" 2
+
+    # spawn the executable and look for the DejaGnu output messages from the
+    # test case.
+    # spawn -noecho -open [open "|./${executable}" "r"]
+
+    # Run under valgrind if RUN_UNDER_VALGRIND is present in the environment.
+    # Note that it's best to configure gcc with --enable-valgrind-annotations
+    # when testing under valgrind.
+    set run_under_valgrind [info exists env(RUN_UNDER_VALGRIND)]
+    if $run_under_valgrind {
+	set valgrind_logfile "${executable}.valgrind.txt"
+	set valgrind_params {"valgrind"}
+	lappend valgrind_params "--leak-check=full"
+	lappend valgrind_params "--log-file=${valgrind_logfile}"
+    } else {
+	set valgrind_params {}
+    }
+    verbose "valgrind_params: $valgrind_params" 2
+
+    set args ${valgrind_params}
+    lappend args "./${executable}"
+    set args [concat $args ${params}]
+    verbose "args: $args" 2
+
+    # We checked that the executable exists above, and can be executed, but
+    # that does not cover other reasons that the launch could fail (e.g.
+    # missing or malformed params); catch such cases here and report them.
+    set err [catch "spawn -noecho $args" pid]
+    set sub_proc_id $spawn_id
+    if { $pid <= 0 || $err != 0 || $sub_proc_id < 0 } {
+        warning "failed to spawn : $args : err = $err"
+    }
+
+    # Increase the buffer size, if needed to avoid spurious buffer-full
+    # events; GCC uses 10000; chose a power of two here.
+    set current_max_match [match_max -i $sub_proc_id]
+    if { $current_max_match < 8192 } {
+        match_max -i $sub_proc_id 8192
+        set used [match_max -i $sub_proc_id]
+    }
+
+    # If we get a buffer-full error, that seems to be unrecoverable so try to
+    # exit in a reasonable manner to avoid wedged processes.
+    expect_after full_buffer {
+        verbose -log "fixed_host_execute: $args FULL BUFFER"
+        # FIXME: this all assumes that closing the connection will cause the
+        # sub-process to terminate (but that is not going to be the case if,
+        # for example, there is something started with -nohup somewhere).
+        # We should explicitly kill it here.
+        # Set the process to be a nowait exit.
+        wait -nowait -i $sub_proc_id
+        catch close
+        perror "${executable} got full_buffer"
+        return "${executable} got full_buffer"
+    }
+
+    set prefix "\[^\r\n\]*"
+    # Work around a Darwin tcl or termios bug that sometimes inserts extra
+    # CR characters into the cooked tty stream
+    set endline "\r\n"
+    if { [istarget *-*-darwin*] } {
+        set endline "\r(\r)*\n"
+    }
+    
+    # Note that the logic here assumes that we cannot (validly) get single
+    # carriage return or line feed characters in the stream.  If those occur,
+    # it will stop any further matching.  We arange for the matching to be
+    # at the start of the buffer - so that if there is any spurious output
+    # to be discarded, it must be done explicitly - not by matching part-way
+    # through the buffer.
+    expect {
+	-re "^$prefix\[0-9\]\[0-9\]:..:..:${text}*$endline" {
+	    regsub "\[\n\r\t\]*NOTE: $text\r\n" $expect_out(0,string) "" output
+	    verbose "$output" 3
+	    set timetol 0
+	    exp_continue
+	}
+	-re "^\tNOTE: (\[^\r\n\]+)$endline" {
+	    # discard notes.
+	    verbose "Ignored note: $expect_out(1,string)" 2
+	    set timetol 0
+	    exp_continue
+	}
+	-re "^\tPASSED: (\[^\r\n\]+)$endline" {
+	    pass "$expect_out(1,string)"
+	    set timetol 0
+	    exp_continue
+	}
+	-re "^\tFAILED: (\[^\r\n\]+)$endline" {
+	    fail "$expect_out(1,string)"
+	    set timetol 0
+	    exp_continue
+	}
+	-re "^\tUNTESTED: (\[^\r\n\]+)$endline" {
+	    untested "$expect_out(1,string)"
+	    set timetol 0
+	    exp_continue
+	}
+	-re "^\tUNRESOLVED: (\[^\r\n\]+)$endline" {
+	    unresolved "$expect_out(1,string)"
+	    set timetol 0
+	    exp_continue
+	}
+	-re "^$prefix$endline" {
+            # This matches and discards any other lines (including blank ones).
+            if { [string length $expect_out(buffer)] <= 2 } {
+                set output "blank line"
+            } else {
+	        set output [string range $expect_out(buffer) 0 end-2]
+	    }
+	    verbose -log "DISCARDED $expect_out(spawn_id) : $output"
+	    exp_continue
+	}
+	eof {
+	    # This seems to be the only way that we can reliably know that the
+	    # output is finished since there are cases where further output
+	    # follows the dejagnu test harness totals.
+	    verbose "saw eof" 2
+	}
+	timeout {
+	    if { $timetol <= 2 } {
+	        verbose -log "Timed out with retry (timeout = $timeout)"
+		incr timetol
+		exp_continue
+	    } else {
+	        warning "Timed out executing testcase (timeout = $timeout)"
+		catch close
+		return "Timed out executing test case"
+	    }
+	}
+    }
+
+    # Use "wait" to pick up the sub-process exit state.  If the sub-process is
+    # writing to a file (perhaps under valgrind) then that also needs to be
+    # complete; only attempt this on a valid spawn.
+    if { $sub_proc_id > 0 } {
+        verbose "waiting for $sub_proc_id" 1
+        # Be explicit about what we are waiting for.
+        catch "wait -i $sub_proc_id" wres
+        verbose "wres: $wres" 2
+        verify_exit_status $executable $wres
+    }
+ 
+    if $run_under_valgrind {
+	upvar 2 name name
+	parse_valgrind_logfile $name $valgrind_logfile
+    }
+
+    # force a close of the executable to be safe.
+    catch close
+
+    return ""
+}
+
+# (end of code from dejagnu.exp)
+
+# GCC_UNDER_TEST is needed by gcc_target_compile
+global GCC_UNDER_TEST
+if ![info exists GCC_UNDER_TEST] {
+    set GCC_UNDER_TEST "[find_gcc]"
+}
+
+g++_init
+
+# Initialize dg.
+dg-init
+
+# Gather a list of all tests.
+
+# C tests within the testsuite: gcc/testsuite/libdiagnostics.dg/test-*.c
+set tests [find $srcdir/$subdir test-*.c]
+
+set tests [lsort $tests]
+
+verbose "tests: $tests"
+
+# libgloss has found the driver (as "xgcc" or "gcc) and stored
+# its full path as GCC_UNDER_TEST.
+proc get_path_of_driver {} {
+    global GCC_UNDER_TEST
+
+    verbose "GCC_UNDER_TEST: $GCC_UNDER_TEST"
+    set binary [lindex $GCC_UNDER_TEST 0]
+    verbose "binary: $binary"
+
+    return [file dirname $binary]
+}
+
+# Expand "SRCDIR" within ARG to the location of the top-level
+# src directory
+
+proc diagnostics-expand-vars {arg} {
+    verbose "diagnostics-expand-vars: $arg"
+    global srcdir
+    verbose " srcdir: $srcdir"
+    # "srcdir" is that of the gcc/testsuite directory, so
+    # we need to go up two levels.
+    set arg [string map [list "SRCDIR" $srcdir/../..] $arg]
+    verbose " new arg: $arg"
+    return $arg
+}
+
+# Parameters used when invoking the executables built from the test cases.
+
+global diagnostics-exe-params
+set diagnostics-exe-params {}
+
+# Set "diagnostics-exe-params", expanding "SRCDIR" in each arg to the location of
+# the top-level srcdir.
+
+proc dg-diagnostics-set-exe-params { args } {
+    verbose "dg-diagnostics-set-exe-params: $args"
+
+    global diagnostics-exe-params
+    set diagnostics-exe-params {}
+    # Skip initial arg (line number)
+    foreach arg [lrange $args 1 [llength $args] ] {
+	lappend diagnostics-exe-params [diagnostics-expand-vars $arg]
+    }
+}
+
+proc diagnostics-dg-test { prog do_what extra_tool_flags } {
+    verbose "within diagnostics-dg-test..."
+    verbose "  prog: $prog"
+    verbose "  do_what: $do_what"
+    verbose "  extra_tool_flags: $extra_tool_flags"
+
+    global dg-do-what-default
+    set dg-do-what [list ${dg-do-what-default} "" P]
+
+    set tmp [dg-get-options $prog]
+    foreach op $tmp {
+	verbose "Processing option: $op" 3
+	set status [catch "$op" errmsg]
+	if { $status != 0 } {
+	    if { 0 && [info exists errorInfo] } {
+		# This also prints a backtrace which will just confuse
+		# testcase writers, so it's disabled.
+		perror "$name: $errorInfo\n"
+	    } else {
+		perror "$name: $errmsg for \"$op\"\n"
+	    }
+	    perror "$name: $errmsg for \"$op\"" 0
+	    return
+	}
+    }
+
+    # If we're not supposed to try this test on this target, we're done.
+    if { [lindex ${dg-do-what} 1] == "N" } {
+	unsupported "$name"
+	verbose "$name not supported on this target, skipping it" 3
+	return
+    }
+
+    # Determine what to name the built executable.
+    #
+    # We simply append .exe to the filename, e.g.
+    #  "test-foo.c.exe"
+    # since some testcases exist in both
+    #  "test-foo.c" and
+    #  "test-foo.cc"
+    # variants, and we don't want them to clobber each other's
+    # executables.
+    #
+    # This also ensures that the source name makes it into the
+    # pass/fail output, so that we can distinguish e.g. which test-foo
+    # is failing.
+    set output_file "[file tail $prog].exe"
+    verbose "output_file: $output_file"
+
+    # Create the test executable:
+    set extension [file extension $prog]
+    if {$extension == ".cc"} {
+	set compilation_function "g++_target_compile"
+	set options "{additional_flags=$extra_tool_flags}"
+    } else {
+	set compilation_function "gcc_target_compile"
+	# Until recently, <dejagnu.h> assumed -fgnu89-inline
+	# Ideally we should fixincludes it (PR other/63613), but
+	# for now add -fgnu89-inline when compiling C JIT testcases.
+	# See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63613
+	# and http://lists.gnu.org/archive/html/dejagnu/2014-10/msg00011.html
+	set options "{additional_flags=$extra_tool_flags -fgnu89-inline}"
+    }
+    verbose "compilation_function=$compilation_function"
+    verbose "options=$options"
+
+    set comp_output [$compilation_function $prog $output_file \
+			 "executable" $options]
+    upvar 1 name name
+    if ![diagnostics_check_compile "$name" "initial compilation" \
+	    $output_file $comp_output] then {
+      return
+    }
+
+    # Run the test executable, capturing the PASS/FAIL textual output
+    # from the C API, converting it into the Tcl API.
+
+    # We need to set LD_LIBRARY_PATH so that the test files can find
+    # libdiagnostics.so
+    # Do this using set_ld_library_path_env_vars from target-libpath.exp
+    # We will restore the old value later using
+    # restore_ld_library_path_env_vars.
+
+    # Unfortunately this API only supports a single saved value, rather
+    # than a stack, and g++_init has already called into this API,
+    # injecting the appropriate value for LD_LIBRARY_PATH for finding
+    # the built copy of libstdc++.
+    # Hence the call to restore_ld_library_path_env_vars would restore
+    # the *initial* value of LD_LIBRARY_PATH, and attempts to run
+    # a C++ testcase after running any prior testcases would thus look
+    # in the wrong place for libstdc++.  This led to failures at startup
+    # of the form:
+    #   ./tut01-hello-world.cc.exe: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by ./tut01-hello-world.cc.exe)
+    # when the built libstdc++ is more recent that the system libstdc++.
+    #
+    # As a workaround, reset the variable "orig_environment_saved" within
+    # target-libpath.exp, so that the {set|restore}_ld_library_path_env_vars
+    # API saves/restores the current value of LD_LIBRARY_PATH (as set up
+    # by g++_init).
+    global orig_environment_saved
+    set orig_environment_saved 0
+
+    global ld_library_path
+    global base_dir
+    set ld_library_path "$base_dir/../../"
+    set_ld_library_path_env_vars
+
+    # dejagnu.exp's host_execute has code to scrape out test results
+    # from the DejaGnu C API and bring back into the tcl world, so we
+    # use that to invoke the built code.
+    # However, it appears to be buggy; see:
+    #  http://lists.gnu.org/archive/html/dejagnu/2014-10/msg00000.html
+    # We instead call a patched local copy, "fixed_host_execute", defined
+    # above.
+
+    global diagnostics-exe-params
+    set args ${diagnostics-exe-params}
+    set diagnostics-exe-params {}
+
+    set result [fixed_host_execute $output_file $args ]
+    verbose "result: $result"
+
+    restore_ld_library_path_env_vars
+
+    # Normally we would return $comp_output and $output_file to the
+    # caller, which would delete $output_file, the generated executable.
+    # If we need to debug, it's handy to be able to suppress this behavior,
+    # keeping the executable around.
+    
+    global env
+    set preserve_executables [info exists env(PRESERVE_EXECUTABLES)]
+    if $preserve_executables {
+	set output_file ""
+    }
+    
+    return [list $comp_output $output_file]
+}
+
+set DEFAULT_CFLAGS "-I$srcdir/.. -ldiagnostics -g -Wall -Werror"
+
+# Main loop.  This will invoke jig-dg-test on each test-*.c file.
+dg-runtest $tests "" $DEFAULT_CFLAGS
+
+# All done.
+dg-finish
-- 
2.26.3


^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH 3/5] libdiagnostics v2: add C++ wrapper API
  2023-11-21 22:20 ` [PATCH 0/6] v2 of libdiagnostics David Malcolm
  2023-11-21 22:20   ` [PATCH 1/5] libdiagnostics v2: header and examples David Malcolm
  2023-11-21 22:20   ` [PATCH 2/5] libdiagnostics v2: work-in-progress implementation David Malcolm
@ 2023-11-21 22:20   ` David Malcolm
  2023-11-21 22:20   ` [PATCH 4/5] diagnostics: add diagnostic_context::get_location_text David Malcolm
                     ` (4 subsequent siblings)
  7 siblings, 0 replies; 29+ messages in thread
From: David Malcolm @ 2023-11-21 22:20 UTC (permalink / raw)
  To: gcc-patches, binutils; +Cc: Nick Clifton, Simon Sobisch, David Malcolm

This is new in v2: a C++ wrapper API that provides some syntactic sugar for
calling into libdiagnostics.{h,so}.

I've been "eating my own dogfood" with this by using it to write a simple
client that reads a SARIF file and dumps it using the text sink:
  https://github.com/davidmalcolm/libdiagnostics-sarif-dump

gcc/ChangeLog:
	* libdiagnostics++.h: New file.

gcc/testsuite/ChangeLog:
	* libdiagnostics.dg/libdiagnostics.exp: Add .cc tests.
	* libdiagnostics.dg/test-error-with-note.cc: New test.
	* libdiagnostics.dg/test-error.cc: New test.
	* libdiagnostics.dg/test-fix-it-hint.cc: New test.
	* libdiagnostics.dg/test-helpers++.h: New header.
	* libdiagnostics.dg/test-labelled-ranges.cc: New test.
---
 gcc/libdiagnostics++.h                        | 378 ++++++++++++++++++
 .../libdiagnostics.dg/libdiagnostics.exp      |   8 +-
 .../libdiagnostics.dg/test-error-with-note.cc |  47 +++
 gcc/testsuite/libdiagnostics.dg/test-error.cc |  40 ++
 .../libdiagnostics.dg/test-fix-it-hint.cc     |  44 ++
 .../libdiagnostics.dg/test-helpers++.h        |  28 ++
 .../libdiagnostics.dg/test-labelled-ranges.cc |  43 ++
 7 files changed, 584 insertions(+), 4 deletions(-)
 create mode 100644 gcc/libdiagnostics++.h
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error-with-note.cc
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error.cc
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.cc
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-helpers++.h
 create mode 100644 gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.cc

diff --git a/gcc/libdiagnostics++.h b/gcc/libdiagnostics++.h
new file mode 100644
index 000000000000..8f412b07aa78
--- /dev/null
+++ b/gcc/libdiagnostics++.h
@@ -0,0 +1,378 @@
+/* A C++ wrapper API around libdiagnostics.h for emitting diagnostics.
+   Copyright (C) 2023 Free Software Foundation, Inc.
+
+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 LIBDIAGNOSTICSPP_H
+#define LIBDIAGNOSTICSPP_H
+
+#include "libdiagnostics.h"
+
+namespace libdiagnostics {
+
+typedef diagnostic_line_num_t line_num_t;
+typedef diagnostic_column_num_t column_num_t;
+
+class file;
+class physical_location;
+class logical_location;
+class group;
+class manager;
+class diagnostic;
+
+/* Wrapper around a const diagnostic_file *.  */
+
+class file
+{
+public:
+  file (const diagnostic_file *file) : m_inner (file) {}
+
+  const diagnostic_file * const m_inner;
+};
+
+/* Wrapper around a const diagnostic_physical_location *.  */
+
+class physical_location
+{
+public:
+  physical_location (const diagnostic_physical_location *location)
+  : m_inner (location)
+  {}
+
+  const diagnostic_physical_location *m_inner;
+};
+
+/* Wrapper around a const diagnostic_logical_location *.  */
+
+class logical_location
+{
+public:
+  logical_location () : m_inner (nullptr) {}
+
+  logical_location (const diagnostic_logical_location *logical_loc)
+  : m_inner (logical_loc)
+  {}
+
+  const diagnostic_logical_location * const m_inner;
+};
+
+/* RAII class for starting/ending a group within a diagnostic_manager.  */
+
+class group
+{
+public:
+  group (manager &mgr);
+  ~group ();
+
+private:
+  manager &m_mgr;
+};
+
+/* Wrapper around a diagnostic *.  */
+
+class diagnostic
+{
+public:
+  diagnostic (::diagnostic *d) : m_inner (d) {}
+
+  void
+  set_cwe (unsigned cwe_id);
+
+  void
+  set_location (physical_location loc);
+
+  void
+  add_location_with_label (physical_location loc,
+			   const char *text);
+
+  void
+  set_logical_location (logical_location loc);
+
+  void
+  add_fix_it_hint_insert_before (physical_location loc,
+				 const char *addition);
+  void
+  add_fix_it_hint_insert_after (physical_location loc,
+				const char *addition);
+  void
+  add_fix_it_hint_replace (physical_location loc,
+			   const char *replacement);
+  void
+  add_fix_it_hint_delete (physical_location loc);
+
+  void
+  finish (const char *fmt, ...)
+    LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2)
+    LIBDIAGNOSTICS_PARAM_GCC_FORMAT_STRING (2, 3);
+
+  ::diagnostic * const m_inner;
+};
+
+/* Wrapper around ownership of a diagnostic_manager *.  */
+
+class manager
+{
+public:
+  manager ()
+  : m_inner (diagnostic_manager_new ())
+  {
+  }
+  ~manager ()
+  {
+    diagnostic_manager_release (m_inner);
+  }
+
+  void
+  add_text_sink (FILE *dst_stream,
+		 enum diagnostic_colorize colorize)
+  {
+    diagnostic_manager_add_text_sink (m_inner, dst_stream, colorize);
+  }
+
+  void
+  add_sarif_sink (FILE *dst_stream,
+		  enum diagnostic_sarif_version version)
+  {
+    diagnostic_manager_add_sarif_sink (m_inner, dst_stream, version);
+  }
+
+  void
+  write_patch (FILE *dst_stream)
+  {
+    diagnostic_manager_write_patch (m_inner, dst_stream);
+  }
+
+  /* Location management.  */
+
+  file
+  new_file (const char *name,
+	    const char *sarif_source_language)
+    LIBDIAGNOSTICS_PARAM_MUST_BE_NON_NULL (2)
+    LIBDIAGNOSTICS_PARAM_CAN_BE_NULL (3);
+
+  void
+  debug_dump (file f,
+	      FILE *out);
+
+  physical_location
+  new_location_from_file_and_line (file f, diagnostic_line_num_t line_num);
+
+  physical_location
+  new_location_from_file_line_column (file f,
+				      line_num_t line_num,
+				      column_num_t column_num);
+
+  physical_location
+  new_location_from_range (physical_location loc_caret,
+			   physical_location loc_start,
+			   physical_location loc_end);
+
+  void
+  debug_dump (physical_location loc,
+	      FILE *out);
+
+  logical_location
+  new_logical_location (enum diagnostic_logical_location_kind_t kind,
+			logical_location parent,
+			const char *short_name,
+			const char *fully_qualified_name,
+			const char *decorated_name);
+
+  void
+  debug_dump (logical_location loc,
+	      FILE *out);
+
+  diagnostic
+  begin_diagnostic (enum diagnostic_level level);
+
+
+  diagnostic_manager * const m_inner;
+};
+
+// Implementation
+
+// class group
+
+inline
+group::group (manager &mgr)
+: m_mgr (mgr)
+{
+  diagnostic_manager_begin_group (m_mgr.m_inner);
+}
+
+inline
+group::~group ()
+{
+  diagnostic_manager_end_group (m_mgr.m_inner);
+}
+
+// class diagnostic
+
+inline void
+diagnostic::set_cwe (unsigned cwe_id)
+{
+  diagnostic_set_cwe (m_inner, cwe_id);
+}
+
+inline void
+diagnostic::set_location (physical_location loc)
+{
+  diagnostic_set_location (m_inner, loc.m_inner);
+}
+
+inline void
+diagnostic::add_location_with_label (physical_location loc,
+				     const char *text)
+{
+  diagnostic_add_location_with_label (m_inner, loc.m_inner, text);
+}
+
+inline void
+diagnostic::set_logical_location (logical_location loc)
+{
+  diagnostic_set_logical_location (m_inner, loc.m_inner);
+}
+
+inline void
+diagnostic::add_fix_it_hint_insert_before (physical_location loc,
+					   const char *addition)
+{
+  diagnostic_add_fix_it_hint_insert_before (m_inner,
+					    loc.m_inner,
+					    addition);
+}
+
+inline void
+diagnostic::add_fix_it_hint_insert_after (physical_location loc,
+				const char *addition)
+{
+  diagnostic_add_fix_it_hint_insert_after (m_inner,
+					   loc.m_inner,
+					   addition);
+}
+
+inline void
+diagnostic::add_fix_it_hint_replace (physical_location loc,
+				     const char *replacement)
+{
+  diagnostic_add_fix_it_hint_replace (m_inner,
+				      loc.m_inner,
+				      replacement);
+}
+
+inline void
+diagnostic::add_fix_it_hint_delete (physical_location loc)
+{
+  diagnostic_add_fix_it_hint_delete (m_inner,
+				     loc.m_inner);
+}
+
+inline void
+diagnostic::finish (const char *fmt, ...)
+{
+  va_list ap;
+  va_start (ap, fmt);
+  diagnostic_finish_va (m_inner, fmt, &ap);
+  va_end (ap);
+}
+
+// class manager
+
+inline file
+manager::new_file (const char *name,
+		   const char *sarif_source_language)
+{
+  return file
+    (diagnostic_manager_new_file (m_inner, name, sarif_source_language));
+}
+
+inline physical_location
+manager::new_location_from_file_and_line (file f,
+					  diagnostic_line_num_t line_num)
+{
+  return physical_location
+    (diagnostic_manager_new_location_from_file_and_line (m_inner,
+							 f.m_inner,
+							 line_num));
+}
+
+inline physical_location
+manager::new_location_from_file_line_column (file f,
+					     line_num_t line_num,
+					     column_num_t column_num)
+{
+  return physical_location
+    (diagnostic_manager_new_location_from_file_line_column (m_inner,
+							    f.m_inner,
+							    line_num,
+							    column_num));
+}
+
+inline physical_location
+manager::new_location_from_range (physical_location loc_caret,
+				  physical_location loc_start,
+				  physical_location loc_end)
+{
+  return physical_location
+    (diagnostic_manager_new_location_from_range (m_inner,
+						 loc_caret.m_inner,
+						 loc_start.m_inner,
+						 loc_end.m_inner));
+}
+
+inline void
+manager::debug_dump (physical_location loc,
+		     FILE *out)
+{
+  diagnostic_manager_debug_dump_location (m_inner,
+					  loc.m_inner,
+					  out);
+}
+inline logical_location
+manager::new_logical_location (enum diagnostic_logical_location_kind_t kind,
+			       logical_location parent,
+			       const char *short_name,
+			       const char *fully_qualified_name,
+			       const char *decorated_name)
+{
+  return logical_location
+    (diagnostic_manager_new_logical_location (m_inner,
+					      kind,
+					      parent.m_inner,
+					      short_name,
+					      fully_qualified_name,
+					      decorated_name));
+}
+
+inline void
+manager::debug_dump (logical_location loc,
+		     FILE *out)
+{
+  diagnostic_manager_debug_dump_logical_location (m_inner,
+						  loc.m_inner,
+						  out);
+}
+
+inline diagnostic
+manager::begin_diagnostic (enum diagnostic_level level)
+{
+  return diagnostic (diagnostic_begin (m_inner, level));
+}
+
+} // namespace libdiagnostics
+
+#endif // #ifndef LIBDIAGNOSTICSPP_H
diff --git a/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp b/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp
index bd50a5568a73..bcceb669d3ff 100644
--- a/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp
+++ b/gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp
@@ -345,10 +345,10 @@ dg-init
 
 # Gather a list of all tests.
 
-# C tests within the testsuite: gcc/testsuite/libdiagnostics.dg/test-*.c
-set tests [find $srcdir/$subdir test-*.c]
-
-set tests [lsort $tests]
+# C and C++ tests within the testsuite: gcc/testsuite/libdiagnostics.dg/test-*.{c,c++}
+set c_tests [find $srcdir/$subdir test-*.c]
+set cxx_tests [find $srcdir/$subdir test-*.cc]
+set tests [concat $c_tests $cxx_tests]
 
 verbose "tests: $tests"
 
diff --git a/gcc/testsuite/libdiagnostics.dg/test-error-with-note.cc b/gcc/testsuite/libdiagnostics.dg/test-error-with-note.cc
new file mode 100644
index 000000000000..e20b9e04d6b1
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-error-with-note.cc
@@ -0,0 +1,47 @@
+/* C++ example of emitting an error with an associated note.
+
+   Intended output is similar to:
+
+PATH/test-error-with-note.c:6: error: can't find 'foo'
+    6 | PRINT "hello world!";
+      |        ^~~~~~~~~~~~
+PATH/test-error-with-note.c:6: note: have you looked behind the couch?
+
+   along with the equivalent in SARIF.  */
+
+#include "libdiagnostics++.h"
+
+/*
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  libdiagnostics::manager mgr;
+
+  mgr.add_text_sink (stderr, DIAGNOSTIC_COLORIZE_IF_TTY);
+  mgr.add_sarif_sink (stderr, DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  auto file = mgr.new_file (__FILE__, "c");
+  auto loc_start = mgr.new_location_from_file_line_column (file, line_num, 8);
+  auto loc_end = mgr.new_location_from_file_line_column (file, line_num, 19);
+  auto loc_range = mgr.new_location_from_range (loc_start,
+						loc_start,
+						loc_end);
+
+  libdiagnostics::group g (mgr);
+  
+  auto err (mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR));
+  err.set_location (loc_range);
+  err.finish ("can't find %qs", "foo");
+
+  auto note = mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_NOTE);
+  note.set_location (loc_range);
+  note.finish ("have you looked behind the couch?");
+
+  return 0;
+};
diff --git a/gcc/testsuite/libdiagnostics.dg/test-error.cc b/gcc/testsuite/libdiagnostics.dg/test-error.cc
new file mode 100644
index 000000000000..019307815bfb
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-error.cc
@@ -0,0 +1,40 @@
+/* C++ example of emitting an error.
+
+   Intended output is similar to:
+
+PATH/test-error.cc:6: error: can't find 'foo'
+    6 | PRINT "hello world!";
+      |        ^~~~~~~~~~~~
+
+   along with the equivalent in SARIF.  */
+
+#include "libdiagnostics++.h"
+
+/*
+_________111111111122
+123456789012345678901
+PRINT "hello world!";
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  libdiagnostics::manager mgr;
+
+  mgr.add_text_sink (stderr, DIAGNOSTIC_COLORIZE_IF_TTY);
+  mgr.add_sarif_sink (stderr, DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  auto file = mgr.new_file (__FILE__, "c");
+  auto loc_start = mgr.new_location_from_file_line_column (file, line_num, 8);
+  auto loc_end = mgr.new_location_from_file_line_column (file, line_num, 19);
+  auto loc_range = mgr.new_location_from_range (loc_start,
+						loc_start,
+						loc_end);
+
+  libdiagnostics::diagnostic d (mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR));
+  d.set_location (loc_range);
+  d.finish ("can't find %qs", "foo");
+
+  return 0;
+};
diff --git a/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.cc b/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.cc
new file mode 100644
index 000000000000..6af2641f2804
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.cc
@@ -0,0 +1,44 @@
+/* C++ example of a fix-it hint, including patch generation.
+
+   Intended output is similar to:
+
+PATH/test-fix-it-hint.cc:19: error: unknown field 'colour'; did you mean 'color'
+   19 |   return p->colour;
+      |             ^~~~~~
+      |             color
+
+   along with the equivalent in SARIF, and a generated patch (on stderr) to
+   make the change.  */
+
+#include "libdiagnostics++.h"
+#include "test-helpers++.h"
+
+/*
+_________11111111112
+12345678901234567890
+  return p->colour;
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  libdiagnostics::manager mgr;
+
+  mgr.add_text_sink (stderr, DIAGNOSTIC_COLORIZE_IF_TTY);
+  mgr.add_sarif_sink (stderr, DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  auto file = mgr.new_file (__FILE__, "c");
+  auto loc_token = make_range (mgr, file, line_num, 13, 18);
+
+  auto d = mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR);
+  d.set_location (loc_token);
+
+  d.add_fix_it_hint_replace (loc_token, "color");
+  
+  d.finish ("unknown field %qs; did you mean %qs", "colour", "color");
+
+  mgr.write_patch (stderr);
+
+  return 0;
+}
diff --git a/gcc/testsuite/libdiagnostics.dg/test-helpers++.h b/gcc/testsuite/libdiagnostics.dg/test-helpers++.h
new file mode 100644
index 000000000000..c8ff2def1ffa
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-helpers++.h
@@ -0,0 +1,28 @@
+/* Common utility code shared between test cases.  */
+
+#ifndef TEST_HELPERSPP_H
+#define TEST_HELPERSPP_H
+
+namespace libdiagnostics {
+
+inline physical_location
+make_range (manager &mgr,
+	    file f,
+	    line_num_t line_num,
+	    column_num_t start_column,
+	    column_num_t end_column)
+{
+  auto loc_start = mgr.new_location_from_file_line_column (f,
+							   line_num,
+							   start_column);
+  auto loc_end = mgr.new_location_from_file_line_column (f,
+							 line_num,
+							 end_column);
+  return mgr.new_location_from_range (loc_start,
+				      loc_start,
+				      loc_end);
+}
+
+} // namespace libdiagnostics
+
+#endif /* #ifndef TEST_HELPERSPP_H */
diff --git a/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.cc b/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.cc
new file mode 100644
index 000000000000..35ccf74e1529
--- /dev/null
+++ b/gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.cc
@@ -0,0 +1,43 @@
+/* C++ example of multiple locations, with labelling of ranges.
+
+   Intended output is similar to:
+
+PATH/test-labelled-ranges.cc:9: error: mismatching types: 'int' and 'const char *'
+   19 |   42 + "foo"
+      |   ~~ ^ ~~~~~
+      |   |    |
+      |   int  const char *
+
+   along with the equivalent in SARIF.  */
+
+#include "libdiagnostics++.h"
+#include "test-helpers++.h"
+
+/*
+_________11111111112
+12345678901234567890
+  42 + "foo"
+*/
+const int line_num = __LINE__ - 2;
+
+int
+main ()
+{
+  libdiagnostics::manager mgr;
+
+  mgr.add_text_sink (stderr, DIAGNOSTIC_COLORIZE_IF_TTY);
+  mgr.add_sarif_sink (stderr, DIAGNOSTIC_SARIF_VERSION_2_1_0);
+
+  libdiagnostics::file file = mgr.new_file (__FILE__, "c");
+  auto loc_operator = mgr.new_location_from_file_line_column (file, line_num, 6);
+
+  auto d (mgr.begin_diagnostic (DIAGNOSTIC_LEVEL_ERROR));
+  d.set_location (loc_operator);
+  d.add_location_with_label (make_range (mgr, file, line_num, 3, 4),
+			     "int");
+  d.add_location_with_label (make_range (mgr, file, line_num, 8, 12),
+			     "const char *");
+  d.finish ("mismatching types: %qs and %qs", "int", "const char *");
+  
+  return 0;
+}
-- 
2.26.3


^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH 4/5] diagnostics: add diagnostic_context::get_location_text
  2023-11-21 22:20 ` [PATCH 0/6] v2 of libdiagnostics David Malcolm
                     ` (2 preceding siblings ...)
  2023-11-21 22:20   ` [PATCH 3/5] libdiagnostics v2: add C++ wrapper API David Malcolm
@ 2023-11-21 22:20   ` David Malcolm
  2023-11-28  1:25     ` David Malcolm
  2023-11-21 22:20   ` [PATCH 5/5] diagnostics: don't print annotation lines when there's no column info David Malcolm
                     ` (3 subsequent siblings)
  7 siblings, 1 reply; 29+ messages in thread
From: David Malcolm @ 2023-11-21 22:20 UTC (permalink / raw)
  To: gcc-patches, binutils; +Cc: Nick Clifton, Simon Sobisch, David Malcolm

No functional change intended.

gcc/ChangeLog:
	* diagnostic.cc (diagnostic_get_location_text): Convert to...
	(diagnostic_context::get_location_text): ...this, and convert
	return type from char * to label_text.
	(diagnostic_build_prefix): Update for above change.
	(default_diagnostic_start_span_fn): Likewise.
	(selftest::assert_location_text): Likewise.
	* diagnostic.h (diagnostic_context::get_location_text): New decl.
---
 gcc/diagnostic.cc | 35 +++++++++++++++--------------------
 gcc/diagnostic.h  |  2 ++
 2 files changed, 17 insertions(+), 20 deletions(-)

diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc
index b4ebcd29457c..4f66fa6acaa8 100644
--- a/gcc/diagnostic.cc
+++ b/gcc/diagnostic.cc
@@ -558,14 +558,12 @@ maybe_line_and_column (int line, int col)
   return result;
 }
 
-/* Return a malloc'd string describing a location e.g. "foo.c:42:10".
-   The caller is responsible for freeing the memory.  */
+/* Return a string describing a location e.g. "foo.c:42:10".  */
 
-static char *
-diagnostic_get_location_text (diagnostic_context *context,
-			      expanded_location s)
+label_text
+diagnostic_context::get_location_text (const expanded_location &s) const
 {
-  pretty_printer *pp = context->printer;
+  pretty_printer *pp = this->printer;
   const char *locus_cs = colorize_start (pp_show_color (pp), "locus");
   const char *locus_ce = colorize_stop (pp_show_color (pp));
   const char *file = s.file ? s.file : progname;
@@ -574,13 +572,13 @@ diagnostic_get_location_text (diagnostic_context *context,
   if (strcmp (file, special_fname_builtin ()))
     {
       line = s.line;
-      if (context->m_show_column)
-	col = context->converted_column (s);
+      if (m_show_column)
+	col = this->converted_column (s);
     }
 
   const char *line_col = maybe_line_and_column (line, col);
-  return build_message_string ("%s%s%s:%s", locus_cs, file,
-			       line_col, locus_ce);
+  return label_text::take (build_message_string ("%s%s%s:%s", locus_cs, file,
+						 line_col, locus_ce));
 }
 
 static const char *const diagnostic_kind_text[] = {
@@ -610,12 +608,11 @@ diagnostic_build_prefix (diagnostic_context *context,
       text_ce = colorize_stop (pp_show_color (pp));
     }
 
-  expanded_location s = diagnostic_expand_location (diagnostic);
-  char *location_text = diagnostic_get_location_text (context, s);
+  const expanded_location s = diagnostic_expand_location (diagnostic);
+  label_text location_text = context->get_location_text (s);
 
-  char *result = build_message_string ("%s %s%s%s", location_text,
+  char *result = build_message_string ("%s %s%s%s", location_text.get (),
 				       text_cs, text, text_ce);
-  free (location_text);
   return result;
 }
 
@@ -1091,9 +1088,8 @@ void
 default_diagnostic_start_span_fn (diagnostic_context *context,
 				  expanded_location exploc)
 {
-  char *text = diagnostic_get_location_text (context, exploc);
-  pp_string (context->printer, text);
-  free (text);
+  label_text text = context->get_location_text (exploc);
+  pp_string (context->printer, text.get ());
   pp_newline (context->printer);
 }
 
@@ -2852,9 +2848,8 @@ assert_location_text (const char *expected_loc_text,
   xloc.data = NULL;
   xloc.sysp = false;
 
-  char *actual_loc_text = diagnostic_get_location_text (&dc, xloc);
-  ASSERT_STREQ (expected_loc_text, actual_loc_text);
-  free (actual_loc_text);
+  label_text actual_loc_text = dc.get_location_text (xloc);
+  ASSERT_STREQ (expected_loc_text, actual_loc_text.get ());
 }
 
 /* Verify that diagnostic_get_location_text works as expected.  */
diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h
index 958068fea926..8b7f147c0d28 100644
--- a/gcc/diagnostic.h
+++ b/gcc/diagnostic.h
@@ -543,6 +543,8 @@ public:
     return m_option_callbacks.m_lang_mask;
   }
 
+  label_text get_location_text (const expanded_location &s) const;
+
 private:
   bool includes_seen_p (const line_map_ordinary *map);
 
-- 
2.26.3


^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH 5/5] diagnostics: don't print annotation lines when there's no column info
  2023-11-21 22:20 ` [PATCH 0/6] v2 of libdiagnostics David Malcolm
                     ` (3 preceding siblings ...)
  2023-11-21 22:20   ` [PATCH 4/5] diagnostics: add diagnostic_context::get_location_text David Malcolm
@ 2023-11-21 22:20   ` David Malcolm
  2023-11-28  1:25     ` David Malcolm
  2023-11-21 22:20   ` [PATCH] binutils: v2: experimental use of libdiagnostics in gas David Malcolm
                     ` (2 subsequent siblings)
  7 siblings, 1 reply; 29+ messages in thread
From: David Malcolm @ 2023-11-21 22:20 UTC (permalink / raw)
  To: gcc-patches, binutils; +Cc: Nick Clifton, Simon Sobisch, David Malcolm

gcc/ChangeLog:
	* diagnostic-show-locus.cc (layout::maybe_add_location_range):
	Don't print annotation lines for ranges when there's no column
	info.
	(selftest::test_one_liner_no_column): New.
	(selftest::test_diagnostic_show_locus_one_liner): Call it.
---
 gcc/diagnostic-show-locus.cc | 26 +++++++++++++++++++++++++-
 1 file changed, 25 insertions(+), 1 deletion(-)

diff --git a/gcc/diagnostic-show-locus.cc b/gcc/diagnostic-show-locus.cc
index 563d2826f249..55e7166b9448 100644
--- a/gcc/diagnostic-show-locus.cc
+++ b/gcc/diagnostic-show-locus.cc
@@ -1295,6 +1295,15 @@ layout::maybe_add_location_range (const location_range *loc_range,
 	   sanely relative to the primary location.  */
 	return false;
 
+  /* If there's no column information, then don't try to print
+     annotation lines for this range.  */
+  enum range_display_kind range_display_kind
+    = loc_range->m_range_display_kind;
+  if (start.column == 0
+      || finish.column == 0
+      || caret.column == 0)
+    range_display_kind = SHOW_LINES_WITHOUT_RANGE;
+
   /* Everything is now known to be in the correct source file,
      but it may require further sanitization.  */
   layout_range ri (exploc_with_display_col (m_file_cache,
@@ -1303,7 +1312,7 @@ layout::maybe_add_location_range (const location_range *loc_range,
 		   exploc_with_display_col (m_file_cache,
 					    finish, m_policy,
 					    LOCATION_ASPECT_FINISH),
-		   loc_range->m_range_display_kind,
+		   range_display_kind,
 		   exploc_with_display_col (m_file_cache,
 					    caret, m_policy,
 					    LOCATION_ASPECT_CARET),
@@ -3297,6 +3306,20 @@ test_one_liner_simple_caret ()
 		pp_formatted_text (dc.printer));
 }
 
+/* No column information (column == 0).
+   No annotation line should be printed.  */
+
+static void
+test_one_liner_no_column ()
+{
+  test_diagnostic_context dc;
+  location_t caret = linemap_position_for_column (line_table, 0);
+  rich_location richloc (line_table, caret);
+  diagnostic_show_locus (&dc, &richloc, DK_ERROR);
+  ASSERT_STREQ (" foo = bar.field;\n",
+		pp_formatted_text (dc.printer));
+}
+
 /* Caret and range.  */
 
 static void
@@ -3848,6 +3871,7 @@ test_diagnostic_show_locus_one_liner (const line_table_case &case_)
   ASSERT_EQ (16, LOCATION_COLUMN (line_end));
 
   test_one_liner_simple_caret ();
+  test_one_liner_no_column ();
   test_one_liner_caret_and_range ();
   test_one_liner_multiple_carets_and_ranges ();
   test_one_liner_fixit_insert_before ();
-- 
2.26.3


^ permalink raw reply	[flat|nested] 29+ messages in thread

* [PATCH] binutils: v2: experimental use of libdiagnostics in gas
  2023-11-21 22:20 ` [PATCH 0/6] v2 of libdiagnostics David Malcolm
                     ` (4 preceding siblings ...)
  2023-11-21 22:20   ` [PATCH 5/5] diagnostics: don't print annotation lines when there's no column info David Malcolm
@ 2023-11-21 22:20   ` David Malcolm
  2023-11-22  7:36     ` Jan Beulich
  2023-11-21 22:35   ` [PATCH 0/6] v2 of libdiagnostics Simon Sobisch
  2023-11-23 17:36   ` Pedro Alves
  7 siblings, 1 reply; 29+ messages in thread
From: David Malcolm @ 2023-11-21 22:20 UTC (permalink / raw)
  To: gcc-patches, binutils; +Cc: Nick Clifton, Simon Sobisch, David Malcolm

Changed in v2:
* updated for change from diagnostic_location_t to
  const diagnostic_physical_location *
* fix #if USE_DIAGNOSTICS to retain context and listing code

Output from the example below with v2 is now:

testsuite/gas/all/warn-1.s:3: warning: a warning message
    3 |  .warning "a warning message"   ;# { dg-warning "Warning: a warning message" }
testsuite/gas/all/warn-1.s:4: error: .warning argument must be a string
    4 |  .warning a warning message     ;# { dg-error "Error: .warning argument must be a string" }
testsuite/gas/all/warn-1.s:5: warning: .warning directive invoked in source file
    5 |  .warning                       ;# { dg-warning "Warning: .warning directive invoked in source file" }
testsuite/gas/all/warn-1.s:6: warning: .warning directive invoked in source file
    6 |  .warning ".warning directive invoked in source file"   ;# { dg-warning "Warning: .warning directive invoked in source file" }
testsuite/gas/all/warn-1.s:7: warning: 
    7 |  .warning ""                    ;# { dg-warning "Warning: " }

and there's also now a way to toggle quoting of the source code (perhaps
for use in the testsuite)


Blurb from v1:
Here's a patch for gas in binutils that makes it use libdiagnostics
(with some nasty hardcoded paths to specific places on my hard drive
to make it easier to develop the API).

For now this hardcodes adding two sinks: a text sink on stderr, and
also a SARIF output to stderr (which happens after all regular output).

For example, without this patch:

   gas testsuite/gas/all/warn-1.s

emits:
VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
testsuite/gas/all/warn-1.s: Assembler messages:
testsuite/gas/all/warn-1.s:3: Warning: a warning message
testsuite/gas/all/warn-1.s:4: Error: .warning argument must be a string
testsuite/gas/all/warn-1.s:5: Warning: .warning directive invoked in source file
testsuite/gas/all/warn-1.s:6: Warning: .warning directive invoked in source file
testsuite/gas/all/warn-1.s:7: Warning:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

whereas with this patch:
  LD_LIBRARY_PATH=/home/david/coding-3/gcc-newgit-canvas-2023/build/gcc ./as-new testsuite/gas/all/warn-1.s
emits:

VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
testsuite/gas/all/warn-1.s:3: warning: a warning message
    3 |  .warning "a warning message"   ;# { dg-warning "Warning: a warning message" }
      |
testsuite/gas/all/warn-1.s:4: error: .warning argument must be a string
    4 |  .warning a warning message     ;# { dg-error "Error: .warning argument must be a string" }
      |
testsuite/gas/all/warn-1.s:5: warning: .warning directive invoked in source file
    5 |  .warning                       ;# { dg-warning "Warning: .warning directive invoked in source file" }
      |
testsuite/gas/all/warn-1.s:6: warning: .warning directive invoked in source file
    6 |  .warning ".warning directive invoked in source file"   ;# { dg-warning "Warning: .warning directive invoked in source file" }
      |
testsuite/gas/all/warn-1.s:7: warning:
    7 |  .warning ""                    ;# { dg-warning "Warning: " }
      |
	{"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", "version": "2.1.0", "runs": [{"tool": {"driver": {"rules": []}}, "invocations": [{"executionSuccessful": true, "toolExecutionNotifications": []}], "originalUriBaseIds": {"PWD": {"uri": "file:///home/david/coding-3/binutils-gdb/gas/"}}, "artifacts": [{"location": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "contents": {"text": ";# Test .warning directive.\n;# { dg-do assemble }\n .warning \"a warning message\"\t;# { dg-warning \"Warning: a warning message\" }\n .warning a warning message\t;# { dg-error \"Error: .warning argument must be a string\" }\n .warning\t\t\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n .warning \".warning directive invoked in source file\"\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n .warning \"\"\t\t\t;# { dg-warning \"Warning: \" }\n"}}], "results": [{"ruleId": "warning", "level": "warning", "message": {"text": "a warning message"}, "locations": [{"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 3, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 3, "snippet": {"text": " .warning \"a warning message\"\t;# { dg-warning \"Warning: a warning message\" }\n"}}}}], "relatedLocations": [{"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 4, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 4, "snippet": {"text": " .warning a warning message\t;# { dg-error \"Error: .warning argument must be a string\" }\n"}}}, "message": {"text": ".warning argument must be a string"}}, {"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 5, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 5, "snippet": {"text": " .warning\t\t\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n"}}}, "message": {"text": ".warning directive invoked in source file"}}, {"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 6, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 6, "snippet": {"text": " .warning \".warning directive invoked in source file\"\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n"}}}, "message": {"text": ".warning directive invoked in source file"}}, {"physicalLocation": {"artifactLocation": {"uri": "testsuite/gas/all/warn-1.s", "uriBaseId": "PWD"}, "region": {"startLine": 7, "startColumn": 0, "endColumn": 1}, "contextRegion": {"startLine": 7, "snippet": {"text": " .warning \"\"\t\t\t;# { dg-warning \"Warning: \" }\n"}}}, "message": {"text": ""}}]}]}]}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

For reference, running that SARIF through "python -m json.tool" to
prettyify it gives:

VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
{
    "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
    "version": "2.1.0",
    "runs": [
        {
            "tool": {
                "driver": {
                    "rules": []
                }
            },
            "invocations": [
                {
                    "executionSuccessful": true,
                    "toolExecutionNotifications": []
                }
            ],
            "originalUriBaseIds": {
                "PWD": {
                    "uri": "file:///home/david/coding-3/binutils-gdb/gas/"
                }
            },
            "artifacts": [
                {
                    "location": {
                        "uri": "testsuite/gas/all/warn-1.s",
                        "uriBaseId": "PWD"
                    },
                    "contents": {
                        "text": ";# Test .warning directive.\n;# { dg-do assemble }\n .warning \"a warning message\"\t;# { dg-warning \"Warning: a warning message\" }\n .warning a warning message\t;# { dg-error \"Error: .warning argument must be a string\" }\n .warning\t\t\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n .warning \".warning directive invoked in source file\"\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n .warning \"\"\t\t\t;# { dg-warning \"Warning: \" }\n"
                    }
                }
            ],
            "results": [
                {
                    "ruleId": "warning",
                    "level": "warning",
                    "message": {
                        "text": "a warning message"
                    },
                    "locations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "testsuite/gas/all/warn-1.s",
                                    "uriBaseId": "PWD"
                                },
                                "region": {
                                    "startLine": 3,
                                    "startColumn": 0,
                                    "endColumn": 1
                                },
                                "contextRegion": {
                                    "startLine": 3,
                                    "snippet": {
                                        "text": " .warning \"a warning message\"\t;# { dg-warning \"Warning: a warning message\" }\n"
                                    }
                                }
                            }
                        }
                    ],
                    "relatedLocations": [
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "testsuite/gas/all/warn-1.s",
                                    "uriBaseId": "PWD"
                                },
                                "region": {
                                    "startLine": 4,
                                    "startColumn": 0,
                                    "endColumn": 1
                                },
                                "contextRegion": {
                                    "startLine": 4,
                                    "snippet": {
                                        "text": " .warning a warning message\t;# { dg-error \"Error: .warning argument must be a string\" }\n"
                                    }
                                }
                            },
                            "message": {
                                "text": ".warning argument must be a string"
                            }
                        },
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "testsuite/gas/all/warn-1.s",
                                    "uriBaseId": "PWD"
                                },
                                "region": {
                                    "startLine": 5,
                                    "startColumn": 0,
                                    "endColumn": 1
                                },
                                "contextRegion": {
                                    "startLine": 5,
                                    "snippet": {
                                        "text": " .warning\t\t\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n"
                                    }
                                }
                            },
                            "message": {
                                "text": ".warning directive invoked in source file"
                            }
                        },
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "testsuite/gas/all/warn-1.s",
                                    "uriBaseId": "PWD"
                                },
                                "region": {
                                    "startLine": 6,
                                    "startColumn": 0,
                                    "endColumn": 1
                                },
                                "contextRegion": {
                                    "startLine": 6,
                                    "snippet": {
                                        "text": " .warning \".warning directive invoked in source file\"\t;# { dg-warning \"Warning: .warning directive invoked in source file\" }\n"
                                    }
                                }
                            },
                            "message": {
                                "text": ".warning directive invoked in source file"
                            }
                        },
                        {
                            "physicalLocation": {
                                "artifactLocation": {
                                    "uri": "testsuite/gas/all/warn-1.s",
                                    "uriBaseId": "PWD"
                                },
                                "region": {
                                    "startLine": 7,
                                    "startColumn": 0,
                                    "endColumn": 1
                                },
                                "contextRegion": {
                                    "startLine": 7,
                                    "snippet": {
                                        "text": " .warning \"\"\t\t\t;# { dg-warning \"Warning: \" }\n"
                                    }
                                }
                            },
                            "message": {
                                "text": ""
                            }
                        }
                    ]
                }
            ]
        }
    ]
}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

gas/ChangeLog:
	* Makefile.am (GASLIBS): Add nasty hack with hardcoded path
	on my hard drive.
	* Makefile.in (GASLIBS): Likewise.
	* as.c (gas_init): Call messages_init.
	(main): Call messages_end.
	* as.h (messages_init): New decl.
	(messages_end): New decl.
	* messages.c (USE_LIBDIAGNOSTICS): New define.
	Add #include with nasty hardcoded path.
	(diag_mgr): New.
	(messages_init): New.
	(messages_end): New.
	(as_warn_internal): Add support for libdiagnostics.
	(as_bad_internal): Likewise.
---
 gas/Makefile.am |  3 ++-
 gas/Makefile.in |  4 +++-
 gas/as.c        |  3 +++
 gas/as.h        |  3 +++
 gas/messages.c  | 60 +++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 71 insertions(+), 2 deletions(-)

diff --git a/gas/Makefile.am b/gas/Makefile.am
index 0e98ca3ec85..fe92997129c 100644
--- a/gas/Makefile.am
+++ b/gas/Makefile.am
@@ -408,7 +408,8 @@ AM_CPPFLAGS = -I. -I$(srcdir) -I../bfd -I$(srcdir)/config \
 # How to link with both our special library facilities
 # and the system's installed libraries.
 
-GASLIBS = @OPCODES_LIB@ ../bfd/libbfd.la ../libiberty/libiberty.a
+# FIXME:
+GASLIBS = @OPCODES_LIB@ ../bfd/libbfd.la ../libiberty/libiberty.a /home/david/coding-3/gcc-newgit-canvas-2023/build/gcc/libdiagnostics.so
 
 # Files to be copied away after each stage in building.
 STAGESTUFF = *.@OBJEXT@ $(noinst_PROGRAMS)
diff --git a/gas/Makefile.in b/gas/Makefile.in
index fae3a47c144..2161d68f9c7 100644
--- a/gas/Makefile.in
+++ b/gas/Makefile.in
@@ -885,7 +885,9 @@ AM_CPPFLAGS = -I. -I$(srcdir) -I../bfd -I$(srcdir)/config \
 
 # How to link with both our special library facilities
 # and the system's installed libraries.
-GASLIBS = @OPCODES_LIB@ ../bfd/libbfd.la ../libiberty/libiberty.a
+
+# FIXME:
+GASLIBS = @OPCODES_LIB@ ../bfd/libbfd.la ../libiberty/libiberty.a /home/david/coding-3/gcc-newgit-canvas-2023/build/gcc/libdiagnostics.so
 
 # Files to be copied away after each stage in building.
 STAGESTUFF = *.@OBJEXT@ $(noinst_PROGRAMS)
diff --git a/gas/as.c b/gas/as.c
index 6839c841588..2a8ce3734a0 100644
--- a/gas/as.c
+++ b/gas/as.c
@@ -1300,6 +1300,7 @@ gas_early_init (int *argcp, char ***argvp)
 static void
 gas_init (void)
 {
+  messages_init ();
   symbol_begin ();
   frag_init ();
   subsegs_begin ();
@@ -1486,6 +1487,8 @@ main (int argc, char ** argv)
 
   input_scrub_end ();
 
+  messages_end ();
+
   /* Use xexit instead of return, because under VMS environments they
      may not place the same interpretation on the value given.  */
   if (had_errors () != 0)
diff --git a/gas/as.h b/gas/as.h
index 99ffe77afd2..9d878a87df5 100644
--- a/gas/as.h
+++ b/gas/as.h
@@ -474,6 +474,9 @@ void   as_bad_value_out_of_range (const char *, offsetT, offsetT, offsetT,
 void   print_version_id (void);
 char * app_push (void);
 
+void messages_init (void);
+void messages_end (void);
+
 /* Number of littlenums required to hold an extended precision number.	*/
 #define MAX_LITTLENUMS 6
 
diff --git a/gas/messages.c b/gas/messages.c
index 7c018acf69f..1854560dae7 100644
--- a/gas/messages.c
+++ b/gas/messages.c
@@ -21,6 +21,14 @@
 #include <limits.h>
 #include <signal.h>
 
+// FIXME: do some configury thing for this
+#define USE_LIBDIAGNOSTICS 1
+
+#if USE_LIBDIAGNOSTICS
+// FIXME: need to fix this path, obviously
+#include "/home/david/coding-3/gcc-newgit-canvas-2023/src/gcc/libdiagnostics.h"
+#endif
+
 /* If the system doesn't provide strsignal, we get it defined in
    libiberty but no declaration is supplied.  Because, reasons. */
 #if !defined (HAVE_STRSIGNAL) && !defined (strsignal)
@@ -101,6 +109,29 @@ had_warnings (void)
   return warning_count;
 }
 
+#if USE_LIBDIAGNOSTICS
+static diagnostic_manager *diag_mgr;
+#endif
+
+void messages_init (void)
+{
+#if USE_LIBDIAGNOSTICS
+  diag_mgr = diagnostic_manager_new ();
+  diagnostic_manager_add_text_sink (diag_mgr, stderr,
+				    DIAGNOSTIC_COLORIZE_IF_TTY);
+  diagnostic_manager_add_sarif_sink (diag_mgr, stderr,
+				     DIAGNOSTIC_SARIF_VERSION_2_1_0);
+#endif
+}
+
+void messages_end (void)
+{
+#if USE_LIBDIAGNOSTICS
+  diagnostic_manager_release (diag_mgr);
+  diag_mgr = NULL;
+#endif
+}
+
 /* Nonzero if we've hit a 'bad error', and should not write an obj file,
    and exit with a nonzero error code.  */
 
@@ -182,6 +213,20 @@ as_warn_internal (const char *file, unsigned int line, char *buffer)
       context = true;
     }
 
+#if USE_LIBDIAGNOSTICS
+  const diagnostic_file *file_obj
+    = diagnostic_manager_new_file (diag_mgr, file, NULL);
+
+  const diagnostic_physical_location *loc
+    = diagnostic_manager_new_location_from_file_and_line (diag_mgr,
+							  file_obj,
+							  line);
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_WARNING);
+  diagnostic_set_location (d, loc);
+  diagnostic_finish (d, "%s", buffer);
+#else
   identify (file);
   if (file)
     {
@@ -192,6 +237,7 @@ as_warn_internal (const char *file, unsigned int line, char *buffer)
     }
   else
     fprintf (stderr, "%s%s\n", _("Warning: "), buffer);
+#endif /* #else clause of #if USE_LIBDIAGNOSTICS */
 
   if (context)
     as_report_context ();
@@ -256,6 +302,19 @@ as_bad_internal (const char *file, unsigned int line, char *buffer)
       context = true;
     }
 
+#if USE_LIBDIAGNOSTICS
+  const diagnostic_file *file_obj
+    = diagnostic_manager_new_file (diag_mgr, file, NULL);
+  const diagnostic_physical_location *loc
+    = diagnostic_manager_new_location_from_file_and_line (diag_mgr,
+							  file_obj,
+							  line);
+
+  diagnostic *d = diagnostic_begin (diag_mgr,
+				    DIAGNOSTIC_LEVEL_ERROR);
+  diagnostic_set_location (d, loc);
+  diagnostic_finish (d, "%s", buffer);
+#else
   identify (file);
   if (file)
     {
@@ -266,6 +325,7 @@ as_bad_internal (const char *file, unsigned int line, char *buffer)
     }
   else
     fprintf (stderr, "%s%s\n", _("Error: "), buffer);
+#endif /* #else clause of #if USE_LIBDIAGNOSTICS */
 
   if (context)
     as_report_context ();
-- 
2.26.3


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 0/6] v2 of libdiagnostics
  2023-11-21 22:20 ` [PATCH 0/6] v2 of libdiagnostics David Malcolm
                     ` (5 preceding siblings ...)
  2023-11-21 22:20   ` [PATCH] binutils: v2: experimental use of libdiagnostics in gas David Malcolm
@ 2023-11-21 22:35   ` Simon Sobisch
  2023-11-23 17:36   ` Pedro Alves
  7 siblings, 0 replies; 29+ messages in thread
From: Simon Sobisch @ 2023-11-21 22:35 UTC (permalink / raw)
  To: David Malcolm, gcc-patches, binutils; +Cc: Nick Clifton

Thank you for your efforts.
Having the wiki page to track this definitely is useful!

I'll have a look at the "real patch" later, likely next week.

But for patch 4+5 which look quite clean: can we get an early 
improvement and inclusion into GCC for those?
They only adjust internals and should be well covered by the existing 
test suite, so we may be able to inspect the other changes from this 
patchset "alone".

Kind Regards,
Simon

Am 21.11.2023 um 23:20 schrieb David Malcolm:
> Here's v2 of the "libdiagnostics" shared library idea; see:
>    https://gcc.gnu.org/wiki/libdiagnostics
> 
> As in v1, patch 1 (for GCC) shows libdiagnostic.h (the public
> header file), along with examples of simple self-contained programs that
> show various uses of the API.
> 
> As in v1, patch 2 (for GCC) is the work-in-progress implementation.
> 
> Patch 3 (for GCC) adds a new libdiagnostics++.h, a wrapper API providing
> some syntactic sugar when using the API from C++.  I've been using this
> to "eat my own dogfood" and write a simple SARIF-dumping tool:
>    https://github.com/davidmalcolm/libdiagnostics-sarif-dump
> 
> Patch 4 (for GCC) is an internal change needed by patch 1.
> 
> Patch 5 (for GCC) updates GCC's source printing code so that when
> there's no column information, we don't print annotation lines.  This
> fixes the extra lines seen using it from gas discussed in:
> https://gcc.gnu.org/pipermail/gcc-patches/2023-November/635575.html
> 
> Patch 6 (for binutils) is an updated version of the experiment at using
> the API from gas.
> 
> Thoughts?
> 
> David Malcolm (5):
>    libdiagnostics v2: header and examples
>    libdiagnostics v2: work-in-progress implementation
>    libdiagnostics v2: add C++ wrapper API
>    diagnostics: add diagnostic_context::get_location_text
>    diagnostics: don't print annotation lines when there's no column info
> 
>   gcc/Makefile.in                               |  131 +-
>   gcc/configure                                 |    2 +-
>   gcc/configure.ac                              |    2 +-
>   gcc/diagnostic-show-locus.cc                  |   26 +-
>   gcc/diagnostic.cc                             |   35 +-
>   gcc/diagnostic.h                              |    2 +
>   gcc/libdiagnostics++.h                        |  378 +++++
>   gcc/libdiagnostics.cc                         | 1306 +++++++++++++++++
>   gcc/libdiagnostics.h                          |  602 ++++++++
>   gcc/libdiagnostics.map                        |   63 +
>   .../libdiagnostics.dg/libdiagnostics.exp      |  544 +++++++
>   gcc/testsuite/libdiagnostics.dg/test-dump.c   |   55 +
>   .../libdiagnostics.dg/test-error-with-note.c  |   57 +
>   .../libdiagnostics.dg/test-error-with-note.cc |   47 +
>   gcc/testsuite/libdiagnostics.dg/test-error.c  |   49 +
>   gcc/testsuite/libdiagnostics.dg/test-error.cc |   40 +
>   .../libdiagnostics.dg/test-fix-it-hint.c      |   49 +
>   .../libdiagnostics.dg/test-fix-it-hint.cc     |   44 +
>   .../libdiagnostics.dg/test-helpers++.h        |   28 +
>   .../libdiagnostics.dg/test-helpers.h          |   29 +
>   .../libdiagnostics.dg/test-labelled-ranges.c  |   52 +
>   .../libdiagnostics.dg/test-labelled-ranges.cc |   43 +
>   .../libdiagnostics.dg/test-logical-location.c |   60 +
>   .../libdiagnostics.dg/test-metadata.c         |   54 +
>   .../libdiagnostics.dg/test-multiple-lines.c   |   61 +
>   .../libdiagnostics.dg/test-no-column.c        |   41 +
>   .../test-note-with-fix-it-hint.c              |   52 +
>   .../test-text-sink-options.c                  |   46 +
>   .../libdiagnostics.dg/test-warning.c          |   52 +
>   .../test-write-sarif-to-file.c                |   46 +
>   .../test-write-text-to-file.c                 |   47 +
>   31 files changed, 4018 insertions(+), 25 deletions(-)
>   create mode 100644 gcc/libdiagnostics++.h
>   create mode 100644 gcc/libdiagnostics.cc
>   create mode 100644 gcc/libdiagnostics.h
>   create mode 100644 gcc/libdiagnostics.map
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/libdiagnostics.exp
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/test-dump.c
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error-with-note.c
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error-with-note.cc
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error.c
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/test-error.cc
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.c
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/test-fix-it-hint.cc
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/test-helpers++.h
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/test-helpers.h
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.c
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/test-labelled-ranges.cc
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/test-logical-location.c
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/test-metadata.c
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/test-multiple-lines.c
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/test-no-column.c
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/test-note-with-fix-it-hint.c
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/test-text-sink-options.c
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/test-warning.c
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/test-write-sarif-to-file.c
>   create mode 100644 gcc/testsuite/libdiagnostics.dg/test-write-text-to-file.c
> 

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH] binutils: v2: experimental use of libdiagnostics in gas
  2023-11-21 22:20   ` [PATCH] binutils: v2: experimental use of libdiagnostics in gas David Malcolm
@ 2023-11-22  7:36     ` Jan Beulich
  0 siblings, 0 replies; 29+ messages in thread
From: Jan Beulich @ 2023-11-22  7:36 UTC (permalink / raw)
  To: David Malcolm; +Cc: Nick Clifton, Simon Sobisch, gcc-patches, binutils

On 21.11.2023 23:20, David Malcolm wrote:
> @@ -101,6 +109,29 @@ had_warnings (void)
>    return warning_count;
>  }
>  
> +#if USE_LIBDIAGNOSTICS
> +static diagnostic_manager *diag_mgr;
> +#endif
> +
> +void messages_init (void)
> +{
> +#if USE_LIBDIAGNOSTICS
> +  diag_mgr = diagnostic_manager_new ();
> +  diagnostic_manager_add_text_sink (diag_mgr, stderr,
> +				    DIAGNOSTIC_COLORIZE_IF_TTY);

Coloring will want to be possible to turn off (or maybe be off by default,
and be possible to turn on).

As to the #if-s: I think they all would better be #ifdef.

> @@ -182,6 +213,20 @@ as_warn_internal (const char *file, unsigned int line, char *buffer)
>        context = true;
>      }
>  
> +#if USE_LIBDIAGNOSTICS
> +  const diagnostic_file *file_obj
> +    = diagnostic_manager_new_file (diag_mgr, file, NULL);
> +
> +  const diagnostic_physical_location *loc
> +    = diagnostic_manager_new_location_from_file_and_line (diag_mgr,
> +							  file_obj,
> +							  line);
> +
> +  diagnostic *d = diagnostic_begin (diag_mgr,
> +				    DIAGNOSTIC_LEVEL_WARNING);
> +  diagnostic_set_location (d, loc);
> +  diagnostic_finish (d, "%s", buffer);
> +#else

This looks identical to ...

> @@ -256,6 +302,19 @@ as_bad_internal (const char *file, unsigned int line, char *buffer)
>        context = true;
>      }
>  
> +#if USE_LIBDIAGNOSTICS
> +  const diagnostic_file *file_obj
> +    = diagnostic_manager_new_file (diag_mgr, file, NULL);
> +  const diagnostic_physical_location *loc
> +    = diagnostic_manager_new_location_from_file_and_line (diag_mgr,
> +							  file_obj,
> +							  line);
> +
> +  diagnostic *d = diagnostic_begin (diag_mgr,
> +				    DIAGNOSTIC_LEVEL_ERROR);
> +  diagnostic_set_location (d, loc);
> +  diagnostic_finish (d, "%s", buffer);
> +#else

... this, except for the level used. Put into a helper function? Which
would the further want using from as_info_where()?

Jan

^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 0/6] v2 of libdiagnostics
  2023-11-21 22:20 ` [PATCH 0/6] v2 of libdiagnostics David Malcolm
                     ` (6 preceding siblings ...)
  2023-11-21 22:35   ` [PATCH 0/6] v2 of libdiagnostics Simon Sobisch
@ 2023-11-23 17:36   ` Pedro Alves
  2024-01-27 23:28     ` Simon Sobisch
  7 siblings, 1 reply; 29+ messages in thread
From: Pedro Alves @ 2023-11-23 17:36 UTC (permalink / raw)
  To: David Malcolm, gcc-patches, binutils; +Cc: Nick Clifton, Simon Sobisch

Hi David,

On 2023-11-21 22:20, David Malcolm wrote:
> Here's v2 of the "libdiagnostics" shared library idea; see:
>   https://gcc.gnu.org/wiki/libdiagnostics
> 
> As in v1, patch 1 (for GCC) shows libdiagnostic.h (the public
> header file), along with examples of simple self-contained programs that
> show various uses of the API.
> 
> As in v1, patch 2 (for GCC) is the work-in-progress implementation.
> 
> Patch 3 (for GCC) adds a new libdiagnostics++.h, a wrapper API providing
> some syntactic sugar when using the API from C++.  I've been using this
> to "eat my own dogfood" and write a simple SARIF-dumping tool:
>   https://github.com/davidmalcolm/libdiagnostics-sarif-dump
> 
> Patch 4 (for GCC) is an internal change needed by patch 1.
> 
> Patch 5 (for GCC) updates GCC's source printing code so that when
> there's no column information, we don't print annotation lines.  This
> fixes the extra lines seen using it from gas discussed in:
> https://gcc.gnu.org/pipermail/gcc-patches/2023-November/635575.html
> 
> Patch 6 (for binutils) is an updated version of the experiment at using
> the API from gas.
> 
> Thoughts?

Do you have plans on making this a top level library instead?  That would allow easily
making it a non-optional dependency for binutils, as we could have the library in
the binutils-gdb repo as well, for instance.  From the Cauldron discussion I understood that
the diagnostics stuff doesn't depend on much of GCC's data structures, and doesn't rely on
the garbage collector.  Is there something preventing that?  (Other than "it's-a-matter-of-time/effort",
of course.)

Pedro Alves


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 4/5] diagnostics: add diagnostic_context::get_location_text
  2023-11-21 22:20   ` [PATCH 4/5] diagnostics: add diagnostic_context::get_location_text David Malcolm
@ 2023-11-28  1:25     ` David Malcolm
  0 siblings, 0 replies; 29+ messages in thread
From: David Malcolm @ 2023-11-28  1:25 UTC (permalink / raw)
  To: gcc-patches, binutils; +Cc: Nick Clifton, Simon Sobisch

On Tue, 2023-11-21 at 17:20 -0500, David Malcolm wrote:
> No functional change intended.
> 
> gcc/ChangeLog:
>         * diagnostic.cc (diagnostic_get_location_text): Convert to...
>         (diagnostic_context::get_location_text): ...this, and convert
>         return type from char * to label_text.
>         (diagnostic_build_prefix): Update for above change.
>         (default_diagnostic_start_span_fn): Likewise.
>         (selftest::assert_location_text): Likewise.
>         * diagnostic.h (diagnostic_context::get_location_text): New
> decl.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.

Not a bugfix, but low-risk and useful for libdiagnostics, so I've taken
the liberty of pushing this to gcc trunk as r14-5895-g93096d3ce14a89.

Dave


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 5/5] diagnostics: don't print annotation lines when there's no column info
  2023-11-21 22:20   ` [PATCH 5/5] diagnostics: don't print annotation lines when there's no column info David Malcolm
@ 2023-11-28  1:25     ` David Malcolm
  0 siblings, 0 replies; 29+ messages in thread
From: David Malcolm @ 2023-11-28  1:25 UTC (permalink / raw)
  To: gcc-patches, binutils; +Cc: Nick Clifton, Simon Sobisch

On Tue, 2023-11-21 at 17:20 -0500, David Malcolm wrote:
> gcc/ChangeLog:
>         * diagnostic-show-locus.cc
> (layout::maybe_add_location_range):
>         Don't print annotation lines for ranges when there's no
> column
>         info.
>         (selftest::test_one_liner_no_column): New.
>         (selftest::test_diagnostic_show_locus_one_liner): Call it.

Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.

Not a bugfix, but low-risk and useful for libdiagnostics, so I've taken
the liberty of pushing this to gcc trunk as r14-5896-g5099525bff4f7c.

Dave


^ permalink raw reply	[flat|nested] 29+ messages in thread

* Re: [PATCH 0/6] v2 of libdiagnostics
  2023-11-23 17:36   ` Pedro Alves
@ 2024-01-27 23:28     ` Simon Sobisch
  0 siblings, 0 replies; 29+ messages in thread
From: Simon Sobisch @ 2024-01-27 23:28 UTC (permalink / raw)
  To: David Malcolm, gcc-patches; +Cc: Pedro Alves, Nick Clifton, binutils

Hi David - and thanks for posting an outline for libdiagnostics at
https://gcc.gnu.org/wiki/libdiagnostics

Currently this shows both libdiagnosts and libdiagnostics-sarif-dump 
integrated into GCC. Is this the plan or would those be available as a 
top-level project (the program as an example for the library), possibly 
with the library sources also pushed to GCC?

Oh, and one question as I stumbled over that today: Would libdiagnostics 
now (or in the future) use libtextstyle for formatting (and another 
possible sink: HTML)?

Simon

Am 23.11.2023 um 18:36 schrieb Pedro Alves:
> Hi David,
> 
> On 2023-11-21 22:20, David Malcolm wrote:
>> Here's v2 of the "libdiagnostics" shared library idea; see:
>>    https://gcc.gnu.org/wiki/libdiagnostics
>>
>> As in v1, patch 1 (for GCC) shows libdiagnostic.h (the public
>> header file), along with examples of simple self-contained programs that
>> show various uses of the API.
>>
>> As in v1, patch 2 (for GCC) is the work-in-progress implementation.
>>
>> Patch 3 (for GCC) adds a new libdiagnostics++.h, a wrapper API providing
>> some syntactic sugar when using the API from C++.  I've been using this
>> to "eat my own dogfood" and write a simple SARIF-dumping tool:
>>    https://github.com/davidmalcolm/libdiagnostics-sarif-dump
>>
>> Patch 4 (for GCC) is an internal change needed by patch 1.
>>
>> Patch 5 (for GCC) updates GCC's source printing code so that when
>> there's no column information, we don't print annotation lines.  This
>> fixes the extra lines seen using it from gas discussed in:
>> https://gcc.gnu.org/pipermail/gcc-patches/2023-November/635575.html
>>
>> Patch 6 (for binutils) is an updated version of the experiment at using
>> the API from gas.
>>
>> Thoughts?
> 
> Do you have plans on making this a top level library instead?  That would allow easily
> making it a non-optional dependency for binutils, as we could have the library in
> the binutils-gdb repo as well, for instance.  From the Cauldron discussion I understood that
> the diagnostics stuff doesn't depend on much of GCC's data structures, and doesn't rely on
> the garbage collector.  Is there something preventing that?  (Other than "it's-a-matter-of-time/effort",
> of course.)
> 
> Pedro Alves
> 
> 

^ permalink raw reply	[flat|nested] 29+ messages in thread

end of thread, other threads:[~2024-01-27 23:28 UTC | newest]

Thread overview: 29+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-11-06 22:29 [PATCH/RFC] libdiagnostics: a shared library for emitting diagnostics David Malcolm
2023-11-06 22:29 ` [PATCH 1/2] libdiagnostics: header and examples David Malcolm
2023-11-06 22:29 ` [PATCH 2/2] libdiagnostics: work-in-progress implementation David Malcolm
2023-11-07  7:54   ` Simon Sobisch
2023-11-07 14:59     ` David Malcolm
2023-11-07 15:35       ` Simon Sobisch
2023-11-06 22:29 ` [PATCH] binutils: experimental use of libdiagnostics in gas David Malcolm
2023-11-07  7:04   ` Simon Sobisch
2023-11-07 14:51     ` David Malcolm
2023-11-07  9:21   ` Clément Chigot
2023-11-07 14:09     ` David Malcolm
2023-11-07 15:57       ` Clément Chigot
2023-11-07 16:18         ` David Malcolm
2023-11-07 10:03   ` Jan Beulich
2023-11-07 14:32     ` David Malcolm
2023-11-07 14:59       ` Jan Beulich
2023-11-21 22:20 ` [PATCH 0/6] v2 of libdiagnostics David Malcolm
2023-11-21 22:20   ` [PATCH 1/5] libdiagnostics v2: header and examples David Malcolm
2023-11-21 22:20   ` [PATCH 2/5] libdiagnostics v2: work-in-progress implementation David Malcolm
2023-11-21 22:20   ` [PATCH 3/5] libdiagnostics v2: add C++ wrapper API David Malcolm
2023-11-21 22:20   ` [PATCH 4/5] diagnostics: add diagnostic_context::get_location_text David Malcolm
2023-11-28  1:25     ` David Malcolm
2023-11-21 22:20   ` [PATCH 5/5] diagnostics: don't print annotation lines when there's no column info David Malcolm
2023-11-28  1:25     ` David Malcolm
2023-11-21 22:20   ` [PATCH] binutils: v2: experimental use of libdiagnostics in gas David Malcolm
2023-11-22  7:36     ` Jan Beulich
2023-11-21 22:35   ` [PATCH 0/6] v2 of libdiagnostics Simon Sobisch
2023-11-23 17:36   ` Pedro Alves
2024-01-27 23:28     ` Simon Sobisch

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).