diff mbox series

[1/2] libdiagnostics: header and examples

Message ID 20231106222959.2707741-2-dmalcolm@redhat.com
State New
Headers show
Series [1/2] libdiagnostics: header and examples | expand

Commit Message

David Malcolm Nov. 6, 2023, 10:29 p.m. UTC
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

Comments

Lewis Hyatt Nov. 8, 2023, 12:02 a.m. UTC | #1
On Mon, Nov 6, 2023 at 8:29 PM David Malcolm <dmalcolm@redhat.com> wrote:
>
> 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?
>
...
> +typedef unsigned int diagnostic_location_t;

One comment that occurred to me... for GCC we have a lot of PRs that
are unhappy about the 32-bit location_t and the consequent issues that
arise with very large source files, or with very long lines that lose
column information.
So far GCC has been able to get by with "don't do that" advice, but a
more general libdiagnostics may need to avoid that arbitrary
limitation? I feel like it may not be that long before GCC needs to
deal with it as well, perhaps with a configure option, but even now,
it could make sense for libdiagnostic to use a 64-bit location_t
itself from the outset, so it won't need to change later, even if it's
practically restricted to 32 bits for now.

-Lewis
David Malcolm Nov. 8, 2023, 12:45 a.m. UTC | #2
On Tue, 2023-11-07 at 19:02 -0500, Lewis Hyatt wrote:
> On Mon, Nov 6, 2023 at 8:29 PM David Malcolm <dmalcolm@redhat.com>
> wrote:
> > 
> > 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?
> > 
> ...
> > +typedef unsigned int diagnostic_location_t;
> 
> One comment that occurred to me... for GCC we have a lot of PRs that
> are unhappy about the 32-bit location_t and the consequent issues
> that
> arise with very large source files, or with very long lines that lose
> column information.
> So far GCC has been able to get by with "don't do that" advice, but a
> more general libdiagnostics may need to avoid that arbitrary
> limitation? I feel like it may not be that long before GCC needs to
> deal with it as well, perhaps with a configure option, but even now,
> it could make sense for libdiagnostic to use a 64-bit location_t
> itself from the outset, so it won't need to change later, even if
> it's
> practically restricted to 32 bits for now.

That's a good point.

Perhaps the interface should give back a pointer to an opaque type, so
it would be:

  typedef struct diagnostic_location diagnostic_location;

and e.g.

extern const diagnostic_location *
diagnostic_manager_new_location_from_file_and_line (diagnostic_manager *diag_mgr,
						    const diagnostic_file *file,
						    diagnostic_line_num_t line_num);


where the diagnostic_manager owns the underlying memory.

Dave
diff mbox series

Patch

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;
+};