public inbox for binutils@sourceware.org
 help / color / mirror / Atom feed
From: David Malcolm <dmalcolm@redhat.com>
To: gcc-patches@gcc.gnu.org, binutils@sourceware.org
Cc: Nick Clifton <nickc@redhat.com>,
	Simon Sobisch <simonsobisch@gnu.org>,
	David Malcolm <dmalcolm@redhat.com>
Subject: [PATCH 1/2] libdiagnostics: header and examples
Date: Mon,  6 Nov 2023 17:29:57 -0500	[thread overview]
Message-ID: <20231106222959.2707741-2-dmalcolm@redhat.com> (raw)
In-Reply-To: <20231106222959.2707741-1-dmalcolm@redhat.com>

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


  reply	other threads:[~2023-11-06 22:30 UTC|newest]

Thread overview: 29+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-11-06 22:29 [PATCH/RFC] libdiagnostics: a shared library for emitting diagnostics David Malcolm
2023-11-06 22:29 ` David Malcolm [this message]
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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20231106222959.2707741-2-dmalcolm@redhat.com \
    --to=dmalcolm@redhat.com \
    --cc=binutils@sourceware.org \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=nickc@redhat.com \
    --cc=simonsobisch@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).